Skip to content

Commit a3fcb22

Browse files
committed
fix(minimax): apply empty live model catalogs
1 parent e1debb4 commit a3fcb22

4 files changed

Lines changed: 79 additions & 23 deletions

File tree

src/pythinker_code/auth/minimax.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,12 @@ async def _fetch_minimax_models(
192192
) -> tuple[MiniMaxModel, ...]:
193193
async with session.get(url, headers=headers, raise_for_status=True) as response:
194194
payload = await response.json(content_type=None)
195-
return _parse_discovered_models(payload)
195+
if not isinstance(payload, dict):
196+
raise ValueError(f"Unexpected MiniMax models response for {url}")
197+
payload_map = cast(dict[str, Any], payload)
198+
if not isinstance(payload_map.get("data"), list):
199+
raise ValueError(f"Unexpected MiniMax models response for {url}")
200+
return _parse_discovered_models(payload_map)
196201

197202

198203
async def _discover_minimax_models(api_key: str) -> tuple[MiniMaxModel, ...]:
@@ -217,8 +222,7 @@ async def _discover_minimax_models(api_key: str) -> tuple[MiniMaxModel, ...]:
217222
except (aiohttp.ClientError, TimeoutError, ValueError) as exc:
218223
errors.append(exc)
219224
continue
220-
if models:
221-
return models
225+
return models
222226

223227
if auth_errors:
224228
raise auth_errors[0]
@@ -295,8 +299,7 @@ async def refresh_minimax_models(config: Config) -> tuple[MiniMaxModel, ...] | N
295299
api_key = _minimax_api_key(config)
296300
if api_key is None:
297301
return None
298-
discovered = await _discover_minimax_models(api_key)
299-
return discovered or None
302+
return await _discover_minimax_models(api_key)
300303

301304

