fix: fake models

This commit is contained in:
Brooklyn Nicholson 2026-04-13 14:57:42 -05:00
parent 77b97b810a
commit ebe3270430
6 changed files with 40 additions and 38 deletions

View file

@ -660,12 +660,12 @@ def switch_model(
api_key=api_key,
base_url=base_url,
)
except Exception:
except Exception as e:
validation = {
"accepted": True,
"persist": True,
"accepted": False,
"persist": False,
"recognized": False,
"message": None,
"message": f"Could not validate `{new_model}`: {e}",
}
if not validation.get("accepted"):

View file

@ -1842,12 +1842,11 @@ def validate_requested_model(
if suggestions:
suggestion_text = "\n Similar models: " + ", ".join(f"`{s}`" for s in suggestions)
return {
"accepted": True,
"persist": True,
"accepted": False,
"persist": False,
"recognized": False,
"message": (
f"Note: `{requested}` was not found in the OpenAI Codex model listing. "
f"It may still work if your account has access to it."
f"Model `{requested}` was not found in the OpenAI Codex model listing."
f"{suggestion_text}"
),
}
@ -1864,26 +1863,20 @@ def validate_requested_model(
"recognized": True,
"message": None,
}
else:
# API responded but model is not listed. Accept anyway —
# the user may have access to models not shown in the public
# listing (e.g. Z.AI Pro/Max plans can use glm-5 on coding
# endpoints even though it's not in /models). Warn but allow.
suggestions = get_close_matches(requested, api_models, n=3, cutoff=0.5)
suggestion_text = ""
if suggestions:
suggestion_text = "\n Similar models: " + ", ".join(f"`{s}`" for s in suggestions)
suggestions = get_close_matches(requested, api_models, n=3, cutoff=0.5)
suggestion_text = ""
if suggestions:
suggestion_text = "\n Similar models: " + ", ".join(f"`{s}`" for s in suggestions)
return {
"accepted": True,
"persist": True,
"recognized": False,
"message": (
f"Note: `{requested}` was not found in this provider's model listing. "
f"It may still work if your plan supports it."
f"{suggestion_text}"
),
}
return {
"accepted": False,
"persist": False,
"recognized": False,
"message": (
f"Model `{requested}` was not found in this provider's model listing."
f"{suggestion_text}"
),
}
# api_models is None — couldn't reach API. Accept and persist,
# but warn so typos don't silently break things.

View file

@ -401,7 +401,8 @@ class TestValidateFormatChecks:
def test_no_slash_model_rejected_if_not_in_api(self):
result = _validate("gpt-5.4", api_models=["openai/gpt-5.4"])
assert result["accepted"] is True
assert result["accepted"] is False
assert result["persist"] is False
assert "not found" in result["message"]
@ -427,15 +428,15 @@ class TestValidateApiFound:
# -- validate — API not found ------------------------------------------------
class TestValidateApiNotFound:
def test_model_not_in_api_accepted_with_warning(self):
def test_model_not_in_api_rejected_with_guidance(self):
result = _validate("anthropic/claude-nonexistent")
assert result["accepted"] is True
assert result["persist"] is True
assert result["accepted"] is False
assert result["persist"] is False
assert "not found" in result["message"]
def test_warning_includes_suggestions(self):
result = _validate("anthropic/claude-opus-4.5")
assert result["accepted"] is True
assert result["accepted"] is False
assert "Similar models" in result["message"]

View file

@ -155,7 +155,7 @@ def test_config_set_model_uses_live_switch_path(monkeypatch):
def _fake_apply(sid, session, raw):
seen["args"] = (sid, session["session_key"], raw)
return "new/model"
return {"value": "new/model", "warning": "catalog unreachable"}
monkeypatch.setattr(server, "_apply_model_switch", _fake_apply)
resp = server.handle_request(
@ -163,6 +163,7 @@ def test_config_set_model_uses_live_switch_path(monkeypatch):
)
assert resp["result"]["value"] == "new/model"
assert resp["result"]["warning"] == "catalog unreachable"
assert seen["args"] == ("sid", "session-key", "new/model")

View file

@ -327,11 +327,11 @@ def _restart_slash_worker(session: dict):
session["slash_worker"] = None
def _apply_model_switch(sid: str, session: dict, raw_input: str) -> str:
def _apply_model_switch(sid: str, session: dict, raw_input: str) -> dict:
agent = session.get("agent")
if not agent:
os.environ["HERMES_MODEL"] = raw_input
return raw_input
return {"value": raw_input, "warning": ""}
from hermes_cli.model_switch import switch_model
@ -355,7 +355,7 @@ def _apply_model_switch(sid: str, session: dict, raw_input: str) -> str:
os.environ["HERMES_MODEL"] = result.new_model
_restart_slash_worker(session)
_emit("session.info", sid, _session_info(agent))
return result.new_model
return {"value": result.new_model, "warning": result.warning_message or ""}
def _compress_session_history(session: dict) -> tuple[int, dict]:
@ -1273,10 +1273,11 @@ def _(rid, params: dict) -> dict:
if not value:
return _err(rid, 4002, "model value required")
if session:
value = _apply_model_switch(params.get("session_id", ""), session, value)
result = _apply_model_switch(params.get("session_id", ""), session, value)
else:
os.environ["HERMES_MODEL"] = value
return _ok(rid, {"key": key, "value": value})
result = {"value": value, "warning": ""}
return _ok(rid, {"key": key, "value": result["value"], "warning": result["warning"]})
except Exception as e:
return _err(rid, 5001, str(e))

View file

@ -119,6 +119,7 @@ const stripTokens = (text: string, re: RegExp) =>
const imageTokenMeta = (info: { height?: number; token_estimate?: number; width?: number } | null | undefined) => {
const dims = info?.width && info?.height ? `${info.width}x${info.height}` : ''
const tok =
typeof info?.token_estimate === 'number' && info.token_estimate > 0 ? `~${fmtK(info.token_estimate)} tok` : ''
@ -1988,6 +1989,11 @@ export function App({ gw }: { gw: GatewayClient }) {
}
sys(`model → ${r.value}`)
if (r.warning) {
sys(`warning: ${r.warning}`)
}
setInfo(prev => (prev ? { ...prev, model: r.value } : prev))
}
)