{"repo":"NousResearch/hermes-agent","public_id":"34786","display_label":"GLM-5 and Kimi K2 reasoning parameter handling","summary":"These items add GLM-5.x reasoning support and controls to the OpenCodeGo profile, and address how reasoning parameters like 'thinking' and 'reasoning_effort' are sent or handled, including a fallback mechanism and a fix to avoid sending conflicting parameters for Kimi K2.","subsystem":"agent","counts":{"members":5,"open_issues":2,"open_issues_distinct":2,"open_prs":0,"closed_issues":0,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":58884},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-07-05T20:48:01+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.33599999999999997},"fix_edges":[{"issue_number":49279,"pr_number":58884,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false}],"members":[{"kind":"issue","number":34786,"title":"feat: automatic in-provider reasoning fallback when API rejects thinking/reasoning_effort parameters","url":null,"state":"open"},{"kind":"issue","number":49279,"title":"feat: Add GLM-5.x reasoning support to OpenCodeGo profile","url":null,"state":"open"},{"kind":"pr","number":34208,"title":"fix(opencode-go): don't send 'thinking' and 'reasoning_effort' together for Kimi K2","url":null,"state":"closed","close_reason":{"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":50694,"title":"feat(opencode-go): support GLM-5 reasoning controls (#49279)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#58884","number":58884}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":58884,"title":"feat(providers): GLM-5.2 native reasoning_effort controls (salvage #51108)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["plugins/model-providers/opencode-zen/__init__.py","plugins/model-providers/zai/__init__.py","tests/plugins/model_providers/test_opencode_go_profile.py","tests/plugins/model_providers/test_zai_profile.py"],"fix_hunks":[{"file":"plugins/model-providers/opencode-zen/__init__.py","hunks":["@@ -31,6 +31,12 @@ def _is_deepseek_thinking_model(model: str | None) -> bool:\n     return m == \"deepseek-reasoner\"\n \n \n+def _is_glm_5_2_model(model: str | None) -> bool:\n+    \"\"\"Detect GLM-5.2 across alias spellings (glm-5.2 / glm-5-2 / glm-5p2).\"\"\"\n+    m = _flat_model_name(model)\n+    return any(token in m for token in (\"glm-5.2\", \"glm-5-2\", \"glm-5p2\"))\n+\n+\n class OpenCodeGoProfile(ProviderProfile):\n     \"\"\"OpenCode Go - model-specific reasoning controls.\"\"\"\n ","@@ -55,6 +61,21 @@ def build_api_kwargs_extras(\n         extra_body: dict[str, Any] = {}\n         top_level: dict[str, Any] = {}\n \n+        if _is_glm_5_2_model(model):\n+            # GLM-5.2 on OpenCode Go uses its native OpenAI-compatible\n+            # reasoning_effort knob, which has exactly two enabled levels:\n+            # high and max. Map Hermes' richer scale onto those; leave the\n+            # server default alone when reasoning is disabled or unset.\n+            if not isinstance(reasoning_config, dict):\n+                return extra_body, top_level\n+            if reasoning_config.get(\"enabled\") is False:\n+                return extra_body, top_level\n+            effort = (reasoning_config.get(\"effort\") or \"\").strip().lower()\n+            if not effort or effort == \"none\":\n+                return extra_body, top_level\n+            top_level[\"reasoning_effort\"] = \"max\" if effort in {\"xhigh\", \"max\"} else \"high\"\n+            return extra_body, top_level\n+\n         if _is_kimi_k2_model(model):\n             # Kimi K2 on OpenCode Go uses Moonshot's native wire shape:\n             # extra_body.thinking (binary toggle) + top-level reasoning_effort"]},{"file":"plugins/model-providers/zai/__init__.py","hunks":["@@ -16,6 +16,12 @@\n is omitted so the server default applies, matching prior behavior.  GLM\n models before 4.5 (e.g. ``glm-4-9b``) don't accept ``thinking`` and are left\n untouched.\n+\n+GLM-5.2 additionally exposes a native ``reasoning_effort`` knob with exactly\n+two enabled levels — ``high`` and ``max`` — on the OpenAI-compatible endpoint\n+(per Z.AI / BigModel docs).  Hermes' richer effort scale is collapsed onto\n+those two so the user's effort preference actually reaches the model instead\n+of being silently dropped.\n \"\"\"\n \n from __future__ import annotations","@@ -40,8 +46,44 @@ def _model_supports_thinking(model: str | None) -> bool:\n     return (major, minor) >= (4, 5)\n \n \n+def _is_glm_5_2(model: str | None) -> bool:\n+    \"\"\"Detect GLM-5.2 across the alias spellings providers use.\n+\n+    Covers the canonical ``glm-5.2`` plus the ``glm-5-2`` / ``glm-5p2``\n+    variants seen on relays (Fireworks ``glm-5p2``, etc.) and any\n+    vendor-prefixed form (``z-ai/glm-5.2``, ``zai-org-glm-5-2``).\n+    \"\"\"\n+    m = (model or \"\").strip().lower()\n+    if not m:\n+        return False\n+    return any(token in m for token in (\"glm-5.2\", \"glm-5-2\", \"glm-5p2\"))\n+\n+\n+def _glm_5_2_reasoning_effort(reasoning_config: dict | None) -> str | None:\n+    \"\"\"Map Hermes reasoning effort onto GLM-5.2's native ``high``/``max``.\n+\n+    GLM-5.2 only supports two enabled effort levels. ``xhigh``/``max``\n+    request the top tier; everything else that is enabled requests ``high``\n+    (its minimum thinking level). When reasoning is explicitly disabled, or\n+    no effort preference is supplied, the server default is left untouched.\n+    \"\"\"\n+    if not isinstance(reasoning_config, dict):\n+        return None\n+    if reasoning_config.get(\"enabled\") is False:\n+        return None\n+\n+    effort = (reasoning_config.get(\"effort\") or \"\").strip().lower()\n+    if not effort or effort == \"none\":\n+        return None\n+\n+    if effort in {\"xhigh\", \"max\"}:\n+        return \"max\"\n+    # low / medium / minimal / high all clamp to GLM-5.2's minimum: high.\n+    return \"high\"\n+\n+\n class ZaiProfile(ProviderProfile):\n-    \"\"\"Z.AI / GLM — extra_body.thinking enabled/disabled.\"\"\"\n+    \"\"\"Z.AI / GLM — extra_body.thinking on/off + GLM-5.2 reasoning_effort.\"\"\"\n \n     def build_api_kwargs_extras(\n         self, *, reasoning_config: dict | None = None, model: str | None = None, **context","@@ -49,7 +91,7 @@ def build_api_kwargs_extras(\n         extra_body: dict[str, Any] = {}\n         top_level: dict[str, Any] = {}\n \n-        if not _model_supports_thinking(model):\n+        if not _model_supports_thinking(model) and not _is_glm_5_2(model):\n             return extra_body, top_level\n \n         # Only emit when the user expressed a preference; omitting the field","@@ -58,6 +100,11 @@ def build_api_kwargs_extras(\n             enabled = reasoning_config.get(\"enabled\") is not False\n             extra_body[\"thinking\"] = {\"type\": \"enabled\" if enabled else \"disabled\"}\n \n+        if _is_glm_5_2(model):\n+            effort = _glm_5_2_reasoning_effort(reasoning_config)\n+            if effort is not None:\n+                top_level[\"reasoning_effort\"] = effort\n+\n         return extra_body, top_level\n \n "]},{"file":"tests/plugins/model_providers/test_opencode_go_profile.py","hunks":["@@ -122,13 +122,68 @@ def test_xhigh_and_max_normalize_to_max(self, opencode_go_profile):\n             assert top_level == {\"reasoning_effort\": \"max\"}\n \n \n+class TestOpenCodeGoGLM52Reasoning:\n+    \"\"\"GLM-5.2 uses its native high/max reasoning_effort knob on OpenCode Go.\"\"\"\n+\n+    def test_high_maps_to_high(self, opencode_go_profile):\n+        extra_body, top_level = opencode_go_profile.build_api_kwargs_extras(\n+            reasoning_config={\"enabled\": True, \"effort\": \"high\"},\n+            model=\"glm-5.2\",\n+        )\n+        assert extra_body == {}\n+        assert top_level == {\"reasoning_effort\": \"high\"}\n+\n+    def test_low_and_medium_clamp_up_to_high(self, opencode_go_profile):\n+        for effort in (\"low\", \"medium\", \"minimal\"):\n+            extra_body, top_level = opencode_go_profile.build_api_kwargs_extras(\n+                reasoning_config={\"enabled\": True, \"effort\": effort},\n+                model=\"glm-5.2\",\n+            )\n+            assert extra_body == {}\n+            assert top_level == {\"reasoning_effort\": \"high\"}\n+\n+    def test_xhigh_and_max_map_to_max(self, opencode_go_profile):\n+        for effort in (\"xhigh\", \"max\"):\n+            extra_body, top_level = opencode_go_profile.build_api_kwargs_extras(\n+                reasoning_config={\"enabled\": True, \"effort\": effort},\n+                model=\"z-ai/glm-5.2\",\n+            )\n+            assert extra_body == {}\n+            assert top_level == {\"reasoning_effort\": \"max\"}\n+\n+    def test_disabled_leaves_server_default(self, opencode_go_profile):\n+        extra_body, top_level = opencode_go_profile.build_api_kwargs_extras(\n+            reasoning_config={\"enabled\": False, \"effort\": \"high\"},\n+            model=\"glm-5.2\",\n+        )\n+        assert extra_body == {}\n+        assert top_level == {}\n+\n+    def test_no_config_leaves_server_default(self, opencode_go_profile):\n+        extra_body, top_level = opencode_go_profile.build_api_kwargs_extras(\n+            reasoning_config=None,\n+            model=\"glm-5.2\",\n+        )\n+        assert extra_body == {}\n+        assert top_level == {}\n+\n+    @pytest.mark.parametrize(\"model\", [\"glm-5-2\", \"glm-5p2\"])\n+    def test_alias_spellings_recognized(self, opencode_go_profile, model):\n+        extra_body, top_level = opencode_go_profile.build_api_kwargs_extras(\n+            reasoning_config={\"enabled\": True, \"effort\": \"max\"},\n+            model=model,\n+        )\n+        assert top_level == {\"reasoning_effort\": \"max\"}\n+\n+\n class TestOpenCodeGoModelGating:\n-    \"\"\"Other OpenCode Go models must not receive Kimi/DeepSeek controls.\"\"\"\n+    \"\"\"Other OpenCode Go models must not receive Kimi/DeepSeek/GLM controls.\"\"\"\n \n     @pytest.mark.parametrize(\n         \"model\",\n         [\n             \"glm-5.1\",\n+            \"glm-5\",\n             \"qwen3.6-plus\",\n             \"minimax-m2.7\",\n             \"deepseek-v3.1\","]},{"file":"tests/plugins/model_providers/test_zai_profile.py","hunks":["@@ -1,4 +1,4 @@\n-\"\"\"Unit tests for the Z.AI / GLM provider profile's thinking-mode wiring.\n+\"\"\"Unit tests for the Z.AI / GLM provider profile's reasoning wiring.\n \n Z.AI's GLM-4.5-and-later chat models default to thinking-mode ON when the\n request omits ``thinking``.  Before the profile emitted the parameter,","@@ -6,6 +6,10 @@\n Z.AI route — users who turned thinking off kept burning thinking tokens on\n every turn (the desktop \"thinking reverts to medium\" report).\n \n+GLM-5.2 additionally exposes a native ``reasoning_effort`` knob with two\n+enabled levels (high / max) on the OpenAI-compatible ``/api/paas/v4``\n+endpoint; the Hermes effort scale is collapsed onto those.\n+\n These tests pin the profile's wire-shape contract so Z.AI requests stay\n correctly shaped without going live.\n \"\"\"","@@ -61,12 +65,102 @@ def test_explicitly_disabled_sends_disabled_marker(self, zai_profile):\n         assert top_level == {}\n \n     def test_no_effort_levels_leak_to_top_level(self, zai_profile):\n-        \"\"\"GLM has no effort knob — never emit ``reasoning_effort``.\"\"\"\n+        \"\"\"Non-5.2 GLM models have no effort knob — never emit\n+        ``reasoning_effort`` for them (GLM-5.2 is the exception, below).\"\"\"\n         for effort in (\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"):\n-            _, top_level = zai_profile.build_api_kwargs_extras(\n-                reasoning_config={\"enabled\": True, \"effort\": effort}, model=\"glm-5.2\"\n-            )\n-            assert top_level == {}\n+            for model in (\"glm-5\", \"glm-5.1\", \"glm-4.6\"):\n+                _, top_level = zai_profile.build_api_kwargs_extras(\n+                    reasoning_config={\"enabled\": True, \"effort\": effort}, model=model\n+                )\n+                assert top_level == {}\n+\n+\n+class TestZaiGLM52ReasoningEffort:\n+    \"\"\"GLM-5.2's native ``reasoning_effort`` knob (two enabled levels).\"\"\"\n+\n+    def test_high_maps_to_high(self, zai_profile):\n+        extra_body, top_level = zai_profile.build_api_kwargs_extras(\n+            reasoning_config={\"enabled\": True, \"effort\": \"high\"},\n+            model=\"glm-5.2\",\n+        )\n+        assert extra_body == {\"thinking\": {\"type\": \"enabled\"}}\n+        assert top_level == {\"reasoning_effort\": \"high\"}\n+\n+    @pytest.mark.parametrize(\"effort\", [\"low\", \"medium\", \"minimal\"])\n+    def test_lower_efforts_clamp_up_to_high(self, zai_profile, effort):\n+        \"\"\"GLM-5.2's minimum thinking level is high — lower Hermes levels\n+        clamp onto it.\"\"\"\n+        extra_body, top_level = zai_profile.build_api_kwargs_extras(\n+            reasoning_config={\"enabled\": True, \"effort\": effort},\n+            model=\"glm-5.2\",\n+        )\n+        assert extra_body == {\"thinking\": {\"type\": \"enabled\"}}\n+        assert top_level == {\"reasoning_effort\": \"high\"}\n+\n+    @pytest.mark.parametrize(\"effort\", [\"xhigh\", \"max\"])\n+    def test_strong_efforts_map_to_max(self, zai_profile, effort):\n+        extra_body, top_level = zai_profile.build_api_kwargs_extras(\n+            reasoning_config={\"enabled\": True, \"effort\": effort},\n+            model=\"glm-5.2\",\n+        )\n+        assert extra_body == {\"thinking\": {\"type\": \"enabled\"}}\n+        assert top_level == {\"reasoning_effort\": \"max\"}\n+\n+    def test_disabled_sends_no_effort(self, zai_profile):\n+        \"\"\"Disabled reasoning still sends the thinking-off marker but never\n+        an effort level.\"\"\"\n+        extra_body, top_level = zai_profile.build_api_kwargs_extras(\n+            reasoning_config={\"enabled\": False, \"effort\": \"high\"},\n+            model=\"glm-5.2\",\n+        )\n+        assert extra_body == {\"thinking\": {\"type\": \"disabled\"}}\n+        assert top_level == {}\n+\n+    def test_no_config_leaves_server_default(self, zai_profile):\n+        extra_body, top_level = zai_profile.build_api_kwargs_extras(\n+            reasoning_config=None,\n+            model=\"glm-5.2\",\n+        )\n+        assert extra_body == {}\n+        assert top_level == {}\n+\n+    def test_no_effort_sends_no_effort_level(self, zai_profile):\n+        \"\"\"Enabled but no effort preference → thinking marker only; the\n+        server picks its default effort.\"\"\"\n+        extra_body, top_level = zai_profile.build_api_kwargs_extras(\n+            reasoning_config={\"enabled\": True},\n+            model=\"glm-5.2\",\n+        )\n+        assert extra_body == {\"thinking\": {\"type\": \"enabled\"}}\n+        assert top_level == {}\n+\n+    @pytest.mark.parametrize(\n+        \"model\",\n+        [\n+            \"z-ai/glm-5.2\",\n+            \"glm-5-2\",\n+            \"glm-5p2\",\n+            \"accounts/fireworks/models/glm-5p2\",\n+            \"zai-org-glm-5-2\",\n+        ],\n+    )\n+    def test_alias_spellings_recognized(self, zai_profile, model):\n+        _, top_level = zai_profile.build_api_kwargs_extras(\n+            reasoning_config={\"enabled\": True, \"effort\": \"max\"},\n+            model=model,\n+        )\n+        assert top_level == {\"reasoning_effort\": \"max\"}\n+\n+    @pytest.mark.parametrize(\n+        \"model\",\n+        [\"glm-5.1\", \"glm-5\", \"glm-4.7\", \"glm-4-9b\", \"\", None],\n+    )\n+    def test_non_glm_5_2_models_get_no_effort(self, zai_profile, model):\n+        _, top_level = zai_profile.build_api_kwargs_extras(\n+            reasoning_config={\"enabled\": True, \"effort\": \"high\"},\n+            model=model,\n+        )\n+        assert top_level == {}\n \n \n class TestZaiModelGating:","@@ -110,7 +204,7 @@ def test_non_thinking_models_emit_nothing(self, zai_profile, model):\n \n \n class TestZaiFullKwargsIntegration:\n-    \"\"\"End-to-end: the transport's full kwargs carry the thinking marker.\"\"\"\n+    \"\"\"End-to-end: the transport's full kwargs carry the reasoning wiring.\"\"\"\n \n     def test_disabled_reaches_the_wire(self, zai_profile):\n         from agent.transports.chat_completions import ChatCompletionsTransport","@@ -139,3 +233,18 @@ def test_no_preference_keeps_wire_clean(self, zai_profile):\n             provider_name=\"zai\",\n         )\n         assert \"thinking\" not in kwargs.get(\"extra_body\", {})\n+\n+    def test_glm_5_2_effort_reaches_top_level(self, zai_profile):\n+        from agent.transports.chat_completions import ChatCompletionsTransport\n+\n+        kwargs = ChatCompletionsTransport().build_kwargs(\n+            model=\"glm-5.2\",\n+            messages=[{\"role\": \"user\", \"content\": \"ping\"}],\n+            tools=None,\n+            provider_profile=zai_profile,\n+            reasoning_config={\"enabled\": True, \"effort\": \"max\"},\n+            base_url=\"https://api.z.ai/api/paas/v4\",\n+            provider_name=\"zai\",\n+        )\n+        assert kwargs[\"reasoning_effort\"] == \"max\"\n+        assert kwargs[\"extra_body\"][\"thinking\"] == {\"type\": \"enabled\"}\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}