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 <noreply@github.com>
This commit is contained in:
LeonSGP43 2026-06-27 18:54:48 -07:00 committed by Teknium
parent 46e18804ad
commit c56b39c11e
2 changed files with 40 additions and 7 deletions

View file

@ -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:

View file

@ -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."""