Three Copilot inline review comments on #37664, two worth landing
in a polish pass before merge:
1. auxiliary_client.py:270 — Copilot suggested keeping the
minimax-* entries in _API_KEY_PROVIDER_AUX_MODELS_FALLBACK as
a safety net for environments where the profile-based
resolution can't import or run plugin discovery. **Declined.**
The deepseek precedent (commit 773a0faca) explicitly removed
deepseek from the same dict for the same reason — the profile
layer is the source of truth and the dict is a legacy
pre-profiles-system fallback. We do not want to fragment the
codebase by provider: either the profile layer is authoritative
or the dict is. The minimax PR picks profile (matching deepseek)
and the dict stays cleaned up. The risk Copilot raises is
real but theoretical — plugin discovery runs at import time of
the providers module, which is the first thing any modern
Hermes entrypoint imports.
2. tests/agent/test_minimax_provider.py:162 — Copilot flagged
that the test class relies on _get_aux_model_for_provider()
resolving via provider profiles but doesn't explicitly trigger
plugin discovery. **Fixed.** Added 'import model_tools # noqa:
F401' at the top of both test_minimax_aux_is_standard and
test_minimax_aux_not_highspeed. The fixtures in the parallel
test_minimax_profile.py already did this; the legacy test in
test_minimax_provider.py was order-dependent and would silently
break if anyone reorganised the test ordering. Pinned the
dependency explicitly so the test is order-independent.
3. tests/plugins/model_providers/test_minimax_profile.py:46 —
Copilot flagged that the docstring referenced a hard-coded
line number 'hermes_cli/models.py:298' that would go stale.
**Fixed.** Replaced with the symbol reference
'hermes_cli.models._PROVIDER_MODELS[\'minimax\']' which is
stable under file edits and grep-friendly. The new docstring
also reads more naturally — readers don't have to look up
'what's at line 298' to follow the reasoning.
All 221 minimax-related tests still pass.
460 lines
21 KiB
Python
460 lines
21 KiB
Python
"""Tests for MiniMax provider hardening — context lengths, thinking, catalog, beta headers, transport."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
class TestMinimaxContextLengths:
|
|
"""Verify context length entries match official docs.
|
|
|
|
M2.x series is 204,800; M3 is 1M (max output 512K).
|
|
Source: https://platform.minimax.io/docs/api-reference/text-anthropic-api
|
|
"""
|
|
|
|
def test_minimax_prefix_has_correct_context(self):
|
|
from agent.model_metadata import DEFAULT_CONTEXT_LENGTHS
|
|
assert DEFAULT_CONTEXT_LENGTHS["minimax"] == 204_800
|
|
|
|
def test_minimax_models_resolve_via_prefix(self):
|
|
from agent.model_metadata import get_model_context_length
|
|
# M2.x models resolve to 204,800 via the "minimax" catch-all
|
|
for model in ("MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"):
|
|
ctx = get_model_context_length(model, "")
|
|
assert ctx == 204_800, f"{model} expected 204800, got {ctx}"
|
|
|
|
def test_minimax_m3_resolves_to_1m(self):
|
|
from agent.model_metadata import get_model_context_length
|
|
# M3 must beat the generic "minimax" catch-all (204,800) and resolve to
|
|
# a 1M-class context. The exact value depends on the source: our
|
|
# hardcoded catalog says 1,000,000; the OpenRouter catalog reports
|
|
# 1,048,576 (1024²). Either is correct — assert "≥ 1M, not 204,800".
|
|
for model in ("MiniMax-M3", "minimax/minimax-m3", "minimax-m3"):
|
|
ctx = get_model_context_length(model, "")
|
|
assert ctx >= 1_000_000, f"{model} expected 1M-class, got {ctx}"
|
|
|
|
|
|
class TestMinimaxM3StaleCacheGuard:
|
|
"""Pre-catalog builds resolved M3 via the generic 'minimax' catch-all
|
|
(204,800) and persisted it before the 'minimax-m3' (1M) catalog entry
|
|
existed. The step-1 cache guard must drop that stale value and re-resolve
|
|
to 1M, while leaving correct M2.x entries (204,800) untouched.
|
|
"""
|
|
|
|
def test_suggests_minimax_m3(self):
|
|
from agent.model_metadata import _model_name_suggests_minimax_m3
|
|
assert _model_name_suggests_minimax_m3("MiniMax-M3")
|
|
assert _model_name_suggests_minimax_m3("minimax/minimax-m3")
|
|
assert not _model_name_suggests_minimax_m3("MiniMax-M2.7")
|
|
assert not _model_name_suggests_minimax_m3("MiniMax-M2.5")
|
|
|
|
def test_stale_m3_cache_dropped_and_reresolves(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
|
import importlib
|
|
import agent.model_metadata as mm
|
|
importlib.reload(mm)
|
|
base = "https://api.minimaxi.com/anthropic"
|
|
mm.save_context_length("MiniMax-M3", base, 204_800)
|
|
ctx = mm.get_model_context_length(
|
|
"MiniMax-M3", base_url=base, api_key="", provider="minimax-cn"
|
|
)
|
|
# Invariant: the stale 204,800 catch-all value must be DROPPED and
|
|
# re-resolved to M3's real, larger context. The exact value depends on
|
|
# the resolution source (hardcoded catalog = 1,000,000; the models.dev
|
|
# registry currently reports 512,000) — both are large-context values
|
|
# well above the generic "minimax" catch-all. Assert the contract
|
|
# ("> 204,800, stale value gone"), not a brittle literal.
|
|
assert ctx > 204_800, f"stale M3 cache not dropped/re-resolved, got {ctx}"
|
|
|
|
def test_correct_m3_cache_preserved(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
|
import importlib
|
|
import agent.model_metadata as mm
|
|
importlib.reload(mm)
|
|
base = "https://api.minimaxi.com/anthropic"
|
|
mm.save_context_length("MiniMax-M3", base, 1_000_000)
|
|
ctx = mm.get_model_context_length(
|
|
"MiniMax-M3", base_url=base, api_key="", provider="minimax-cn"
|
|
)
|
|
assert ctx == 1_000_000
|
|
|
|
def test_m2_cache_not_clobbered(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
|
import importlib
|
|
import agent.model_metadata as mm
|
|
importlib.reload(mm)
|
|
base = "https://api.minimaxi.com/anthropic"
|
|
# 204,800 is the CORRECT value for M2.x — guard must not touch it.
|
|
for slug in ("MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1"):
|
|
mm.save_context_length(slug, base, 204_800)
|
|
ctx = mm.get_model_context_length(
|
|
slug, base_url=base, api_key="", provider="minimax-cn"
|
|
)
|
|
assert ctx == 204_800, f"{slug} should stay 204800, got {ctx}"
|
|
|
|
|
|
|
|
class TestMinimaxThinkingSupport:
|
|
"""Verify that MiniMax gets manual thinking (not adaptive).
|
|
|
|
MiniMax's Anthropic-compat endpoint officially supports the thinking
|
|
parameter (https://platform.minimax.io/docs/api-reference/text-anthropic-api).
|
|
It should get manual thinking (type=enabled + budget_tokens), NOT adaptive
|
|
thinking (which is Claude 4.6-only).
|
|
"""
|
|
|
|
def test_minimax_m27_gets_manual_thinking(self):
|
|
from agent.anthropic_adapter import build_anthropic_kwargs
|
|
kwargs = build_anthropic_kwargs(
|
|
model="MiniMax-M2.7",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
tools=None,
|
|
max_tokens=4096,
|
|
reasoning_config={"enabled": True, "effort": "medium"},
|
|
)
|
|
assert "thinking" in kwargs
|
|
assert kwargs["thinking"]["type"] == "enabled"
|
|
assert "budget_tokens" in kwargs["thinking"]
|
|
# MiniMax should NOT get adaptive thinking or output_config
|
|
assert "output_config" not in kwargs
|
|
|
|
def test_minimax_m25_gets_manual_thinking(self):
|
|
from agent.anthropic_adapter import build_anthropic_kwargs
|
|
kwargs = build_anthropic_kwargs(
|
|
model="MiniMax-M2.5",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
tools=None,
|
|
max_tokens=4096,
|
|
reasoning_config={"enabled": True, "effort": "high"},
|
|
)
|
|
assert "thinking" in kwargs
|
|
assert kwargs["thinking"]["type"] == "enabled"
|
|
|
|
def test_thinking_still_works_for_claude(self):
|
|
from agent.anthropic_adapter import build_anthropic_kwargs
|
|
kwargs = build_anthropic_kwargs(
|
|
model="claude-sonnet-4-20250514",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
tools=None,
|
|
max_tokens=4096,
|
|
reasoning_config={"enabled": True, "effort": "medium"},
|
|
)
|
|
assert "thinking" in kwargs
|
|
|
|
|
|
class TestMinimaxAuxModel:
|
|
"""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-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")
|
|
|
|
|
|
class TestMinimaxBetaHeaders:
|
|
"""MiniMax Anthropic-compat endpoints reject fine-grained-tool-streaming beta.
|
|
|
|
Verify that build_anthropic_client omits the tool-streaming beta for MiniMax
|
|
(both global and China domains) while keeping it for native Anthropic and
|
|
other third-party endpoints. Covers the fix for #6510 / #6555.
|
|
"""
|
|
|
|
_TOOL_BETA = "fine-grained-tool-streaming-2025-05-14"
|
|
_THINKING_BETA = "interleaved-thinking-2025-05-14"
|
|
|
|
# -- helper ----------------------------------------------------------
|
|
|
|
def _build_and_get_betas(self, api_key, base_url=None):
|
|
"""Build client, return the anthropic-beta header string."""
|
|
from agent.anthropic_adapter import build_anthropic_client
|
|
with patch("agent.anthropic_adapter._anthropic_sdk") as mock_sdk:
|
|
build_anthropic_client(api_key, base_url=base_url)
|
|
kwargs = mock_sdk.Anthropic.call_args[1]
|
|
headers = kwargs.get("default_headers", {})
|
|
return headers.get("anthropic-beta", "")
|
|
|
|
# -- MiniMax global --------------------------------------------------
|
|
|
|
def test_minimax_global_omits_tool_streaming(self):
|
|
betas = self._build_and_get_betas(
|
|
"mm-key-123", base_url="https://api.minimax.io/anthropic"
|
|
)
|
|
assert self._TOOL_BETA not in betas
|
|
assert self._THINKING_BETA in betas
|
|
|
|
def test_minimax_global_trailing_slash(self):
|
|
betas = self._build_and_get_betas(
|
|
"mm-key-123", base_url="https://api.minimax.io/anthropic/"
|
|
)
|
|
assert self._TOOL_BETA not in betas
|
|
|
|
# -- MiniMax China ---------------------------------------------------
|
|
|
|
def test_minimax_cn_omits_tool_streaming(self):
|
|
betas = self._build_and_get_betas(
|
|
"mm-cn-key-456", base_url="https://api.minimaxi.com/anthropic"
|
|
)
|
|
assert self._TOOL_BETA not in betas
|
|
assert self._THINKING_BETA in betas
|
|
|
|
def test_minimax_cn_trailing_slash(self):
|
|
betas = self._build_and_get_betas(
|
|
"mm-cn-key-456", base_url="https://api.minimaxi.com/anthropic/"
|
|
)
|
|
assert self._TOOL_BETA not in betas
|
|
|
|
# -- Non-MiniMax keeps full betas ------------------------------------
|
|
|
|
def test_native_anthropic_keeps_tool_streaming(self):
|
|
betas = self._build_and_get_betas("sk-ant-api03-real-key-here")
|
|
assert self._TOOL_BETA in betas
|
|
assert self._THINKING_BETA in betas
|
|
|
|
def test_third_party_proxy_keeps_tool_streaming(self):
|
|
betas = self._build_and_get_betas(
|
|
"custom-key", base_url="https://my-proxy.example.com/anthropic"
|
|
)
|
|
assert self._TOOL_BETA in betas
|
|
|
|
def test_custom_base_url_keeps_tool_streaming(self):
|
|
betas = self._build_and_get_betas(
|
|
"custom-key", base_url="https://custom.api.com"
|
|
)
|
|
assert self._TOOL_BETA in betas
|
|
|
|
# -- _common_betas_for_base_url unit tests ---------------------------
|
|
|
|
def test_common_betas_none_url(self):
|
|
from agent.anthropic_adapter import _common_betas_for_base_url, _COMMON_BETAS
|
|
assert _common_betas_for_base_url(None) == _COMMON_BETAS
|
|
|
|
def test_common_betas_empty_url(self):
|
|
from agent.anthropic_adapter import _common_betas_for_base_url, _COMMON_BETAS
|
|
assert _common_betas_for_base_url("") == _COMMON_BETAS
|
|
|
|
def test_common_betas_minimax_url(self):
|
|
from agent.anthropic_adapter import _common_betas_for_base_url, _TOOL_STREAMING_BETA
|
|
betas = _common_betas_for_base_url("https://api.minimax.io/anthropic")
|
|
assert _TOOL_STREAMING_BETA not in betas
|
|
assert len(betas) > 0 # still has other betas
|
|
|
|
def test_common_betas_minimax_cn_url(self):
|
|
from agent.anthropic_adapter import _common_betas_for_base_url, _TOOL_STREAMING_BETA
|
|
betas = _common_betas_for_base_url("https://api.minimaxi.com/anthropic")
|
|
assert _TOOL_STREAMING_BETA not in betas
|
|
|
|
def test_common_betas_regular_url(self):
|
|
from agent.anthropic_adapter import _common_betas_for_base_url, _COMMON_BETAS
|
|
assert _common_betas_for_base_url("https://api.anthropic.com") == _COMMON_BETAS
|
|
|
|
|
|
class TestMinimaxApiMode:
|
|
"""Verify determine_api_mode returns anthropic_messages for MiniMax providers.
|
|
|
|
The MiniMax /anthropic endpoint speaks Anthropic Messages wire format,
|
|
not OpenAI chat completions. The overlay transport must reflect this
|
|
so that code paths calling determine_api_mode() without a base_url
|
|
(e.g. /model switch) get the correct api_mode.
|
|
"""
|
|
|
|
def test_minimax_returns_anthropic_messages(self):
|
|
from hermes_cli.providers import determine_api_mode
|
|
assert determine_api_mode("minimax") == "anthropic_messages"
|
|
|
|
def test_minimax_cn_returns_anthropic_messages(self):
|
|
from hermes_cli.providers import determine_api_mode
|
|
assert determine_api_mode("minimax-cn") == "anthropic_messages"
|
|
|
|
def test_minimax_with_url_also_works(self):
|
|
from hermes_cli.providers import determine_api_mode
|
|
# Even with explicit base_url, provider lookup takes priority
|
|
assert determine_api_mode("minimax", "https://api.minimax.io/anthropic") == "anthropic_messages"
|
|
|
|
def test_anthropic_still_returns_anthropic_messages(self):
|
|
from hermes_cli.providers import determine_api_mode
|
|
assert determine_api_mode("anthropic") == "anthropic_messages"
|
|
|
|
def test_openai_returns_chat_completions(self):
|
|
from hermes_cli.providers import determine_api_mode
|
|
# Sanity check: standard providers are unaffected
|
|
result = determine_api_mode("deepseek")
|
|
assert result == "chat_completions"
|
|
|
|
|
|
class TestMinimaxMaxOutput:
|
|
"""Verify _get_anthropic_max_output returns correct limits for MiniMax models.
|
|
|
|
MiniMax max output is 131,072 tokens (source: OpenClaw model definitions,
|
|
cross-referenced with MiniMax API behavior).
|
|
"""
|
|
|
|
def test_minimax_m27_output_limit(self):
|
|
from agent.anthropic_adapter import _get_anthropic_max_output
|
|
assert _get_anthropic_max_output("MiniMax-M2.7") == 131_072
|
|
|
|
def test_minimax_m25_output_limit(self):
|
|
from agent.anthropic_adapter import _get_anthropic_max_output
|
|
assert _get_anthropic_max_output("MiniMax-M2.5") == 131_072
|
|
|
|
def test_minimax_m2_output_limit(self):
|
|
from agent.anthropic_adapter import _get_anthropic_max_output
|
|
assert _get_anthropic_max_output("MiniMax-M2") == 131_072
|
|
|
|
def test_claude_output_unaffected(self):
|
|
from agent.anthropic_adapter import _get_anthropic_max_output
|
|
# Sanity: Claude limits are not broken by the MiniMax entry
|
|
assert _get_anthropic_max_output("claude-sonnet-4-6") == 64_000
|
|
|
|
|
|
class TestMinimaxPreserveDots:
|
|
"""Verify that MiniMax model names preserve dots through the Anthropic adapter.
|
|
|
|
MiniMax model IDs like 'MiniMax-M2.7' must NOT have dots converted to
|
|
hyphens — the endpoint expects the exact name with dots.
|
|
"""
|
|
|
|
def test_minimax_provider_preserves_dots(self):
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(provider="minimax", base_url="")
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is True
|
|
|
|
def test_minimax_cn_provider_preserves_dots(self):
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(provider="minimax-cn", base_url="")
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is True
|
|
|
|
def test_minimax_url_preserves_dots(self):
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(provider="custom", base_url="https://api.minimax.io/anthropic")
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is True
|
|
|
|
def test_minimax_cn_url_preserves_dots(self):
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(provider="custom", base_url="https://api.minimaxi.com/anthropic")
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is True
|
|
|
|
def test_anthropic_does_not_preserve_dots(self):
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(provider="anthropic", base_url="https://api.anthropic.com")
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is False
|
|
|
|
def test_opencode_zen_provider_preserves_dots(self):
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(provider="opencode-zen", base_url="")
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is True
|
|
|
|
def test_opencode_zen_url_preserves_dots(self):
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(provider="custom", base_url="https://opencode.ai/zen/v1")
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is True
|
|
|
|
def test_zai_provider_preserves_dots(self):
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(provider="zai", base_url="")
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is True
|
|
|
|
def test_bigmodel_cn_url_preserves_dots(self):
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(provider="custom", base_url="https://open.bigmodel.cn/api/paas/v4")
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is True
|
|
|
|
def test_normalize_preserves_m25_free_dot(self):
|
|
from agent.anthropic_adapter import normalize_model_name
|
|
assert normalize_model_name("minimax-m2.5-free", preserve_dots=True) == "minimax-m2.5-free"
|
|
|
|
def test_normalize_preserves_m27_dot(self):
|
|
from agent.anthropic_adapter import normalize_model_name
|
|
assert normalize_model_name("MiniMax-M2.7", preserve_dots=True) == "MiniMax-M2.7"
|
|
|
|
def test_normalize_preserves_non_anthropic_dots_without_preserve(self):
|
|
from agent.anthropic_adapter import normalize_model_name
|
|
# Non-Anthropic model families use dots as canonical version separators;
|
|
# only Claude/Anthropic names are hyphen-normalized by default.
|
|
assert normalize_model_name("MiniMax-M2.7", preserve_dots=False) == "MiniMax-M2.7"
|
|
|
|
def test_normalize_still_converts_claude_dots_without_preserve(self):
|
|
from agent.anthropic_adapter import normalize_model_name
|
|
assert normalize_model_name("claude-opus-4.6", preserve_dots=False) == "claude-opus-4-6"
|
|
|
|
|
|
class TestMinimaxSwitchModelCredentialGuard:
|
|
"""Verify switch_model() does not leak Anthropic credentials to MiniMax.
|
|
|
|
The __init__ path correctly guards against this (line 761), but switch_model()
|
|
must mirror that guard. Without it, /model switch to minimax with no explicit
|
|
api_key would fall back to resolve_anthropic_token() and send Anthropic creds
|
|
to the MiniMax endpoint.
|
|
"""
|
|
|
|
def test_switch_to_minimax_does_not_resolve_anthropic_token(self):
|
|
"""switch_model() should NOT call resolve_anthropic_token() for MiniMax."""
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
with patch("run_agent.AIAgent.__init__", return_value=None):
|
|
from run_agent import AIAgent
|
|
agent = AIAgent.__new__(AIAgent)
|
|
agent.provider = "anthropic"
|
|
agent.model = "claude-sonnet-4"
|
|
agent.api_key = "sk-ant-fake"
|
|
agent.base_url = "https://api.anthropic.com"
|
|
agent.api_mode = "anthropic_messages"
|
|
agent._anthropic_base_url = "https://api.anthropic.com"
|
|
agent._anthropic_api_key = "sk-ant-fake"
|
|
agent._is_anthropic_oauth = False
|
|
agent._client_kwargs = {}
|
|
agent.client = None
|
|
agent._anthropic_client = MagicMock()
|
|
agent._fallback_chain = []
|
|
|
|
with patch("agent.anthropic_adapter.build_anthropic_client") as mock_build, \
|
|
patch("agent.anthropic_adapter.resolve_anthropic_token", return_value="sk-ant-leaked") as mock_resolve, \
|
|
patch("agent.anthropic_adapter._is_oauth_token", return_value=False):
|
|
|
|
agent.switch_model(
|
|
new_model="MiniMax-M2.7",
|
|
new_provider="minimax",
|
|
api_mode="anthropic_messages",
|
|
api_key="mm-key-123",
|
|
base_url="https://api.minimax.io/anthropic",
|
|
)
|
|
# resolve_anthropic_token should NOT be called for non-Anthropic providers
|
|
mock_resolve.assert_not_called()
|
|
# The key passed to build_anthropic_client should be the MiniMax key
|
|
build_args = mock_build.call_args
|
|
assert build_args[0][0] == "mm-key-123"
|