fix(model-switch): prevent custom-provider misattribution in model picker (#48305)

When the current provider is a custom endpoint (custom or custom:*), the model
switch pipeline must NOT auto-switch to a native provider/OpenRouter based on a
static-catalog match. The user explicitly configured their own endpoint and the
same model name may be served there; silently rewriting model.provider destroys
their config.

- detect_static_provider_for_model(): skip the static-catalog scan when the
  current provider is custom/custom:*
- switch_model() Step e: extend is_custom to cover custom:* so the
  detect_provider_for_model() last-resort fallback cannot fire

Salvaged from #48351 by Elshayib (authorship preserved).

Fixes #48305
This commit is contained in:
Elshayib 2026-06-24 18:51:49 +05:30 committed by kshitijk4poor
parent b85c460540
commit 1a435a6d5d
3 changed files with 34 additions and 2 deletions

View file

@ -1057,8 +1057,10 @@ def switch_model(
# --- Step e: detect_provider_for_model() as last resort ---
_base = current_base_url or ""
is_custom = current_provider in {"custom", "local"} or (
"localhost" in _base or "127.0.0.1" in _base
is_custom = (
current_provider in {"custom", "local"}
or current_provider.startswith("custom:")
or ("localhost" in _base or "127.0.0.1" in _base)
)
if (

View file

@ -1882,6 +1882,14 @@ def detect_static_provider_for_model(
return None
# --- Step 1: check static provider catalogs for a direct match ---
# If the current provider is a custom endpoint (custom or custom:*), never
# auto-switch away from it based on a static catalog match — the user
# explicitly configured their own endpoint and the same model name may be
# served there (#48305).
_is_custom_current = (
current_provider == "custom"
or current_provider.startswith("custom:")
)
for pid, models in _PROVIDER_MODELS.items():
if (
pid in current_keys
@ -1889,6 +1897,8 @@ def detect_static_provider_for_model(
or pid in _BORROWED_MODEL_PROVIDERS
):
continue
if _is_custom_current:
continue
if any(name_lower == m.lower() for m in models):
return (pid, name)

View file

@ -301,6 +301,26 @@ class TestDetectProviderForModel:
assert result is not None
assert result[0] not in {"nous",} # nous has claude models but shouldn't be suggested
def test_custom_provider_not_overridden_by_static_catalog(self):
"""When current provider is custom:*, a static-catalog match must NOT
override it otherwise a model served by the user's own endpoint gets
misattributed to a native provider, rewriting model.provider (#48305).
`gpt-5.4` is in the static openai catalog; with current=custom:foo,
detection must return None instead of switching to openai.
"""
assert detect_provider_for_model("gpt-5.4", "custom:foo") is None
def test_bare_custom_provider_not_overridden_by_static_catalog(self):
"""Same protection for the bare 'custom' provider."""
assert detect_provider_for_model("gpt-5.4", "custom") is None
def test_non_custom_provider_detection_unaffected(self):
"""The custom-provider guard must NOT change detection for non-custom
current providers a static-catalog model still routes normally."""
result = detect_provider_for_model("gpt-5.4", "openrouter")
assert result is not None and result[0] == "openai"
class TestIsNousFreeTier:
"""Tests for is_nous_free_tier — account tier detection."""