diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 5735648f13..e812a337f5 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -916,6 +916,19 @@ def _try_openrouter() -> Tuple[Optional[OpenAI], Optional[str]]: default_headers=_OR_HEADERS), _OPENROUTER_MODEL +def _describe_openrouter_unavailable() -> str: + """Return a more precise OpenRouter auth failure reason for logs.""" + pool_present, entry = _select_pool_entry("openrouter") + if pool_present: + if entry is None: + return "OpenRouter credential pool has no usable entries (credentials may be exhausted)" + if not _pool_runtime_api_key(entry): + return "OpenRouter credential pool entry is missing a runtime API key" + if not str(os.getenv("OPENROUTER_API_KEY") or "").strip(): + return "OPENROUTER_API_KEY not set" + return "no usable OpenRouter credentials found" + + def _try_nous(vision: bool = False) -> Tuple[Optional[OpenAI], Optional[str]]: # Check cross-session rate limit guard before attempting Nous — # if another session already recorded a 429, skip Nous entirely @@ -1627,8 +1640,10 @@ def resolve_provider_client( if provider == "openrouter": client, default = _try_openrouter() if client is None: - logger.warning("resolve_provider_client: openrouter requested " - "but OPENROUTER_API_KEY not set") + logger.warning( + "resolve_provider_client: openrouter requested but %s", + _describe_openrouter_unavailable(), + ) return None, None final_model = _normalize_resolved_model(model or default, provider) return (_to_async_client(client, final_model) if async_mode diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index 4c775b8a6c..b5b74bd309 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -447,6 +447,34 @@ class TestExplicitProviderRouting: adapter = client.chat.completions assert adapter._is_oauth is False + def test_explicit_openrouter_pool_exhausted_logs_precise_warning(self, monkeypatch, caplog): + monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) + with patch("agent.auxiliary_client._select_pool_entry", return_value=(True, None)): + with caplog.at_level(logging.WARNING, logger="agent.auxiliary_client"): + client, model = resolve_provider_client("openrouter") + assert client is None + assert model is None + assert any( + "credential pool has no usable entries" in record.message + for record in caplog.records + ) + assert not any( + "OPENROUTER_API_KEY not set" in record.message + for record in caplog.records + ) + + def test_explicit_openrouter_missing_env_keeps_not_set_warning(self, monkeypatch, caplog): + monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) + with patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)): + with caplog.at_level(logging.WARNING, logger="agent.auxiliary_client"): + client, model = resolve_provider_client("openrouter") + assert client is None + assert model is None + assert any( + "OPENROUTER_API_KEY not set" in record.message + for record in caplog.records + ) + class TestGetTextAuxiliaryClient: """Test the full resolution chain for get_text_auxiliary_client."""