diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 27d4c7ed..bed5c8d4 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -1149,10 +1149,10 @@ def _resolve_api_key_provider() -> Tuple[Optional[OpenAI], Optional[str]]: -def _try_openrouter() -> Tuple[Optional[OpenAI], Optional[str]]: +def _try_openrouter(explicit_api_key: str = None) -> Tuple[Optional[OpenAI], Optional[str]]: pool_present, entry = _select_pool_entry("openrouter") if pool_present: - or_key = _pool_runtime_api_key(entry) + or_key = explicit_api_key or _pool_runtime_api_key(entry) if not or_key: return None, None base_url = _pool_runtime_base_url(entry, OPENROUTER_BASE_URL) or OPENROUTER_BASE_URL @@ -1160,7 +1160,7 @@ def _try_openrouter() -> Tuple[Optional[OpenAI], Optional[str]]: return OpenAI(api_key=or_key, base_url=base_url, default_headers=_OR_HEADERS), _OPENROUTER_MODEL - or_key = os.getenv("OPENROUTER_API_KEY") + or_key = explicit_api_key or os.getenv("OPENROUTER_API_KEY") if not or_key: return None, None logger.debug("Auxiliary client: OpenRouter") @@ -2053,9 +2053,9 @@ def resolve_provider_client( return (_to_async_client(client, final_model, is_vision=is_vision) if async_mode else (client, final_model)) - # ── OpenRouter ─────────────────────────────────────────────────── + # ── OpenRouter ─────────────────────────────────────────── if provider == "openrouter": - client, default = _try_openrouter() + client, default = _try_openrouter(explicit_api_key=explicit_api_key) if client is None: logger.warning( "resolve_provider_client: openrouter requested but %s", diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index bc74fc73..c57a0b63 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -1818,3 +1818,78 @@ class TestBuildCallKwargsToolDedup: provider="openai", model="gpt-4o", messages=[], tools=None, ) assert "tools" not in kwargs + + +@pytest.fixture(autouse=True) +def _clean_env(monkeypatch): + """Strip provider env vars so each test starts clean.""" + for key in ( + "OPENROUTER_API_KEY", "OPENAI_BASE_URL", "OPENAI_API_KEY", + ): + monkeypatch.delenv(key, raising=False) + + +class TestOpenRouterExplicitApiKey: + """Test that explicit_api_key is correctly propagated to _try_openrouter().""" + + def test_resolve_provider_client_passes_explicit_api_key_to_openrouter( + self, monkeypatch + ): + """ + When resolve_provider_client() is called with explicit_api_key for OpenRouter, + the explicit key should be passed to the OpenAI client instead of falling back + to OPENROUTER_API_KEY env var. + """ + # Set up env var as fallback (should NOT be used when explicit_api_key is provided) + monkeypatch.setenv("OPENROUTER_API_KEY", "env-fallback-key") + + # Mock OpenAI to capture the api_key used + mock_openai = MagicMock() + mock_openai.return_value = MagicMock(name="openrouter-client") + + with patch("agent.auxiliary_client.OpenAI", mock_openai): + client, model = resolve_provider_client( + provider="openrouter", + explicit_api_key="explicit-pool-key", + ) + + # Verify a client was created + assert client is not None + # Verify the explicit key was used, not the env var fallback + mock_openai.assert_called_once() + call_kwargs = mock_openai.call_args[1] + assert call_kwargs["api_key"] == "explicit-pool-key", ( + f"Expected explicit_api_key to be passed, got: {call_kwargs['api_key']}" + ) + assert call_kwargs["api_key"] != "env-fallback-key", ( + "Should NOT fall back to OPENROUTER_API_KEY when explicit_api_key is provided" + ) + + def test_resolve_provider_client_without_explicit_api_key_falls_back_to_env( + self, monkeypatch + ): + """ + When resolve_provider_client() is called WITHOUT explicit_api_key for OpenRouter, + it should fall back to OPENROUTER_API_KEY env var. + """ + # Set up env var as fallback (should be used when explicit_api_key is NOT provided) + monkeypatch.setenv("OPENROUTER_API_KEY", "env-fallback-key") + + # Mock OpenAI to capture the api_key used + mock_openai = MagicMock() + mock_openai.return_value = MagicMock(name="openrouter-client") + + with patch("agent.auxiliary_client.OpenAI", mock_openai): + client, model = resolve_provider_client( + provider="openrouter", + explicit_api_key=None, + ) + + # Verify a client was created + assert client is not None + # Verify the env var fallback was used + mock_openai.assert_called_once() + call_kwargs = mock_openai.call_args[1] + assert call_kwargs["api_key"] == "env-fallback-key", ( + f"Expected env fallback key to be used when explicit_api_key is None, got: {call_kwargs['api_key']}" + )