diff --git a/agent/model_metadata.py b/agent/model_metadata.py index a0e3bea8c0cd..7f81ded7d8f0 100644 --- a/agent/model_metadata.py +++ b/agent/model_metadata.py @@ -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, diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index 11c2fa06aa98..deb8ff3e1708 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -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: diff --git a/hermes_cli/models.py b/hermes_cli/models.py index 9fc68933e810..1b11f49eff08 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -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", @@ -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", @@ -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: @@ -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, diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 52f6e36d66cd..02f597b74525 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -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"], diff --git a/tests/hermes_cli/test_model_validation.py b/tests/hermes_cli/test_model_validation.py index 5ed6b9d54340..ac83127cd5ef 100644 --- a/tests/hermes_cli/test_model_validation.py +++ b/tests/hermes_cli/test_model_validation.py @@ -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" @@ -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 ----------------