fix(agent): honor explicit free OpenRouter models
This commit is contained in:
@@ -2943,7 +2943,6 @@ def _try_openrouter(explicit_api_key: str = None, model: str = None) -> Tuple[Op
|
||||
"auxiliary.free_only.",
|
||||
or_model,
|
||||
)
|
||||
_mark_provider_unhealthy("openrouter", ttl=60)
|
||||
return None, None
|
||||
if not _is_free_model(or_model):
|
||||
_warn_paid_lane_once(or_model)
|
||||
@@ -2969,8 +2968,15 @@ def _try_openrouter(explicit_api_key: str = None, model: str = None) -> Tuple[Op
|
||||
default_headers=build_or_headers()), or_model
|
||||
|
||||
|
||||
def _describe_openrouter_unavailable() -> str:
|
||||
"""Return a more precise OpenRouter auth failure reason for logs."""
|
||||
def _describe_openrouter_unavailable(model: str = None) -> str:
|
||||
"""Return the policy or credential reason OpenRouter was unavailable."""
|
||||
free_only, cfg_model = _aux_openrouter_settings()
|
||||
or_model = model or cfg_model
|
||||
if free_only and not _is_free_model(or_model):
|
||||
return (
|
||||
f"auxiliary.free_only rejected non-free model {or_model!r}; "
|
||||
"the request was skipped before provider availability checks"
|
||||
)
|
||||
pool_present, entry = _select_pool_entry("openrouter")
|
||||
if pool_present:
|
||||
if entry is None:
|
||||
@@ -6489,11 +6495,14 @@ def resolve_provider_client(
|
||||
|
||||
# ── OpenRouter ───────────────────────────────────────────
|
||||
if provider == "openrouter":
|
||||
client, default = _try_openrouter(explicit_api_key=explicit_api_key)
|
||||
client, default = _try_openrouter(
|
||||
explicit_api_key=explicit_api_key,
|
||||
model=model,
|
||||
)
|
||||
if client is None:
|
||||
logger.warning(
|
||||
"resolve_provider_client: openrouter requested but %s",
|
||||
_describe_openrouter_unavailable(),
|
||||
_describe_openrouter_unavailable(model=model),
|
||||
)
|
||||
return None, None
|
||||
final_model = _normalize_resolved_model(model or default, provider)
|
||||
|
||||
@@ -1042,6 +1042,51 @@ class TestOpenRouterPaidLaneGuard:
|
||||
assert model is None
|
||||
mock_openai.assert_not_called()
|
||||
|
||||
def test_resolver_forwards_explicit_free_model_to_gate(self, monkeypatch):
|
||||
"""The concrete OpenRouter route gates the caller's model, not its default."""
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
|
||||
with patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)), \
|
||||
patch("hermes_cli.config.load_config_readonly",
|
||||
return_value={"auxiliary": {"free_only": True}}), \
|
||||
patch("agent.auxiliary_client.OpenAI") as mock_openai:
|
||||
mock_client = MagicMock(name="openrouter_client")
|
||||
mock_openai.return_value = mock_client
|
||||
client, model = resolve_provider_client(
|
||||
"openrouter", model="nvidia/nemotron-3-ultra-550b-a55b:free"
|
||||
)
|
||||
|
||||
assert client is mock_client
|
||||
assert model == "nvidia/nemotron-3-ultra-550b-a55b:free"
|
||||
|
||||
def test_free_only_gate_does_not_mark_openrouter_unhealthy(self, monkeypatch):
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
|
||||
with patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)), \
|
||||
patch("hermes_cli.config.load_config_readonly",
|
||||
return_value={"auxiliary": {"free_only": True}}), \
|
||||
patch("agent.auxiliary_client._mark_provider_unhealthy") as mark_unhealthy:
|
||||
client, model = resolve_provider_client(
|
||||
"openrouter", model="google/gemini-3.6-flash"
|
||||
)
|
||||
|
||||
assert client is None
|
||||
assert model is None
|
||||
mark_unhealthy.assert_not_called()
|
||||
|
||||
def test_free_only_gate_reports_policy_not_credentials(self, monkeypatch, caplog):
|
||||
import logging
|
||||
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
|
||||
with patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)), \
|
||||
patch("hermes_cli.config.load_config_readonly",
|
||||
return_value={"auxiliary": {"free_only": True}}), \
|
||||
caplog.at_level(logging.WARNING, logger="agent.auxiliary_client"):
|
||||
resolve_provider_client("openrouter", model="google/gemini-3.6-flash")
|
||||
|
||||
messages = [record.getMessage() for record in caplog.records]
|
||||
assert any("free_only" in message and "google/gemini-3.6-flash" in message
|
||||
for message in messages)
|
||||
assert not any("credentials" in message for message in messages)
|
||||
|
||||
def test_paid_lane_warns_once(self, monkeypatch, caplog):
|
||||
"""Engaging the default paid model logs a WARNING (once per model)."""
|
||||
import logging
|
||||
|
||||
Reference in New Issue
Block a user