fix(models): never auto-switch to a provider the user has no credentials for
/model <name> on provider A, where the name is only known to provider B
(static catalog or OpenRouter), switched the session to B even when B had
no key: an immediate 401 for most vendors, and for OpenRouter — whose
runtime resolves with an EMPTY key instead of raising — a silent switch
onto a metered aggregator. The dashboard's flat Model field had two more
copies of the same guess ("vendor/model on a native provider" → openrouter).
detect_provider_for_model() now walks its ladder as candidates and skips any
target without credentials (env/.env key, auth-store login, or a usable
credential pool entry). Exceptions: the user NAMED the provider (/model nous)
or there is no current provider yet ("auto") — then the guess is handed back
so the credential step fails loudly instead of silently ignoring input. A
vendor/ prefix naming a provider declared in `providers:` is a selection, not
a guess, and always routes. The dashboard fallbacks apply the same gate.
Tests that pinned "switch to OpenRouter/vendor with no key" now grant the
credential they assumed; two new invariants cover the gate.
This commit is contained in:
@@ -426,7 +426,12 @@ def _normalize_main_model_assignment(provider: str, model: str) -> tuple[str, st
|
||||
canonical = normalize_provider(cur_provider)
|
||||
prov_in = cur_provider
|
||||
else:
|
||||
canonical = prov_in = "openrouter"
|
||||
from hermes_cli.models_detect import provider_has_credentials
|
||||
|
||||
# Only guess OpenRouter when the user actually holds a key for it; otherwise keep the
|
||||
# pair as sent rather than persisting a provider they never selected.
|
||||
if provider_has_credentials("openrouter"):
|
||||
canonical = prov_in = "openrouter"
|
||||
|
||||
if canonical in _KNOWN_PROVIDER_NAMES and not canonical.startswith("custom"):
|
||||
try:
|
||||
@@ -770,11 +775,15 @@ def _infer_provider_on_model_change(model_val: str, prev_provider: str) -> tuple
|
||||
|
||||
if "/" in name:
|
||||
try:
|
||||
from hermes_cli.models_detect import provider_has_credentials
|
||||
|
||||
cur_is_aggregator = normalize_provider(prev_provider) in _AGGREGATOR_PROVIDERS
|
||||
# A vendor slug on a native provider is a guess at an aggregator; never guess one the
|
||||
# user has no key for — that silently writes a metered provider into config.yaml.
|
||||
if not cur_is_aggregator and provider_has_credentials("openrouter"):
|
||||
return "openrouter", name
|
||||
except Exception:
|
||||
cur_is_aggregator = False
|
||||
if not cur_is_aggregator:
|
||||
return "openrouter", name
|
||||
pass
|
||||
return "", name
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user