fix(auxiliary): propagate explicit_api_key to _try_openrouter()
When resolve_provider_client() passes explicit_api_key for OpenRouter auxiliary tasks, _try_openrouter() now accepts and honors this parameter instead of silently ignoring it and falling back to OPENROUTER_API_KEY env var. Root cause: _try_openrouter() had no explicit_api_key parameter, so even when callers wanted to pass a runtime credential pool key, it could not be used. Fix: - Add explicit_api_key: str = None parameter to _try_openrouter() - Prioritize explicit_api_key over pool key and env var - Update resolve_provider_client() call site to pass explicit_api_key Regression coverage: - Test that explicit_api_key is passed to OpenAI client when provided - Test that fallback to OPENROUTER_API_KEY still works when explicit_api_key is None Closes #18338
This commit is contained in:
parent
73bcd83dba
commit
af98122793
@ -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",
|
||||
|
||||
@ -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']}"
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user