302305
async def login_minimax_api_key(
@@ -323,9 +326,7 @@ async def login_minimax_api_key(
323326

324327
models = MINIMAX_MODELS
325328
try:
326-
discovered = await _discover_minimax_models(resolved_key)
327-
if discovered:
328-
models = discovered
329+
models = await _discover_minimax_models(resolved_key)
329330
except aiohttp.ClientResponseError as exc:
330331
if exc.status in {401, 403}:
331332
yield OAuthEvent("error", "Invalid MiniMax API key; the key was not saved.")

src/pythinker_code/auth/platforms.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ async def refresh_managed_models(config: Config) -> bool:
414414
minimax_models = await refresh_minimax_models(config)
415415
except (aiohttp.ClientError, TimeoutError, ValueError) as exc:
416416
logger.warning("Failed to refresh MiniMax models: {error}", error=exc)
417-
if minimax_models and apply_minimax_models(config, minimax_models):
417+
if minimax_models is not None and apply_minimax_models(config, minimax_models):
418418
changed = True
419419

420420
if changed:
@@ -425,7 +425,7 @@ async def refresh_managed_models(config: Config) -> bool:
425425
save_changed = True
426426
if opencode_go_models and apply_opencode_go_models(config_for_save, opencode_go_models):
427427
save_changed = True
428-
if minimax_models and apply_minimax_models(config_for_save, minimax_models):
428+
if minimax_models is not None and apply_minimax_models(config_for_save, minimax_models):
429429
save_changed = True
430430
if save_changed:
431431
save_config(config_for_save)

tests/auth/test_minimax_auth.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -171,26 +171,42 @@ async def fake_discover(api_key):
171171

172172
@pytest.mark.asyncio
173173
async def test_login_minimax_token_plan_uses_discovered_available_subset(monkeypatch, tmp_path):
174-
from pythinker_code.auth.minimax import MiniMaxModel, login_minimax_api_key
174+
from pythinker_code.auth.minimax import MINIMAX_ANTHROPIC_MODELS_URL, login_minimax_api_key
175175

176176
monkeypatch.setenv("PYTHINKER_SHARE_DIR", str(tmp_path))
177177
config = Config(is_from_default_location=True)
178178

179-
async def fake_discover(api_key):
180-
assert api_key == "sk-cp-token-plan-abc"
181-
return (
182-
MiniMaxModel(
183-
model_id="MiniMax-M2.7",
184-
alias_suffix="m2.7",
185-
display_name="MiniMax M2.7",
186-
),
187-
)
179+
class FakeResponse:
180+
async def __aenter__(self):
181+
return self
188182

189-
monkeypatch.setattr("pythinker_code.auth.minimax._discover_minimax_models", fake_discover)
183+
async def __aexit__(self, *args):
184+
pass
185+
186+
async def json(self, *, content_type=None):
187+
return {"data": [{"id": "MiniMax-M2.7"}]}
188+
189+
class FakeSession:
190+
def __init__(self):
191+
self.calls: list[tuple[str, dict[str, str]]] = []
192+
193+
async def __aenter__(self):
194+
return self
195+
196+
async def __aexit__(self, *args):
197+
pass
198+
199+
def get(self, url, *, headers, raise_for_status):
200+
self.calls.append((url, headers))
201+
return FakeResponse()
202+
203+
session = FakeSession()
204+
monkeypatch.setattr("pythinker_code.auth.minimax.new_client_session", lambda **_: session)
190205

191206
events = [event async for event in login_minimax_api_key(config, "sk-cp-token-plan-abc")]
192207

193208
assert [event.type for event in events] == ["info", "success"]
209+
assert session.calls == [(MINIMAX_ANTHROPIC_MODELS_URL, {"X-Api-Key": "sk-cp-token-plan-abc"})]
194210
assert set(config.models) == {"minimax/m2.7"}
195211
assert config.default_model == "minimax/m2.7"
196212

@@ -227,6 +243,8 @@ async def fake_discover(api_key):
227243
assert "Token Plan" in events[0].message
228244
assert types[-1] == "success"
229245
assert "sk-cp-token-plan-abc" not in "\n".join(event.json for event in events)
246+
assert config.models == {}
247+
assert config.default_model == ""
230248

231249

232250
@pytest.mark.asyncio

tests/auth/test_platforms.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@ async def test_refresh_managed_models_refreshes_minimax_token_plan_without_relog
800800

801801
with (
802802
patch(
803-
"pythinker_code.auth.minimax._discover_minimax_models",
803+
"pythinker_code.auth.minimax.refresh_minimax_models",
804804
new=AsyncMock(return_value=discovered),
805805
),
806806
patch("pythinker_code.auth.platforms.list_models", new=AsyncMock()) as list_models_mock,
@@ -824,6 +824,41 @@ async def test_refresh_managed_models_refreshes_minimax_token_plan_without_relog
824824
assert "minimax/m2.7-highspeed" not in saved[0].models
825825

826826

827+
@pytest.mark.asyncio
828+
async def test_refresh_managed_models_applies_empty_minimax_catalog():
829+
"""An authenticated empty MiniMax catalog is authoritative and prunes stale models."""
830+
from pythinker_code.auth.minimax import MINIMAX_ANTHROPIC_PROVIDER_KEY
831+
832+
config = _make_minimax_config()
833+
saved: list[Config] = []
834+
835+
with (
836+
patch(
837+
"pythinker_code.auth.minimax.refresh_minimax_models",
838+
new=AsyncMock(return_value=()),
839+
) as refresh_mock,
840+
patch("pythinker_code.auth.platforms.list_models", new=AsyncMock()) as list_models_mock,
841+
patch("pythinker_code.auth.platforms.load_config", side_effect=_make_minimax_config),
842+
patch(
843+
"pythinker_code.auth.platforms.save_config",
844+
side_effect=lambda cfg, *a, **k: saved.append(cfg),
845+
),
846+
):
847+
changed = await refresh_managed_models(config)
848+
849+
assert changed is True
850+
assert refresh_mock.await_count == 1
851+
assert list_models_mock.await_count == 0
852+
assert not any(
853+
model.provider == MINIMAX_ANTHROPIC_PROVIDER_KEY for model in config.models.values()
854+
)
855+
assert config.default_model == ""
856+
assert len(saved) == 1
857+
assert not any(
858+
model.provider == MINIMAX_ANTHROPIC_PROVIDER_KEY for model in saved[0].models.values()
859+
)
860+
861+
827862
@pytest.mark.asyncio
828863
async def test_refresh_managed_models_isolates_minimax_discovery_failure():
829864
"""MiniMax refresh failure must not abort other managed-provider refreshes."""
@@ -857,7 +892,7 @@ def _config_with_generic_provider() -> Config:
857892

858893
with (
859894
patch(
860-
"pythinker_code.auth.minimax._discover_minimax_models",
895+
"pythinker_code.auth.minimax.refresh_minimax_models",
861896
new=AsyncMock(side_effect=aiohttp.ClientConnectionError("offline")),
862897
),
863898
patch(
@@ -878,4 +913,6 @@ def _config_with_generic_provider() -> Config:
878913
assert changed is True
879914
assert len(saved) == 1
880915
assert saved[0].models["pythinker-code/pythinker-for-coding"].max_context_size == 200_000
916+
assert "minimax/m2.7-highspeed" in saved[0].models
917+
assert saved[0].models["minimax/m2.7-highspeed"].provider == "managed:minimax-anthropic"
881918
assert "minimax/m2.7-highspeed" in config.models

0 commit comments

Comments
 (0)