fix(models): route bare-provider /model switch through the cost-safe default
`detect_static_provider_for_model` handles a bare provider name typed as a model (e.g. `/model nous`) by returning `_PROVIDER_MODELS[provider][0]` — the first curated entry. For metered aggregators whose curated list is ordered most-capable-first (Nous Portal), entry [0] is the priciest flagship, so `/model nous` silently switched to it. This is exactly the billing footgun `_PROVIDER_SILENT_DEFAULT_OVERRIDES` / `get_default_model_for_provider` exist to prevent (per their docstring, a missing model "escalated to Opus and billed 863 requests before the user noticed"). The non-interactive fallback already routes through that cost-safe helper; the interactive `/model <provider>` path did not. Route this path through `get_default_model_for_provider` too. Providers without a silent-default override are unchanged (the helper returns `models[0]`), so only overridden providers (currently `nous`) change — from the flagship to the low-cost default. Adds regression tests: `/model nous` resolves to the cost-safe default, and non-overridden providers still resolve to their first catalog model.
This commit is contained in:
@@ -76,6 +76,46 @@ class TestGetDefaultModelForProvider:
|
||||
assert result == models_mod._PROVIDER_MODELS["openai-codex"][0]
|
||||
|
||||
|
||||
class TestDetectStaticProviderCostSafeDefault:
|
||||
"""detect_static_provider_for_model must apply the same cost-safe default
|
||||
as get_default_model_for_provider when a bare provider name is typed as a
|
||||
model (e.g. ``/model nous``)."""
|
||||
|
||||
def test_bare_nous_does_not_escalate_to_flagship(self):
|
||||
from hermes_cli.models import (
|
||||
_PROVIDER_MODELS,
|
||||
get_default_model_for_provider,
|
||||
detect_static_provider_for_model,
|
||||
)
|
||||
|
||||
result = detect_static_provider_for_model("nous", "openrouter")
|
||||
assert result is not None
|
||||
provider, model = result
|
||||
assert provider == "nous"
|
||||
# Must match the cost-safe silent default, NOT the priciest catalog
|
||||
# entry [0]. Regression: this path returned _PROVIDER_MODELS["nous"][0]
|
||||
# directly, re-introducing the billing footgun on the interactive
|
||||
# ``/model nous`` path.
|
||||
assert model == get_default_model_for_provider("nous")
|
||||
assert "opus" not in model.lower()
|
||||
assert model != _PROVIDER_MODELS["nous"][0]
|
||||
|
||||
def test_provider_without_override_still_uses_first_model(self):
|
||||
"""Providers with no silent-default override are unchanged."""
|
||||
from hermes_cli.models import (
|
||||
_PROVIDER_MODELS,
|
||||
_PROVIDER_SILENT_DEFAULT_OVERRIDES,
|
||||
detect_static_provider_for_model,
|
||||
)
|
||||
|
||||
for provider in ("anthropic", "xai"):
|
||||
if provider in _PROVIDER_SILENT_DEFAULT_OVERRIDES:
|
||||
continue
|
||||
result = detect_static_provider_for_model(provider, "openrouter")
|
||||
assert result is not None
|
||||
assert result[1] == _PROVIDER_MODELS[provider][0]
|
||||
|
||||
|
||||
class TestGatewayEmptyModelFallback:
|
||||
"""Test that _resolve_session_agent_runtime fills in empty model from provider catalog."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user