fix(desktop): preserve configured base_url on same-provider model switch (#41121)

The desktop model picker calls POST /api/model/set with provider+model only
(no base_url). _apply_main_model_assignment cleared model.base_url for every
non-custom provider, so re-picking a Xiaomi MiMo model wiped a Token Plan
endpoint (https://token-plan-*.xiaomimimo.com/v1) back to the registry default
api.xiaomimimo.com — breaking valid tp- keys with 401s.

Now base_url is cleared only when switching to a different provider (the stale
URL belonged to the old one); same-provider re-assignment preserves it, and an
explicitly supplied base_url is honored for any provider.
This commit is contained in:
Teknium
2026-06-07 02:48:21 -07:00
committed by GitHub
parent 349a3f601c
commit 0507e4630d
2 changed files with 87 additions and 17 deletions
+60 -8
View File
@@ -1269,10 +1269,11 @@ class TestWebServerEndpoints:
assert data.get("gateway_tools", []) == []
def test_apply_main_model_assignment_base_url_and_context_reconcile(self):
"""The shared main-slot assignment helper must persist base_url only for
custom providers, clear stale base_url for hosted ones, and always drop
a hardcoded context_length override. Both POST /api/model/set and
profile-model writes route through this, so the contract is pinned here."""
"""The shared main-slot assignment helper must persist a supplied
base_url, clear a stale base_url only when switching providers, preserve
it on same-provider re-assignment, and always drop a hardcoded
context_length override. Both POST /api/model/set and profile-model
writes route through this, so the contract is pinned here."""
from hermes_cli.web_server import _apply_main_model_assignment
# Custom + base_url → persisted; stale context_length dropped.
@@ -1284,16 +1285,39 @@ class TestWebServerEndpoints:
assert out["base_url"] == "http://127.0.0.1:8000/v1"
assert "context_length" not in out
# Hosted provider → stale base_url cleared (no base_url supplied).
# Switching providers (custom → openrouter) → stale base_url cleared.
out = _apply_main_model_assignment(
{"base_url": "http://127.0.0.1:8000/v1"}, "openrouter", "anthropic/claude-opus-4.8"
{"provider": "custom", "base_url": "http://127.0.0.1:8000/v1"},
"openrouter",
"anthropic/claude-opus-4.8",
)
assert out["provider"] == "openrouter"
assert out["base_url"] == ""
# Custom WITHOUT a base_url → don't invent one, clear any stale value.
# Same provider, no new base_url → existing custom endpoint preserved.
# Regression: picking a different MiMo model under xiaomi must NOT wipe a
# Token Plan base_url (https://token-plan-*.xiaomimimo.com/v1).
out = _apply_main_model_assignment(
{"base_url": "http://stale:1/v1"}, "custom", "m"
{"provider": "xiaomi", "base_url": "https://token-plan-ams.xiaomimimo.com/v1"},
"xiaomi",
"mimo-v2.5-pro",
)
assert out["provider"] == "xiaomi"
assert out["default"] == "mimo-v2.5-pro"
assert out["base_url"] == "https://token-plan-ams.xiaomimimo.com/v1"
# A supplied base_url is honored for any provider, not just custom.
out = _apply_main_model_assignment(
{"provider": "xiaomi"},
"xiaomi",
"mimo-v2.5",
"https://token-plan-cn.xiaomimimo.com/v1",
)
assert out["base_url"] == "https://token-plan-cn.xiaomimimo.com/v1"
# Switching providers without a base_url → don't invent one, clear stale.
out = _apply_main_model_assignment(
{"provider": "openrouter", "base_url": "http://stale:1/v1"}, "custom", "m"
)
assert out["base_url"] == ""
@@ -1377,6 +1401,34 @@ class TestWebServerEndpoints:
assert resp.status_code == 200
assert resp.json()["base_url"] == ""
def test_set_model_main_same_provider_preserves_base_url(self):
"""Re-picking a model under the SAME provider must NOT wipe a configured
base_url. Regression for the desktop bug where selecting a Xiaomi MiMo
model reset a Token Plan endpoint back to the registry default, breaking
Token Plan keys (https://token-plan-*.xiaomimimo.com/v1)."""
from hermes_cli.config import load_config, save_config
cfg = load_config()
cfg["model"] = {
"provider": "xiaomi",
"default": "mimo-v2.5-pro",
"base_url": "https://token-plan-ams.xiaomimimo.com/v1",
}
save_config(cfg)
# Desktop model picker sends provider+model only (no base_url).
resp = self.client.post(
"/api/model/set",
json={"scope": "main", "provider": "xiaomi", "model": "mimo-v2.5"},
)
assert resp.status_code == 200
assert resp.json()["base_url"] == "https://token-plan-ams.xiaomimimo.com/v1"
model_cfg = load_config().get("model")
assert isinstance(model_cfg, dict)
assert model_cfg["default"] == "mimo-v2.5"
assert model_cfg["base_url"] == "https://token-plan-ams.xiaomimimo.com/v1"
def test_set_model_main_reports_stale_auxiliary_pins(self):
"""Switching the main provider must report auxiliary slots still pinned
to a *different* provider so the UI can warn the user their helper tasks