{"repo":"NousResearch/hermes-agent","public_id":"7475","display_label":"ZAI/GLM model name and auxiliary provider corrections","summary":"Several issues with GLM model names having dots stripped (causing 'model not found' errors) on ZAI and OpenCode Zen providers were addressed, along with corrections to how auxiliary vision models and provider overrides are handled.","subsystem":"agent","counts":{"members":5,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":2,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":8794},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"stale","last_verified_at":"2026-04-13T04:22:59+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.33599999999999997},"fix_edges":[{"issue_number":7421,"pr_number":8794,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":7475,"pr_number":8794,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":7674,"pr_number":8794,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":7710,"pr_number":8794,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":7475,"title":"[Bug]: Select Model GLM-4.7 get \"提示模型不存在，请检查模型代码\"","url":null,"state":"closed"},{"kind":"issue","number":60381,"title":"bug: zai provider vision — glm-4v-flash returns 1211 '模型不存在' despite model working via direct API","url":null,"state":"closed"},{"kind":"pr","number":7719,"title":"fix(zai): preserve dots in GLM model names on Anthropic-compatible endpoints","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#8794","number":8794}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":8794,"title":"fix: preserve dots in model names for OpenCode Zen and ZAI providers","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/model_normalize.py","hermes_cli/models.py","hermes_cli/setup.py","run_agent.py","tests/agent/test_minimax_provider.py","tests/hermes_cli/test_model_normalize.py"],"fix_hunks":[{"file":"hermes_cli/model_normalize.py","hunks":["@@ -8,8 +8,9 @@\n   hyphens: ``claude-sonnet-4-6``.\n - **Copilot** expects bare names *with* dots preserved:\n   ``claude-sonnet-4.6``.\n-- **OpenCode Zen** follows the same dot-to-hyphen convention as\n-  Anthropic: ``claude-sonnet-4-6``.\n+- **OpenCode Zen** preserves dots for GPT/GLM/Gemini/Kimi/MiniMax-style\n+  model IDs, but Claude still uses hyphenated native names like\n+  ``claude-sonnet-4-6``.\n - **OpenCode Go** preserves dots in model names: ``minimax-m2.7``.\n - **DeepSeek** only accepts two model identifiers:\n   ``deepseek-chat`` and ``deepseek-reasoner``.","@@ -67,7 +68,6 @@\n # Providers that want bare names with dots replaced by hyphens.\n _DOT_TO_HYPHEN_PROVIDERS: frozenset[str] = frozenset({\n     \"anthropic\",\n-    \"opencode-zen\",\n })\n \n # Providers that want bare names with dots preserved.","@@ -329,6 +329,9 @@ def normalize_model_for_provider(model_input: str, target_provider: str) -> str:\n         >>> normalize_model_for_provider(\"claude-sonnet-4.6\", \"opencode-zen\")\n         'claude-sonnet-4-6'\n \n+        >>> normalize_model_for_provider(\"minimax-m2.5-free\", \"opencode-zen\")\n+        'minimax-m2.5-free'\n+\n         >>> normalize_model_for_provider(\"deepseek-v3\", \"deepseek\")\n         'deepseek-chat'\n ","@@ -351,7 +354,16 @@ def normalize_model_for_provider(model_input: str, target_provider: str) -> str:\n     if provider in _AGGREGATOR_PROVIDERS:\n         return _prepend_vendor(name)\n \n-    # --- Anthropic / OpenCode: strip matching provider prefix, dots -> hyphens ---\n+    # --- OpenCode Zen: Claude stays hyphenated; other models keep dots ---\n+    if provider == \"opencode-zen\":\n+        bare = _strip_matching_provider_prefix(name, provider)\n+        if \"/\" in bare:\n+            return bare\n+        if bare.lower().startswith(\"claude-\"):\n+            return _dots_to_hyphens(bare)\n+        return bare\n+\n+    # --- Anthropic: strip matching provider prefix, dots -> hyphens ---\n     if provider in _DOT_TO_HYPHEN_PROVIDERS:\n         bare = _strip_matching_provider_prefix(name, provider)\n         if \"/\" in bare:"]},{"file":"hermes_cli/models.py","hunks":["@@ -130,6 +130,7 @@ def _codex_curated_models() -> list[str]:\n         \"gemma-4-26b-it\",\n     ],\n     \"zai\": [\n+        \"glm-5.1\",\n         \"glm-5\",\n         \"glm-5-turbo\",\n         \"glm-4.7\","]},{"file":"hermes_cli/setup.py","hunks":["@@ -104,7 +104,7 @@ def _supports_same_provider_pool_setup(provider: str) -> bool:\n         \"gemini-2.5-pro\", \"gemini-2.5-flash\", \"gemini-2.5-flash-lite\",\n         \"gemma-4-31b-it\", \"gemma-4-26b-it\",\n     ],\n-    \"zai\": [\"glm-5\", \"glm-4.7\", \"glm-4.5\", \"glm-4.5-flash\"],\n+    \"zai\": [\"glm-5.1\", \"glm-5\", \"glm-4.7\", \"glm-4.5\", \"glm-4.5-flash\"],\n     \"kimi-coding\": [\"kimi-k2.5\", \"kimi-k2-thinking\", \"kimi-k2-turbo-preview\"],\n     \"minimax\": [\"MiniMax-M2.7\", \"MiniMax-M2.5\", \"MiniMax-M2.1\", \"MiniMax-M2\"],\n     \"minimax-cn\": [\"MiniMax-M2.7\", \"MiniMax-M2.5\", \"MiniMax-M2.1\", \"MiniMax-M2\"],"]},{"file":"run_agent.py","hunks":["@@ -5856,11 +5856,12 @@ def _anthropic_preserve_dots(self) -> bool:\n         \"\"\"True when using an anthropic-compatible endpoint that preserves dots in model names.\n         Alibaba/DashScope keeps dots (e.g. qwen3.5-plus).\n         MiniMax keeps dots (e.g. MiniMax-M2.7).\n-        OpenCode Go keeps dots (e.g. minimax-m2.7).\"\"\"\n-        if (getattr(self, \"provider\", \"\") or \"\").lower() in {\"alibaba\", \"minimax\", \"minimax-cn\", \"opencode-go\"}:\n+        OpenCode Go/Zen keeps dots for non-Claude models (e.g. minimax-m2.5-free).\n+        ZAI/Zhipu keeps dots (e.g. glm-4.7, glm-5.1).\"\"\"\n+        if (getattr(self, \"provider\", \"\") or \"\").lower() in {\"alibaba\", \"minimax\", \"minimax-cn\", \"opencode-go\", \"opencode-zen\", \"zai\"}:\n             return True\n         base = (getattr(self, \"base_url\", \"\") or \"\").lower()\n-        return \"dashscope\" in base or \"aliyuncs\" in base or \"minimax\" in base or \"opencode.ai/zen/go\" in base\n+        return \"dashscope\" in base or \"aliyuncs\" in base or \"minimax\" in base or \"opencode.ai/zen/\" in base or \"bigmodel.cn\" in base\n \n     def _is_qwen_portal(self) -> bool:\n         \"\"\"Return True when the base URL targets Qwen Portal.\"\"\""]},{"file":"tests/agent/test_minimax_provider.py","hunks":["@@ -308,6 +308,34 @@ def test_anthropic_does_not_preserve_dots(self):\n         from run_agent import AIAgent\n         assert AIAgent._anthropic_preserve_dots(agent) is False\n \n+    def test_opencode_zen_provider_preserves_dots(self):\n+        from types import SimpleNamespace\n+        agent = SimpleNamespace(provider=\"opencode-zen\", base_url=\"\")\n+        from run_agent import AIAgent\n+        assert AIAgent._anthropic_preserve_dots(agent) is True\n+\n+    def test_opencode_zen_url_preserves_dots(self):\n+        from types import SimpleNamespace\n+        agent = SimpleNamespace(provider=\"custom\", base_url=\"https://opencode.ai/zen/v1\")\n+        from run_agent import AIAgent\n+        assert AIAgent._anthropic_preserve_dots(agent) is True\n+\n+    def test_zai_provider_preserves_dots(self):\n+        from types import SimpleNamespace\n+        agent = SimpleNamespace(provider=\"zai\", base_url=\"\")\n+        from run_agent import AIAgent\n+        assert AIAgent._anthropic_preserve_dots(agent) is True\n+\n+    def test_bigmodel_cn_url_preserves_dots(self):\n+        from types import SimpleNamespace\n+        agent = SimpleNamespace(provider=\"custom\", base_url=\"https://open.bigmodel.cn/api/paas/v4\")\n+        from run_agent import AIAgent\n+        assert AIAgent._anthropic_preserve_dots(agent) is True\n+\n+    def test_normalize_preserves_m25_free_dot(self):\n+        from agent.anthropic_adapter import normalize_model_name\n+        assert normalize_model_name(\"minimax-m2.5-free\", preserve_dots=True) == \"minimax-m2.5-free\"\n+\n     def test_normalize_preserves_m27_dot(self):\n         from agent.anthropic_adapter import normalize_model_name\n         assert normalize_model_name(\"MiniMax-M2.7\", preserve_dots=True) == \"MiniMax-M2.7\""]},{"file":"tests/hermes_cli/test_model_normalize.py","hunks":["@@ -54,14 +54,19 @@ def test_anthropic_strips_vendor_prefix(self):\n \n # ── OpenCode Zen regression ────────────────────────────────────────────\n \n-class TestOpenCodeZenDotToHyphen:\n-    \"\"\"OpenCode Zen follows Anthropic convention (dots→hyphens).\"\"\"\n+class TestOpenCodeZenModelNormalization:\n+    \"\"\"OpenCode Zen preserves dots for most models, but Claude stays hyphenated.\"\"\"\n \n     @pytest.mark.parametrize(\"model,expected\", [\n         (\"claude-sonnet-4.6\", \"claude-sonnet-4-6\"),\n-        (\"glm-4.5\", \"glm-4-5\"),\n+        (\"opencode-zen/claude-opus-4.5\", \"claude-opus-4-5\"),\n+        (\"glm-4.5\", \"glm-4.5\"),\n+        (\"glm-5.1\", \"glm-5.1\"),\n+        (\"gpt-5.4\", \"gpt-5.4\"),\n+        (\"minimax-m2.5-free\", \"minimax-m2.5-free\"),\n+        (\"kimi-k2.5\", \"kimi-k2.5\"),\n     ])\n-    def test_zen_converts_dots(self, model, expected):\n+    def test_zen_normalizes_models(self, model, expected):\n         result = normalize_model_for_provider(model, \"opencode-zen\")\n         assert result == expected\n ","@@ -69,6 +74,10 @@ def test_zen_strips_vendor_prefix(self):\n         result = normalize_model_for_provider(\"opencode-zen/claude-sonnet-4.6\", \"opencode-zen\")\n         assert result == \"claude-sonnet-4-6\"\n \n+    def test_zen_strips_vendor_prefix_for_non_claude(self):\n+        result = normalize_model_for_provider(\"opencode-zen/glm-5.1\", \"opencode-zen\")\n+        assert result == \"glm-5.1\"\n+\n \n # ── Copilot dot preservation (regression) ──────────────────────────────\n \n"]}]},{"kind":"pr","number":60967,"title":"fix(auxiliary): respect explicit auxiliary.vision.model over _PROVIDER_VISION_MODELS mapping","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#60381","number":60381},{"kind":"pr","value":"#60381","number":60381}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}