{"repo":"NousResearch/hermes-agent","public_id":"36196","display_label":"Minimax M3 model support and aux model alignment","summary":"These items concern adding support for the MinimaxM3 model and fixing the default auxiliary model to align with the M3 frontier for minimax and minimax-cn providers. Multiple fix attempts address the same alignment issue.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":39024,"merged_at":"2026-06-04T12:53:36+00:00"},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-06-04T12:53:36+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":36196,"pr_number":39024,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-06-04T12:53:36+00:00"}],"members":[{"kind":"issue","number":36196,"title":"[Feature]: MinimaxM3 model support","url":null,"state":"closed"},{"kind":"pr","number":37664,"title":"fix(minimax): align default_aux_model with M3 frontier on minimax + minimax-cn","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"3d1d0a49f","number":0},{"kind":"commit","value":"b531b5d12","number":0},{"kind":"commit","value":"fa8e2f935","number":0},{"kind":"pr","value":"#39024","number":39024}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":39024,"title":"fix(minimax): align default_aux_model with M3 frontier (salvage #37664)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/auxiliary_client.py","plugins/model-providers/minimax/__init__.py","tests/agent/test_minimax_provider.py","tests/hermes_cli/test_api_key_providers.py","tests/plugins/model_providers/test_minimax_profile.py"],"fix_hunks":[{"file":"agent/auxiliary_client.py","hunks":["@@ -265,9 +265,6 @@ def _get_aux_model_for_provider(provider_id: str) -> str:\n     \"stepfun\": \"step-3.5-flash\",\n     \"kimi-coding-cn\": \"kimi-k2-turbo-preview\",\n     \"gmi\": \"google/gemini-3.1-flash-lite-preview\",\n-    \"minimax\": \"MiniMax-M2.7\",\n-    \"minimax-oauth\": \"MiniMax-M2.7-highspeed\",\n-    \"minimax-cn\": \"MiniMax-M2.7\",\n     \"anthropic\": \"claude-haiku-4-5-20251001\",\n     \"opencode-zen\": \"gemini-3-flash\",\n     \"opencode-go\": \"glm-5\","]},{"file":"plugins/model-providers/minimax/__init__.py","hunks":["@@ -14,7 +14,7 @@\n     env_vars=(\"MINIMAX_API_KEY\",),\n     base_url=\"https://api.minimax.io/anthropic\",\n     auth_type=\"api_key\",\n-    default_aux_model=\"MiniMax-M2.7\",\n+    default_aux_model=\"MiniMax-M3\",\n )\n \n minimax_cn = ProviderProfile(","@@ -24,7 +24,7 @@\n     env_vars=(\"MINIMAX_CN_API_KEY\",),\n     base_url=\"https://api.minimaxi.com/anthropic\",\n     auth_type=\"api_key\",\n-    default_aux_model=\"MiniMax-M2.7\",\n+    default_aux_model=\"MiniMax-M3\",\n )\n \n minimax_oauth = ProviderProfile(","@@ -37,7 +37,7 @@\n     env_vars=(),  # OAuth — tokens in auth.json, not env\n     base_url=\"https://api.minimax.io/anthropic\",\n     auth_type=\"oauth_external\",\n-    default_aux_model=\"MiniMax-M2.7-highspeed\",\n+    default_aux_model=\"MiniMax-M2.7\",\n )\n \n register_provider(minimax)"]},{"file":"tests/agent/test_minimax_provider.py","hunks":["@@ -141,14 +141,37 @@ def test_thinking_still_works_for_claude(self):\n \n \n class TestMinimaxAuxModel:\n-    \"\"\"Verify auxiliary model is standard (not highspeed) — now reads from profiles.\"\"\"\n+    \"\"\"Verify auxiliary model is the current frontier standard (not highspeed).\n+\n+    As of M3's release (2026-06-01) the minimax / minimax-cn provider\n+    profiles advertise ``MiniMax-M3`` as their ``default_aux_model`` (the\n+    same model users see in ``_PROVIDER_MODELS[\"minimax\"]`` and in the\n+    user-facing ``model.default`` for a Token-Plan install).  The OAuth\n+    / Coding Plan path sticks with M2.7 because M3 is not on that\n+    tier — see ``test_minimax_profile.py`` for the per-provider split.\n+\n+    The historical concern this class guards is the #4082 / #6082\n+    regression: the highspeed variant costs 2x with no model-quality\n+    benefit, so we still assert that no aux choice contains the substring\n+    ``\"highspeed\"``.\n+    \"\"\"\n \n     def test_minimax_aux_is_standard(self):\n+        # Import model_tools to trigger plugin discovery so the\n+        # ProviderProfile objects are registered in the providers\n+        # registry before _get_aux_model_for_provider() is called.\n+        # Without this, profile-based resolution can be order-dependent\n+        # or fail outright in isolation (the minimax-* entries are\n+        # no longer in _API_KEY_PROVIDER_AUX_MODELS_FALLBACK after the\n+        # minimax-M3 default-aux-model cleanup, so the profile is\n+        # the only path to a non-empty aux value).\n+        import model_tools  # noqa: F401\n         from agent.auxiliary_client import _get_aux_model_for_provider\n-        assert _get_aux_model_for_provider(\"minimax\") == \"MiniMax-M2.7\"\n-        assert _get_aux_model_for_provider(\"minimax-cn\") == \"MiniMax-M2.7\"\n+        assert _get_aux_model_for_provider(\"minimax\") == \"MiniMax-M3\"\n+        assert _get_aux_model_for_provider(\"minimax-cn\") == \"MiniMax-M3\"\n \n     def test_minimax_aux_not_highspeed(self):\n+        import model_tools  # noqa: F401\n         from agent.auxiliary_client import _get_aux_model_for_provider\n         assert \"highspeed\" not in _get_aux_model_for_provider(\"minimax\")\n         assert \"highspeed\" not in _get_aux_model_for_provider(\"minimax-cn\")"]},{"file":"tests/hermes_cli/test_api_key_providers.py","hunks":["@@ -1279,6 +1279,20 @@ def test_minimax_oauth_has_models(self):\n         assert len(models) >= 1\n \n     def test_minimax_oauth_aux_model_registered(self):\n-        from agent.auxiliary_client import _API_KEY_PROVIDER_AUX_MODELS\n-        assert \"minimax-oauth\" in _API_KEY_PROVIDER_AUX_MODELS\n-        assert _API_KEY_PROVIDER_AUX_MODELS[\"minimax-oauth\"]  # non-empty\n+        # Aux model for the minimax-oauth provider now lives on the\n+        # ProviderProfile (plugins/model-providers/minimax/__init__.py),\n+        # not the legacy _API_KEY_PROVIDER_AUX_MODELS dict in\n+        # agent/auxiliary_client.py. The profile layer is the source\n+        # of truth; _get_aux_model_for_provider() reads from it first\n+        # and only falls back to the dict when no profile is registered.\n+        import model_tools  # noqa: F401  -- triggers plugin discovery\n+        import providers\n+\n+        profile = providers.get_provider_profile(\"minimax-oauth\")\n+        assert profile is not None, \"minimax-oauth provider profile must be registered\"\n+        assert profile.default_aux_model, (\n+            \"minimax-oauth profile must advertise a non-empty default_aux_model \"\n+            \"so the auxiliary client (compression / vision / session-search) \"\n+            \"doesn't fire the 'No auxiliary LLM provider configured' warning \"\n+            \"for every minimax-oauth session.\"\n+        )"]},{"file":"tests/plugins/model_providers/test_minimax_profile.py","hunks":["@@ -0,0 +1,120 @@\n+\"\"\"Unit tests for the MiniMax provider profile.\n+\n+Three MiniMax provider profiles (`minimax` direct API, `minimax-cn` China direct\n+API, `minimax-oauth` browser OAuth) all advertise a `default_aux_model` on\n+their `ProviderProfile`. The previous M2.7 / M2.7-highspeed values were\n+stale relative to the current frontier model (M3, released 2026-06-01) and\n+inconsistent with the `_PROVIDER_MODELS[\"minimax\"]` catalog top entry in\n+`hermes_cli/models.py`.\n+\n+This file pins the new defaults so the choice is reviewable and any future\n+revert shows up in a failing test rather than silent behavior drift.\n+\n+Refs:\n+  - Issue #36196: M3 support request\n+  - PR #36205 (closed unmerged): Csrayz's M3 + 1M context work\n+  - PR #36212 (open): adds M3 to `_PROVIDER_MODELS[\"minimax\"]` catalog\n+  - PR #6082: M2.7-highspeed → M2.7 for aux model (half-price fix)\n+  - Commit 773a0faca: same profile-layer fix pattern for `deepseek`\n+\"\"\"\n+\n+from __future__ import annotations\n+\n+import pytest\n+\n+\n[redacted:email](params=[\"minimax\", \"minimax-cn\", \"minimax-oauth\"])\n+def minimax_profile(request):\n+    \"\"\"Resolve each registered MiniMax profile.\n+\n+    Going through ``providers.get_provider_profile`` keeps the test honest —\n+    if someone later replaces the registered class with a plain\n+    ``ProviderProfile``, every assertion below collapses.\n+    \"\"\"\n+    import model_tools  # noqa: F401  -- triggers plugin discovery\n+    import providers\n+\n+    profile = providers.get_provider_profile(request.param)\n+    assert profile is not None, f\"{request.param} provider profile must be registered\"\n+    return profile, request.param\n+\n+\n+class TestMinimaxAuxModelM3:\n+    \"\"\"MiniMax profile aux model is the new frontier M3, not the stale M2.7.\n+\n+    The catalog top entry is ``MiniMax-M3`` in\n+    ``hermes_cli.models._PROVIDER_MODELS['minimax']`` and the\n+    user-facing ``model.default`` for a Token-Plan install is M3,\n+    so pinning the aux default to the same model keeps the runtime\n+    consistent (same auth, same billing pool, same rate limits, no\n+    surprise 2x-cost highspeed variant). M3 was released 2026-06-01\n+    — picking it as the aux default matches the forward-looking\n+    catalog order rather than the pre-M3 era.\n+    \"\"\"\n+\n+    @pytest.mark.parametrize(\n+        \"provider_id,expected\",\n+        [\n+            (\"minimax\", \"MiniMax-M3\"),\n+            (\"minimax-cn\", \"MiniMax-M3\"),\n+            # minimax-oauth sticks with M2.7: the OAuth / Coding Plan\n+            # tier historically used -highspeed (PR #6082 collapsed that\n+            # to plain M2.7 to avoid the 2x TPS surcharge). M3 is not on\n+            # the OAuth/Coding Plan tier per platform docs as of this PR,\n+            # so the safe choice is the cheapest generally-available\n+            # M2.7 — matching PR #6082's intent.\n+            (\"minimax-oauth\", \"MiniMax-M2.7\"),\n+        ],\n+    )\n+    def test_profile_advertises_expected_aux_model(\n+        self, provider_id, expected\n+    ):\n+        import model_tools  # noqa: F401\n+        import providers\n+\n+        profile = providers.get_provider_profile(provider_id)\n+        assert profile is not None\n+        assert profile.default_aux_model == expected, (\n+            f\"{provider_id} default_aux_model drifted to \"\n+            f\"{profile.default_aux_model!r}, expected {expected!r}\"\n+        )\n+\n+    def test_consumer_api_returns_non_empty_for_each_provider(self, minimax_profile):\n+        from agent.auxiliary_client import _get_aux_model_for_provider\n+\n+        profile, provider_id = minimax_profile\n+        resolved = _get_aux_model_for_provider(provider_id)\n+        assert resolved != \"\", (\n+            f\"_get_aux_model_for_provider({provider_id!r}) returned empty — \"\n+            \"the 'No auxiliary LLM provider configured' warning will fire on \"\n+            f\"every {provider_id} session even though the profile advertises \"\n+            f\"default_aux_model={profile.default_aux_model!r}\"\n+        )\n+        assert resolved == profile.default_aux_model, (\n+            f\"_get_aux_model_for_provider({provider_id!r}) returned \"\n+            f\"{resolved!r} but profile advertises {profile.default_aux_model!r} \"\n+            \"— the consumer API and the profile have drifted out of sync\"\n+        )\n+\n+\n+class TestMinimaxAuxModelNotHighspeed:\n+    \"\"\"Regression guard against re-introducing the M2.7-highspeed aux default.\n+\n+    PR #6082 collapsed the highspeed aux choice to plain M2.7 because the\n+    highspeed variant costs 2x with no real benefit for compression / vision /\n+    session-search aux tasks. None of the three MiniMax profiles should\n+    silently re-introduce that 2x-cost path.\n+    \"\"\"\n+\n+    @pytest.mark.parametrize(\"provider_id\", [\"minimax\", \"minimax-cn\", \"minimax-oauth\"])\n+    def test_default_aux_model_is_not_highspeed(self, provider_id):\n+        import model_tools  # noqa: F401\n+        import providers\n+\n+        profile = providers.get_provider_profile(provider_id)\n+        assert profile is not None\n+        assert \"highspeed\" not in profile.default_aux_model.lower(), (\n+            f\"{provider_id} default_aux_model={profile.default_aux_model!r} \"\n+            \"is a -highspeed variant — that costs 2x for the same model and \"\n+            \"broke #4082 the first time. Revert to plain M2.7 or M3.\"\n+        )\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}