fix(#4728): persist provider-only default-model changes end to end

This commit is contained in:
Rod Boev authored and nesquena-hermes committed 2026-06-24 20:09:30 +00:00
1 parent 69d202b94a
commit bede035a5e
5 files changed
+27 -7

No files matched your search

+4 -3
View File
@@ -3555,7 +3555,7 @@ def _apply_advanced_model_options(model_cfg: dict, advanced: dict | None) -> Non
model_cfg["api_key"] = api_key
def set_hermes_default_model(model_id: str, advanced: dict | None = None) -> dict:
def set_hermes_default_model(model_id: str, provider: str | None = None, advanced: dict | None = None) -> dict:
"""Persist the Hermes default model in config.yaml and reload runtime config."""
selected_model = str(model_id or "").strip()
if not selected_model:
@@ -3572,6 +3572,7 @@ def set_hermes_default_model(model_id: str, advanced: dict | None = None) -> dic
model_cfg = {}
previous_provider = str(model_cfg.get("provider") or "").strip()
requested_provider = str(provider or "").strip()
resolved_model, resolved_provider, resolved_base_url = resolve_model_provider(
selected_model
)
@@ -3585,7 +3586,7 @@ def set_hermes_default_model(model_id: str, advanced: dict | None = None) -> dic
# CLI-shaped bare form via `_applyModelToDropdown()`'s normalising
# matcher — see `static/panels.js` (#895).
persisted_model = str(resolved_model or selected_model).strip()
persisted_provider = str(resolved_provider or previous_provider or "").strip()
persisted_provider = str(requested_provider or resolved_provider or previous_provider or "").strip()
# Never persist the bogus ``local`` value — see #1384. The auto-detect
# block in ``_build_available_models_uncached`` was rewriting unknown
# loopback hosts to ``provider: "local"``, which is not registered and
@@ -3617,7 +3618,7 @@ def set_hermes_default_model(model_id: str, advanced: dict | None = None) -> dic
# it triggers a live provider fetch (up to 8s) that blocks the HTTP response
# to the browser, causing a visible freeze on every Settings save (#895).
invalidate_models_cache()
return {"ok": True, "model": persisted_model}
return {"ok": True, "model": persisted_model, "provider": persisted_provider or None}
# ── Auxiliary model configuration ──────────────────────────────────────────
+3 -2
View File
@@ -10943,7 +10943,8 @@ def handle_post(handler, parsed) -> bool:
if parsed.path == "/api/default-model":
try:
advanced = body.get("advanced") if isinstance(body, dict) else None
return j(handler, set_hermes_default_model(body.get("model"), advanced=advanced))
provider = body.get("provider") if isinstance(body, dict) else None
return j(handler, set_hermes_default_model(body.get("model"), provider=provider, advanced=advanced))
except ValueError as e:
return bad(handler, str(e))
except RuntimeError as e:
@@ -10964,7 +10965,7 @@ def handle_post(handler, parsed) -> bool:
return bad(handler, str(exc), status=400)
if scope == "main":
try:
return j(handler, set_hermes_default_model(model, advanced=advanced))
return j(handler, set_hermes_default_model(model, provider=provider, advanced=advanced))
except ValueError as exc:
return bad(handler, str(exc), status=400)
return bad(handler, f"unknown scope: {scope}", status=400)
+2 -2
View File
@@ -9795,7 +9795,7 @@ async function saveSettings(andClose){
const saved=await api('/api/settings',{method:'POST',body:JSON.stringify(payload)});
if(modelChanged && model){
try{
await api('/api/default-model',{method:'POST',body:JSON.stringify({model})});
await api('/api/default-model',{method:'POST',body:JSON.stringify({model,provider:modelState.model_provider||null})});
body.default_model=model;
body.default_model_provider=(modelState&&modelState.model===model)?(modelState.model_provider||null):null;
}catch(_modelErr){
@@ -9825,7 +9825,7 @@ async function saveSettings(andClose){
const saved=await api('/api/settings',{method:'POST',body:JSON.stringify(body)});
if(modelChanged && model){
try{
await api('/api/default-model',{method:'POST',body:JSON.stringify({model})});
await api('/api/default-model',{method:'POST',body:JSON.stringify({model,provider:modelState.model_provider||null})});
body.default_model=model;
body.default_model_provider=(modelState&&modelState.model===model)?(modelState.model_provider||null):null;
}catch(_modelErr){
+17
View File
@@ -474,6 +474,23 @@ class TestAuxiliaryModelsBackend:
assert "reasoning_effort: none" in text
assert "DUMMY_KEY_DO_NOT_PRINT" in text
def test_set_hermes_default_model_persists_explicit_provider_override(self, monkeypatch, tmp_path):
from api import config
config_path = tmp_path / "config.yaml"
config_path.write_text("model:\n provider: openai\n default: gpt-5.5\n", encoding="utf-8")
monkeypatch.setattr(config, "_get_config_path", lambda: config_path)
monkeypatch.setattr(config, "reload_config", lambda: None)
monkeypatch.setattr(config, "invalidate_models_cache", lambda: None)
monkeypatch.setattr(config, "resolve_model_provider", lambda model: (model, "", None))
result = config.set_hermes_default_model("gpt-5.5", provider="anthropic")
assert result["ok"] is True
assert result["provider"] == "anthropic"
text = config_path.read_text(encoding="utf-8")
assert "provider: anthropic" in text
def test_set_auxiliary_model_persists_advanced_options(self, monkeypatch, tmp_path):
"""Gear-modal payload should persist supported per-slot options."""
from api import config
@@ -162,6 +162,7 @@ def test_save_settings_syncs_default_model_provider_with_saved_model():
autosave_block = panels_js[panels_js.index("const pwField=$('settingsPassword');"):panels_js.index("if(!pwDirty&&!modelDirty){", panels_js.index("const pwField=$('settingsPassword');")) + 24]
assert "_captureModelDropdownSelection($('settingsModel'))" in save_block
assert "JSON.stringify({model,provider:modelState.model_provider||null})" in save_block
assert "body.default_model_provider=(modelState&&modelState.model===model)?(modelState.model_provider||null):null;" in save_block
assert "const modelChanged=(model||'')!==(_settingsHermesDefaultModelOnOpen||'')||((modelState.model_provider||null)!==(_settingsHermesDefaultModelProviderOnOpen||null));" in save_block
assert "if(Object.prototype.hasOwnProperty.call(body,'default_model_provider')) window._activeProvider=body.default_model_provider||null;" in apply_saved_block