fix: custom/unknown model prefixes must not be stripped on provider switch — v0.50.100 (#752)
## Summary Regression fix for #751. Models with custom or unrecognized prefixes (e.g. `custom-provider/my-model`, `test/import-model`) were being incorrectly replaced with the active provider default. Root cause: `_normalize_provider_id("custom-provider")` matched the `"custom"` prefix and returned `"custom"`, which ≠ `active_provider` → normalization fired. Two-part fix: 1. Add `"custom"` and `"openrouter"` to the `model_provider` exclusion set in `_resolve_compatible_session_model` (parallel to the existing `active_provider` guard) 2. Return `""` for unknown prefixes in `_normalize_provider_id` so the `if model_provider` truthiness check safely short-circuits Adds a regression test covering `custom-provider/`, `test/`, `my-local-llm/`, and `lmstudio-community/` prefixes. ## Tests 1499 passed, 0 failures (was 2 failures before this fix)
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Hermes Web UI -- Changelog
|
||||
|
||||
## [v0.50.100] — 2026-04-20
|
||||
|
||||
### Fixed
|
||||
- **Session model normalization: unknown provider prefixes now pass through** — custom/unlisted model prefixes (e.g. `custom-provider/my-model`) are no longer incorrectly stripped when switching providers. Only well-known provider prefixes (`gpt-`, `claude-`, `gemini-`, etc.) are normalized. Regression introduced in v0.50.99. (#751)
|
||||
|
||||
## [v0.50.99] — 2026-04-20
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -183,7 +183,9 @@ def _normalize_provider_id(value: str | None) -> str:
|
||||
):
|
||||
if raw.startswith(prefix):
|
||||
return normalized
|
||||
return raw
|
||||
# Unknown prefix — return empty so callers treat it as "no match" and pass
|
||||
# the model through unchanged rather than incorrectly stripping it.
|
||||
return ""
|
||||
|
||||
|
||||
def _resolve_compatible_session_model(model_id: str | None) -> tuple[str, bool]:
|
||||
@@ -217,7 +219,9 @@ def _resolve_compatible_session_model(model_id: str | None) -> tuple[str, bool]:
|
||||
return model, False
|
||||
|
||||
model_provider = _normalize_provider_id(model[:slash])
|
||||
if model_provider and model_provider != active_provider and default_model:
|
||||
# Skip normalization for models on custom/openrouter namespaces — these are
|
||||
# user-controlled and should never be silently replaced.
|
||||
if model_provider and model_provider not in {"", "custom", "openrouter"} and model_provider != active_provider and default_model:
|
||||
return default_model, True
|
||||
return model, False
|
||||
|
||||
|
||||
@@ -430,3 +430,32 @@ class TestChatStartEffectiveModelRecovery:
|
||||
assert "localStorage.setItem('hermes-webui-model', startData.effective_model)" in src, (
|
||||
"effective_model correction must update the saved model preference"
|
||||
)
|
||||
|
||||
|
||||
def test_unknown_prefix_model_passes_through_unchanged(monkeypatch):
|
||||
"""Models with unknown/custom prefixes must never be stripped — regression test for #751."""
|
||||
import api.routes as routes
|
||||
|
||||
monkeypatch.setattr(
|
||||
routes,
|
||||
"get_available_models",
|
||||
lambda: {
|
||||
"active_provider": "openai-codex",
|
||||
"default_model": "gpt-5.4-mini",
|
||||
},
|
||||
)
|
||||
|
||||
for custom_model in (
|
||||
"custom-provider/test-model-999",
|
||||
"test/import-model",
|
||||
"my-local-llm/variant-1",
|
||||
"lmstudio-community/Qwen2.5-Coder-7B-Instruct-GGUF",
|
||||
):
|
||||
effective, changed = routes._resolve_compatible_session_model(custom_model)
|
||||
assert changed is False, (
|
||||
f"Model '{custom_model}' has an unknown prefix and must pass through unchanged, "
|
||||
f"but _resolve_compatible_session_model returned changed=True (effective='{effective}')"
|
||||
)
|
||||
assert effective == custom_model, (
|
||||
f"Expected '{custom_model}', got '{effective}'"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user