From c56b39c11e814ee571e391fc46384b0e7888a490 Mon Sep 17 00:00:00 2001 From: LeonSGP43 Date: Sat, 27 Jun 2026 18:54:48 -0700 Subject: [PATCH] fix(auxiliary): fall back to OPENROUTER_API_KEY when credential pool exhausted _try_openrouter() returned (None, None) whenever an OpenRouter credential pool existed but was exhausted (_select_pool_entry -> (True, None)), making the OPENROUTER_API_KEY env-var fallback unreachable. Auxiliary tasks (compression, vision, web_extract) silently failed even with a valid env key. Now the pool-present branch only returns early when it successfully builds a client; an exhausted pool falls through to the env-var path. The final failure (pool exhausted AND no env var) still marks the provider unhealthy. Fixes #23452. Co-authored-by: ambition0802 --- agent/auxiliary_client.py | 15 +++++++------ tests/agent/test_auxiliary_client.py | 32 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 3e776eebac6..c1c79b6aad0 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -1669,13 +1669,14 @@ def _try_openrouter(explicit_api_key: str = None, model: str = None) -> Tuple[Op pool_present, entry = _select_pool_entry("openrouter") if pool_present: or_key = explicit_api_key or _pool_runtime_api_key(entry) - if not or_key: - _mark_provider_unhealthy("openrouter", ttl=60) - return None, None - base_url = _pool_runtime_base_url(entry, OPENROUTER_BASE_URL) or OPENROUTER_BASE_URL - logger.debug("Auxiliary client: OpenRouter via pool") - return OpenAI(api_key=or_key, base_url=base_url, - default_headers=build_or_headers()), model or _OPENROUTER_MODEL + if or_key: + base_url = _pool_runtime_base_url(entry, OPENROUTER_BASE_URL) or OPENROUTER_BASE_URL + logger.debug("Auxiliary client: OpenRouter via pool") + return OpenAI(api_key=or_key, base_url=base_url, + default_headers=build_or_headers()), model or _OPENROUTER_MODEL + # Pool exists but is exhausted (no usable runtime key) — fall through to + # the OPENROUTER_API_KEY env-var path rather than failing outright. + logger.debug("Auxiliary client: OpenRouter pool exhausted, trying OPENROUTER_API_KEY") or_key = explicit_api_key or os.getenv("OPENROUTER_API_KEY") if not or_key: diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index 2100b5dcdd6..47e0a3d4f90 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -27,6 +27,9 @@ from agent.auxiliary_client import ( _refresh_nous_recommended_model, _normalize_aux_provider, _try_payment_fallback, + _try_openrouter, + _OPENROUTER_MODEL, + OPENROUTER_BASE_URL, _resolve_auto, _resolve_xai_oauth_for_aux, _CodexCompletionsAdapter, @@ -919,6 +922,35 @@ class TestExplicitProviderRouting: for record in caplog.records ) + def test_try_openrouter_pool_exhausted_falls_back_to_env(self, monkeypatch): + """Pool present but exhausted → fall through to OPENROUTER_API_KEY env var.""" + monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-env-fallback") + with patch("agent.auxiliary_client._select_pool_entry", return_value=(True, None)), \ + patch("agent.auxiliary_client.OpenAI") as mock_openai: + mock_client = MagicMock(name="openrouter_client") + mock_openai.return_value = mock_client + + client, model = _try_openrouter() + + assert client is mock_client + assert model == _OPENROUTER_MODEL + mock_openai.assert_called_once() + assert mock_openai.call_args.kwargs["api_key"] == "sk-or-env-fallback" + assert mock_openai.call_args.kwargs["base_url"] == OPENROUTER_BASE_URL + + def test_try_openrouter_pool_exhausted_no_env_marks_unhealthy(self, monkeypatch): + """Pool exhausted AND no env var → final failure marks provider unhealthy.""" + monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) + with patch("agent.auxiliary_client._select_pool_entry", return_value=(True, None)), \ + patch("agent.auxiliary_client._mark_provider_unhealthy") as mock_mark, \ + patch("agent.auxiliary_client.OpenAI") as mock_openai: + client, model = _try_openrouter() + + assert client is None + assert model is None + mock_openai.assert_not_called() + mock_mark.assert_called_once_with("openrouter", ttl=60) + class TestGetTextAuxiliaryClient: """Test the full resolution chain for get_text_auxiliary_client."""