diff --git a/api/routes.py b/api/routes.py index ec50dc7c..572a775c 100644 --- a/api/routes.py +++ b/api/routes.py @@ -3196,18 +3196,7 @@ def _catalog_has_provider( provider_raw in raw_provider_ids or (provider_normalized and provider_normalized in raw_provider_ids) or (provider_normalized and provider_normalized in normalized_provider_ids) - # Compound provider IDs like "custom:zenmux-relay" normalize to - # "custom" which is a raw catalog provider_id. The three checks - # above compare raw-vs-raw and normalized-vs-normalized but miss - # normalized-vs-raw for compound hints. Without this, a - # @custom:relay-name:model selection is silently reverted to the - # profile default on every non-explicit turn because the catalog - # lookup fails and the fallback branch at line ~3932 fires. - or ( - provider_normalized - and ":" in provider_raw - and provider_normalized in raw_provider_ids - ) + ) diff --git a/static/messages.js b/static/messages.js index 4ae2b75e..e6085a5b 100644 --- a/static/messages.js +++ b/static/messages.js @@ -1281,6 +1281,7 @@ async function send(){ const _isCrossProviderPick = _modelState.model && _modelState.model_provider && _defaultModel + && _activeProvider && _modelState.model !== _defaultModel && String(_modelState.model_provider||'') !== String(_activeProvider||''); const _explicitPick = _pendingPickMatch || _isCrossProviderPick; diff --git a/tests/test_catalog_has_provider_compound_ids.py b/tests/test_catalog_has_provider_compound_ids.py new file mode 100644 index 00000000..9c415fdd --- /dev/null +++ b/tests/test_catalog_has_provider_compound_ids.py @@ -0,0 +1,66 @@ +"""Tests for _catalog_has_provider() with compound provider IDs. + +Compound provider IDs like "custom:zenmux-relay" or "custom:glm-free-relay" +normalize to "custom" via _normalize_provider_id(). The existing three +clauses in _catalog_has_provider() already handle these correctly: + + C1: exact raw match ("custom:zenmux-relay" ∈ raw_ids) + C2: normalized ∈ raw_ids ("custom" ∈ raw_ids) + C3: normalized ∈ norm_ids ("custom" ∈ norm_ids) + +This test guards against regressions and verifies that no 4th clause is +needed for compound IDs. +""" +from __future__ import annotations + +from api.routes import _catalog_has_provider + + +def test_compound_provider_exact_raw_match(): + """C1: exact raw match for a compound ID like 'custom:zenmux-relay'.""" + assert _catalog_has_provider( + "custom:zenmux-relay", + "custom", + {"custom:zenmux-relay"}, + {"custom"}, + ) + + +def test_compound_provider_normalized_in_raw_ids(): + """C2: normalized form 'custom' found in raw_provider_ids.""" + assert _catalog_has_provider( + "custom:zenmux-relay", + "custom", + {"custom"}, + {"custom"}, + ) + + +def test_compound_provider_normalized_in_norm_ids(): + """C3: normalized form 'custom' found in normalized_provider_ids.""" + assert _catalog_has_provider( + "custom:zenmux-relay", + "custom", + {"other"}, + {"custom"}, + ) + + +def test_compound_provider_not_found(): + """Returns False when neither raw nor normalized form is in the catalog.""" + assert not _catalog_has_provider( + "custom:unknown-relay", + "custom", + {"openrouter", "nous"}, + {"openrouter", "nous"}, + ) + + +def test_compound_provider_glm_free_relay(): + """Real-world case: custom:glm-free-relay providing z-ai/glm-5.2-free.""" + assert _catalog_has_provider( + "custom:glm-free-relay", + "custom", + {"custom:glm-free-relay"}, + {"custom"}, + )