diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 4c88772327f2..ca3b83c98905 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -265,9 +265,6 @@ def _get_aux_model_for_provider(provider_id: str) -> str: "stepfun": "step-3.5-flash", "kimi-coding-cn": "kimi-k2-turbo-preview", "gmi": "google/gemini-3.1-flash-lite-preview", - "minimax": "MiniMax-M2.7", - "minimax-oauth": "MiniMax-M2.7-highspeed", - "minimax-cn": "MiniMax-M2.7", "anthropic": "claude-haiku-4-5-20251001", "opencode-zen": "gemini-3-flash", "opencode-go": "glm-5", diff --git a/plugins/model-providers/minimax/__init__.py b/plugins/model-providers/minimax/__init__.py index f29eb1aa07e2..6d77536acee0 100644 --- a/plugins/model-providers/minimax/__init__.py +++ b/plugins/model-providers/minimax/__init__.py @@ -14,7 +14,7 @@ env_vars=("MINIMAX_API_KEY",), base_url="https://api.minimax.io/anthropic", auth_type="api_key", - default_aux_model="MiniMax-M2.7", + default_aux_model="MiniMax-M3", ) minimax_cn = ProviderProfile( @@ -24,7 +24,7 @@ env_vars=("MINIMAX_CN_API_KEY",), base_url="https://api.minimaxi.com/anthropic", auth_type="api_key", - default_aux_model="MiniMax-M2.7", + default_aux_model="MiniMax-M3", ) minimax_oauth = ProviderProfile( @@ -37,7 +37,7 @@ env_vars=(), # OAuth — tokens in auth.json, not env base_url="https://api.minimax.io/anthropic", auth_type="oauth_external", - default_aux_model="MiniMax-M2.7-highspeed", + default_aux_model="MiniMax-M2.7", ) register_provider(minimax) diff --git a/scripts/release.py b/scripts/release.py index f5ae67e88478..758f842109a6 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -373,6 +373,7 @@ "harish.kukreja@gmail.com": "counterposition", "nidhi2894@gmail.com": "nidhi-singh02", "35294173+Fearvox@users.noreply.github.com": "Fearvox", + "fearvox1015@gmail.com": "Fearvox", "hypnus.yuan@gmail.com": "Hypnus-Yuan", "15558128926@qq.com": "xsfX20", "binhnt.ht.92@gmail.com": "binhnt92", diff --git a/tests/agent/test_minimax_provider.py b/tests/agent/test_minimax_provider.py index 73e8034ddd15..2dd1510786e7 100644 --- a/tests/agent/test_minimax_provider.py +++ b/tests/agent/test_minimax_provider.py @@ -141,14 +141,37 @@ def test_thinking_still_works_for_claude(self): class TestMinimaxAuxModel: - """Verify auxiliary model is standard (not highspeed) — now reads from profiles.""" + """Verify auxiliary model is the current frontier standard (not highspeed). + + As of M3's release (2026-06-01) the minimax / minimax-cn provider + profiles advertise ``MiniMax-M3`` as their ``default_aux_model`` (the + same model users see in ``_PROVIDER_MODELS["minimax"]`` and in the + user-facing ``model.default`` for a Token-Plan install). The OAuth + / Coding Plan path sticks with M2.7 because M3 is not on that + tier — see ``test_minimax_profile.py`` for the per-provider split. + + The historical concern this class guards is the #4082 / #6082 + regression: the highspeed variant costs 2x with no model-quality + benefit, so we still assert that no aux choice contains the substring + ``"highspeed"``. + """ def test_minimax_aux_is_standard(self): + # Import model_tools to trigger plugin discovery so the + # ProviderProfile objects are registered in the providers + # registry before _get_aux_model_for_provider() is called. + # Without this, profile-based resolution can be order-dependent + # or fail outright in isolation (the minimax-* entries are + # no longer in _API_KEY_PROVIDER_AUX_MODELS_FALLBACK after the + # minimax-M3 default-aux-model cleanup, so the profile is + # the only path to a non-empty aux value). + import model_tools # noqa: F401 from agent.auxiliary_client import _get_aux_model_for_provider - assert _get_aux_model_for_provider("minimax") == "MiniMax-M2.7" - assert _get_aux_model_for_provider("minimax-cn") == "MiniMax-M2.7" + assert _get_aux_model_for_provider("minimax") == "MiniMax-M3" + assert _get_aux_model_for_provider("minimax-cn") == "MiniMax-M3" def test_minimax_aux_not_highspeed(self): + import model_tools # noqa: F401 from agent.auxiliary_client import _get_aux_model_for_provider assert "highspeed" not in _get_aux_model_for_provider("minimax") assert "highspeed" not in _get_aux_model_for_provider("minimax-cn") diff --git a/tests/hermes_cli/test_api_key_providers.py b/tests/hermes_cli/test_api_key_providers.py index af576ed29952..6dacd5e353b5 100644 --- a/tests/hermes_cli/test_api_key_providers.py +++ b/tests/hermes_cli/test_api_key_providers.py @@ -1279,6 +1279,20 @@ def test_minimax_oauth_has_models(self): assert len(models) >= 1 def test_minimax_oauth_aux_model_registered(self): - from agent.auxiliary_client import _API_KEY_PROVIDER_AUX_MODELS - assert "minimax-oauth" in _API_KEY_PROVIDER_AUX_MODELS - assert _API_KEY_PROVIDER_AUX_MODELS["minimax-oauth"] # non-empty + # Aux model for the minimax-oauth provider now lives on the + # ProviderProfile (plugins/model-providers/minimax/__init__.py), + # not the legacy _API_KEY_PROVIDER_AUX_MODELS dict in + # agent/auxiliary_client.py. The profile layer is the source + # of truth; _get_aux_model_for_provider() reads from it first + # and only falls back to the dict when no profile is registered. + import model_tools # noqa: F401 -- triggers plugin discovery + import providers + + profile = providers.get_provider_profile("minimax-oauth") + assert profile is not None, "minimax-oauth provider profile must be registered" + assert profile.default_aux_model, ( + "minimax-oauth profile must advertise a non-empty default_aux_model " + "so the auxiliary client (compression / vision / session-search) " + "doesn't fire the 'No auxiliary LLM provider configured' warning " + "for every minimax-oauth session." + ) diff --git a/tests/plugins/model_providers/test_minimax_profile.py b/tests/plugins/model_providers/test_minimax_profile.py new file mode 100644 index 000000000000..e66b0dea8baf --- /dev/null +++ b/tests/plugins/model_providers/test_minimax_profile.py @@ -0,0 +1,120 @@ +"""Unit tests for the MiniMax provider profile. + +Three MiniMax provider profiles (`minimax` direct API, `minimax-cn` China direct +API, `minimax-oauth` browser OAuth) all advertise a `default_aux_model` on +their `ProviderProfile`. The previous M2.7 / M2.7-highspeed values were +stale relative to the current frontier model (M3, released 2026-06-01) and +inconsistent with the `_PROVIDER_MODELS["minimax"]` catalog top entry in +`hermes_cli/models.py`. + +This file pins the new defaults so the choice is reviewable and any future +revert shows up in a failing test rather than silent behavior drift. + +Refs: + - Issue #36196: M3 support request + - PR #36205 (closed unmerged): Csrayz's M3 + 1M context work + - PR #36212 (open): adds M3 to `_PROVIDER_MODELS["minimax"]` catalog + - PR #6082: M2.7-highspeed → M2.7 for aux model (half-price fix) + - Commit 773a0faca: same profile-layer fix pattern for `deepseek` +""" + +from __future__ import annotations + +import pytest + + +@pytest.fixture(params=["minimax", "minimax-cn", "minimax-oauth"]) +def minimax_profile(request): + """Resolve each registered MiniMax profile. + + Going through ``providers.get_provider_profile`` keeps the test honest — + if someone later replaces the registered class with a plain + ``ProviderProfile``, every assertion below collapses. + """ + import model_tools # noqa: F401 -- triggers plugin discovery + import providers + + profile = providers.get_provider_profile(request.param) + assert profile is not None, f"{request.param} provider profile must be registered" + return profile, request.param + + +class TestMinimaxAuxModelM3: + """MiniMax profile aux model is the new frontier M3, not the stale M2.7. + + The catalog top entry is ``MiniMax-M3`` in + ``hermes_cli.models._PROVIDER_MODELS['minimax']`` and the + user-facing ``model.default`` for a Token-Plan install is M3, + so pinning the aux default to the same model keeps the runtime + consistent (same auth, same billing pool, same rate limits, no + surprise 2x-cost highspeed variant). M3 was released 2026-06-01 + — picking it as the aux default matches the forward-looking + catalog order rather than the pre-M3 era. + """ + + @pytest.mark.parametrize( + "provider_id,expected", + [ + ("minimax", "MiniMax-M3"), + ("minimax-cn", "MiniMax-M3"), + # minimax-oauth sticks with M2.7: the OAuth / Coding Plan + # tier historically used -highspeed (PR #6082 collapsed that + # to plain M2.7 to avoid the 2x TPS surcharge). M3 is not on + # the OAuth/Coding Plan tier per platform docs as of this PR, + # so the safe choice is the cheapest generally-available + # M2.7 — matching PR #6082's intent. + ("minimax-oauth", "MiniMax-M2.7"), + ], + ) + def test_profile_advertises_expected_aux_model( + self, provider_id, expected + ): + import model_tools # noqa: F401 + import providers + + profile = providers.get_provider_profile(provider_id) + assert profile is not None + assert profile.default_aux_model == expected, ( + f"{provider_id} default_aux_model drifted to " + f"{profile.default_aux_model!r}, expected {expected!r}" + ) + + def test_consumer_api_returns_non_empty_for_each_provider(self, minimax_profile): + from agent.auxiliary_client import _get_aux_model_for_provider + + profile, provider_id = minimax_profile + resolved = _get_aux_model_for_provider(provider_id) + assert resolved != "", ( + f"_get_aux_model_for_provider({provider_id!r}) returned empty — " + "the 'No auxiliary LLM provider configured' warning will fire on " + f"every {provider_id} session even though the profile advertises " + f"default_aux_model={profile.default_aux_model!r}" + ) + assert resolved == profile.default_aux_model, ( + f"_get_aux_model_for_provider({provider_id!r}) returned " + f"{resolved!r} but profile advertises {profile.default_aux_model!r} " + "— the consumer API and the profile have drifted out of sync" + ) + + +class TestMinimaxAuxModelNotHighspeed: + """Regression guard against re-introducing the M2.7-highspeed aux default. + + PR #6082 collapsed the highspeed aux choice to plain M2.7 because the + highspeed variant costs 2x with no real benefit for compression / vision / + session-search aux tasks. None of the three MiniMax profiles should + silently re-introduce that 2x-cost path. + """ + + @pytest.mark.parametrize("provider_id", ["minimax", "minimax-cn", "minimax-oauth"]) + def test_default_aux_model_is_not_highspeed(self, provider_id): + import model_tools # noqa: F401 + import providers + + profile = providers.get_provider_profile(provider_id) + assert profile is not None + assert "highspeed" not in profile.default_aux_model.lower(), ( + f"{provider_id} default_aux_model={profile.default_aux_model!r} " + "is a -highspeed variant — that costs 2x for the same model and " + "broke #4082 the first time. Revert to plain M2.7 or M3." + )