Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions agent/model_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def _strip_provider_prefix(model: str) -> str:
"grok-2": 131072, # grok-2, grok-2-1212, grok-2-latest
"grok": 131072, # catch-all (grok-beta, unknown grok-*)
# Kimi
"k2.6-code-preview": 256000,
"kimi": 262144,
# Arcee
"trinity": 262144,
Expand Down
19 changes: 19 additions & 0 deletions hermes_cli/model_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,25 @@ def switch_model(
)

target_provider = pdef.id
# Some providers have a models.dev ID that differs from the Hermes
# internal ID (e.g. models.dev uses "kimi-for-coding" but Hermes uses
# "kimi-coding" everywhere else). Prefer the Hermes internal ID when
# the user explicitly named a provider we know natively.
try:
from hermes_cli.auth import PROVIDER_REGISTRY
_HERMES_ALIASES = {
"kimi": "kimi-coding",
"kimi-for-coding": "kimi-coding",
"moonshot": "kimi-coding",
"kimi-cn": "kimi-coding-cn",
"moonshot-cn": "kimi-coding-cn",
}
_raw = explicit_provider.strip().lower()
_canonical_explicit = _HERMES_ALIASES.get(_raw, _raw)
if _canonical_explicit in PROVIDER_REGISTRY:
target_provider = _canonical_explicit
except Exception:
pass

# If no model specified, try auto-detect from endpoint
if not new_model:
Expand Down
26 changes: 26 additions & 0 deletions hermes_cli/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ def _codex_curated_models() -> list[str]:
],
"kimi-coding": [
"kimi-for-coding",
"k2.6-code-preview",
"kimi-k2.5",
"kimi-k2-thinking",
"kimi-k2-thinking-turbo",
Expand All @@ -165,11 +166,13 @@ def _codex_curated_models() -> list[str]:
],
"kimi-coding-cn": [
"kimi-k2.5",
"k2.6-code-preview",
"kimi-k2-thinking",
"kimi-k2-turbo-preview",
"kimi-k2-0905-preview",
],
"moonshot": [
"k2.6-code-preview",
"kimi-k2.5",
"kimi-k2-thinking",
"kimi-k2-turbo-preview",
Expand Down Expand Up @@ -1949,6 +1952,17 @@ def validate_requested_model(
# 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.

# Trust our static curated list when the live API is incomplete
# (e.g. Kimi Coding Plan only exposes kimi-for-coding).
static_models = _PROVIDER_MODELS.get(normalized, [])
if requested_for_lookup in static_models:
return {
"accepted": True,
"persist": True,
"recognized": True,
"message": None,
}

# Auto-correct if the top match is very similar (e.g. typo)
auto = get_close_matches(requested_for_lookup, api_models, n=1, cutoff=0.9)
if auto:
Expand Down Expand Up @@ -2015,6 +2029,18 @@ def validate_requested_model(
pass # Fall through to generic warning

provider_label = _PROVIDER_LABELS.get(normalized, normalized)

# Trust our static curated list even when the live API is unreachable.
# If we hand-picked the model, there's no value in warning the user.
static_models = _PROVIDER_MODELS.get(normalized, [])
if requested_for_lookup in static_models:
return {
"accepted": True,
"persist": True,
"recognized": True,
"message": None,
}

return {
"accepted": True,
"persist": True,
Expand Down
4 changes: 2 additions & 2 deletions hermes_cli/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ def _supports_same_provider_pool_setup(provider: str) -> bool:
"gemma-4-31b-it", "gemma-4-26b-it",
],
"zai": ["glm-5.1", "glm-5", "glm-4.7", "glm-4.5", "glm-4.5-flash"],
"kimi-coding": ["kimi-k2.5", "kimi-k2-thinking", "kimi-k2-turbo-preview"],
"kimi-coding-cn": ["kimi-k2.5", "kimi-k2-thinking", "kimi-k2-turbo-preview"],
"kimi-coding": ["k2.6-code-preview", "kimi-k2.5", "kimi-k2-thinking", "kimi-k2-turbo-preview"],
"kimi-coding-cn": ["k2.6-code-preview", "kimi-k2.5", "kimi-k2-thinking", "kimi-k2-turbo-preview"],
"arcee": ["trinity-large-thinking", "trinity-large-preview", "trinity-mini"],
"minimax": ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"],
"minimax-cn": ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"],
Expand Down
12 changes: 11 additions & 1 deletion tests/hermes_cli/test_model_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def test_case_insensitive(self):
class TestProviderLabel:
def test_known_labels_and_auto(self):
assert provider_label("anthropic") == "Anthropic"
assert provider_label("kimi") == "Kimi / Moonshot"
assert provider_label("kimi") == "Kimi / Kimi Coding Plan"
assert provider_label("copilot") == "GitHub Copilot"
assert provider_label("copilot-acp") == "GitHub Copilot ACP"
assert provider_label("auto") == "Auto"
Expand Down Expand Up @@ -453,6 +453,16 @@ def test_dissimilar_model_shows_suggestions_not_autocorrect(self):
assert result.get("corrected_model") is None
assert "not found" in result["message"]

def test_static_list_trusted_when_api_is_incomplete(self):
"""If the live API omits a model we curate statically, trust the static list."""
# Simulate Kimi Coding Plan's /models endpoint, which only exposes
# kimi-for-coding even though newer models like k2.6-code-preview work.
with patch("hermes_cli.models.fetch_api_models", return_value=["kimi-for-coding"]):
result = validate_requested_model("k2.6-code-preview", "kimi-coding")
assert result["accepted"] is True
assert result["recognized"] is True
assert result["message"] is None


# -- validate — API unreachable — accept and persist everything ----------------

Expand Down