feat(minimax): add m3 model support (#88860)

This commit is contained in:
Peter Steinberger
2026-05-31 21:47:47 -04:00
committed by GitHub
parent 9919e4601f
commit 0369672691
25 changed files with 279 additions and 115 deletions

View File

@@ -1953,7 +1953,7 @@ jobs:
profiles: stable full
- suite_id: native-live-src-gateway-profiles-minimax
label: Native live gateway profiles MiniMax
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M2.7,minimax-portal/MiniMax-M2.7 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M3,minimax-portal/MiniMax-M3 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 60
profile_env_only: false
profiles: stable full
@@ -2252,7 +2252,7 @@ jobs:
profiles: stable full
- suite_id: live-gateway-minimax-docker
label: Docker live gateway MiniMax
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M2.7,minimax-portal/MiniMax-M2.7 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M3,minimax-portal/MiniMax-M3 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
profiles: stable full

View File

@@ -303,7 +303,7 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
| Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` |
| Kilo Gateway | `kilocode` | `KILOCODE_API_KEY` | `kilocode/kilo/auto` |
| Kimi Coding | `kimi` | `KIMI_API_KEY` or `KIMICODE_API_KEY` | `kimi/kimi-for-coding` |
| MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M2.7` |
| MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M3` |
| Mistral | `mistral` | `MISTRAL_API_KEY` | `mistral/mistral-large-latest` |
| Moonshot | `moonshot` | `MOONSHOT_API_KEY` | `moonshot/kimi-k2.6` |
| NVIDIA | `nvidia` | `NVIDIA_API_KEY` | `nvidia/nvidia/nemotron-3-super-120b-a12b` |
@@ -331,7 +331,7 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
Gemini-backed refs follow the same proxy-Gemini sanitation path; `kilocode/kilo/auto` and other proxy-reasoning-unsupported refs skip proxy reasoning injection.
</Accordion>
<Accordion title="MiniMax">
API-key onboarding writes explicit text-only M2.7 chat model definitions; image understanding stays on the plugin-owned `MiniMax-VL-01` media provider.
API-key onboarding writes explicit M3 and M2.7 chat model definitions; image understanding stays on the plugin-owned `MiniMax-VL-01` media provider.
</Accordion>
<Accordion title="NVIDIA">
Model ids use a `nvidia/<vendor>/<model>` namespace (for example `nvidia/nvidia/nemotron-...` alongside `nvidia/moonshotai/kimi-k2.5`); pickers preserve the literal `<provider>/<model-id>` composition while the canonical key sent to the API stays single-prefixed.
@@ -537,7 +537,7 @@ On MiniMax's Anthropic-compatible streaming path, OpenClaw disables thinking by
Plugin-owned capability split:
- Text/chat defaults stay on `minimax/MiniMax-M2.7`
- Text/chat defaults stay on `minimax/MiniMax-M3`
- Image generation is `minimax/image-01` or `minimax-portal/image-01`
- Image understanding is plugin-owned `MiniMax-VL-01` on both MiniMax auth paths
- Web search stays on provider id `minimax`

View File

@@ -645,14 +645,14 @@ Interactive custom-provider onboarding infers image input for common vision mode
<Accordion title="Local models (LM Studio)">
See [Local Models](/gateway/local-models). TL;DR: run a large local model via LM Studio Responses API on serious hardware; keep hosted models merged for fallback.
</Accordion>
<Accordion title="MiniMax M2.7 (direct)">
<Accordion title="MiniMax M3 (direct)">
```json5
{
agents: {
defaults: {
model: { primary: "minimax/MiniMax-M2.7" },
model: { primary: "minimax/MiniMax-M3" },
models: {
"minimax/MiniMax-M2.7": { alias: "Minimax" },
"minimax/MiniMax-M3": { alias: "Minimax" },
},
},
},
@@ -665,12 +665,12 @@ Interactive custom-provider onboarding infers image input for common vision mode
api: "anthropic-messages",
models: [
{
id: "MiniMax-M2.7",
name: "MiniMax M2.7",
id: "MiniMax-M3",
name: "MiniMax M3",
reasoning: true,
input: ["text"],
cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 },
contextWindow: 204800,
input: ["text", "image"],
cost: { input: 0.6, output: 2.4, cacheRead: 0.12, cacheWrite: 0 },
contextWindow: 1000000,
maxTokens: 131072,
},
],
@@ -680,7 +680,7 @@ Interactive custom-provider onboarding infers image input for common vision mode
}
```
Set `MINIMAX_API_KEY`. Shortcuts: `openclaw onboard --auth-choice minimax-global-api` or `openclaw onboard --auth-choice minimax-cn-api`. The model catalog defaults to M2.7 only. On the Anthropic-compatible streaming path, OpenClaw disables MiniMax thinking by default unless you explicitly set `thinking` yourself. `/fast on` or `params.fastMode: true` rewrites `MiniMax-M2.7` to `MiniMax-M2.7-highspeed`.
Set `MINIMAX_API_KEY`. Shortcuts: `openclaw onboard --auth-choice minimax-global-api` or `openclaw onboard --auth-choice minimax-cn-api`. The model catalog defaults to M3 and also includes the M2.7 variants. On the Anthropic-compatible streaming path, OpenClaw disables MiniMax thinking by default unless you explicitly set `thinking` yourself. `/fast on` or `params.fastMode: true` rewrites `MiniMax-M2.7` to `MiniMax-M2.7-highspeed`.
</Accordion>
<Accordion title="Moonshot AI (Kimi)">

View File

@@ -215,7 +215,7 @@ troubleshooting, see the main [FAQ](/help/faq).
</Accordion>
<Accordion title='Why do I see "Unknown model: minimax/MiniMax-M2.7"?'>
<Accordion title='Why do I see "Unknown model: minimax/MiniMax-M3"?'>
This means the **provider isn't configured** (no MiniMax provider config or auth
profile was found), so the model can't be resolved.
@@ -227,8 +227,9 @@ troubleshooting, see the main [FAQ](/help/faq).
(`MINIMAX_API_KEY` for `minimax`, `MINIMAX_OAUTH_TOKEN` or stored MiniMax
OAuth for `minimax-portal`).
3. Use the exact model id (case-sensitive) for your auth path:
`minimax/MiniMax-M2.7` or `minimax/MiniMax-M2.7-highspeed` for API-key
setup, or `minimax-portal/MiniMax-M2.7` /
`minimax/MiniMax-M3`, `minimax/MiniMax-M2.7`, or
`minimax/MiniMax-M2.7-highspeed` for API-key setup, or
`minimax-portal/MiniMax-M3`, `minimax-portal/MiniMax-M2.7`, or
`minimax-portal/MiniMax-M2.7-highspeed` for OAuth setup.
4. Run:
@@ -253,9 +254,9 @@ troubleshooting, see the main [FAQ](/help/faq).
env: { MINIMAX_API_KEY: "sk-...", OPENAI_API_KEY: "sk-..." },
agents: {
defaults: {
model: { primary: "minimax/MiniMax-M2.7" },
model: { primary: "minimax/MiniMax-M3" },
models: {
"minimax/MiniMax-M2.7": { alias: "minimax" },
"minimax/MiniMax-M3": { alias: "minimax" },
"openai/gpt-5.5": { alias: "gpt" },
},
},

View File

@@ -73,7 +73,7 @@ Live tests are split into two layers so we can isolate failures:
- `pnpm test:live` (or `OPENCLAW_LIVE_TEST=1` if invoking Vitest directly)
- Set `OPENCLAW_LIVE_MODELS=modern`, `small`, or `all` (alias for modern) to actually run this suite; otherwise it skips to keep `pnpm test:live` focused on gateway smoke
- How to select models:
- `OPENCLAW_LIVE_MODELS=modern` to run the modern allowlist (Opus/Sonnet 4.6+, GPT-5.2 + Codex, Gemini 3, DeepSeek V4, GLM 4.7, MiniMax M2.7, Grok 4.3)
- `OPENCLAW_LIVE_MODELS=modern` to run the modern allowlist (Opus/Sonnet 4.6+, GPT-5.2 + Codex, Gemini 3, DeepSeek V4, GLM 4.7, MiniMax M3, Grok 4.3)
- `OPENCLAW_LIVE_MODELS=small` to run the constrained small-model allowlist (Qwen 8B/9B local-compatible routes, Ollama Gemma, OpenRouter Qwen/GLM, and Z.AI GLM)
- `OPENCLAW_LIVE_MODELS=all` is an alias for the modern allowlist
- or `OPENCLAW_LIVE_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,..."` (comma allowlist)
@@ -109,7 +109,7 @@ Live tests are split into two layers so we can isolate failures:
- How to enable:
- `pnpm test:live` (or `OPENCLAW_LIVE_TEST=1` if invoking Vitest directly)
- How to select models:
- Default: modern allowlist (Opus/Sonnet 4.6+, GPT-5.2 + Codex, Gemini 3, DeepSeek V4, GLM 4.7, MiniMax M2.7, Grok 4.3)
- Default: modern allowlist (Opus/Sonnet 4.6+, GPT-5.2 + Codex, Gemini 3, DeepSeek V4, GLM 4.7, MiniMax M3, Grok 4.3)
- `OPENCLAW_LIVE_GATEWAY_MODELS=all` is an alias for the modern allowlist
- Or set `OPENCLAW_LIVE_GATEWAY_MODELS="provider/model"` (or comma list) to narrow
- Modern/all gateway sweeps default to a curated high-signal cap; set `OPENCLAW_LIVE_GATEWAY_MAX_MODELS=0` for an exhaustive modern sweep or a positive number for a smaller cap.
@@ -351,7 +351,7 @@ Narrow, explicit allowlists are fastest and least flaky:
- `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- Tool calling across several providers:
- `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3-flash-preview,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M2.7" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- `OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3-flash-preview,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M3" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- Google focus (Gemini API key + Antigravity):
- Gemini (API key): `OPENCLAW_LIVE_GATEWAY_MODELS="google/gemini-3-flash-preview" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
@@ -385,10 +385,10 @@ This is the "common models" run we expect to keep working:
- Google (Antigravity): `google-antigravity/claude-opus-4-6-thinking` and `google-antigravity/gemini-3-flash`
- DeepSeek: `deepseek/deepseek-v4-flash` and `deepseek/deepseek-v4-pro`
- Z.AI (GLM): `zai/glm-5.1`
- MiniMax: `minimax/MiniMax-M2.7`
- MiniMax: `minimax/MiniMax-M3`
Run gateway smoke with tools + image:
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3.1-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M2.7" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.5,anthropic/claude-opus-4-6,google/gemini-3.1-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,deepseek/deepseek-v4-flash,zai/glm-5.1,minimax/MiniMax-M3" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
### Baseline: tool calling (Read + optional Exec)
@@ -399,7 +399,7 @@ Pick at least one per provider family:
- Google: `google/gemini-3-flash-preview` (or `google/gemini-3.1-pro-preview`)
- DeepSeek: `deepseek/deepseek-v4-flash`
- Z.AI (GLM): `zai/glm-5.1`
- MiniMax: `minimax/MiniMax-M2.7`
- MiniMax: `minimax/MiniMax-M3`
Optional additional coverage (nice to have):

View File

@@ -6,7 +6,7 @@ read_when:
title: "MiniMax"
---
OpenClaw's MiniMax provider defaults to **MiniMax M2.7**.
OpenClaw's MiniMax provider defaults to **MiniMax M3**.
MiniMax also provides:
@@ -26,7 +26,8 @@ Provider split:
| Model | Type | Description |
| ------------------------ | ---------------- | ---------------------------------------- |
| `MiniMax-M2.7` | Chat (reasoning) | Default hosted reasoning model |
| `MiniMax-M3` | Chat (reasoning) | Default hosted reasoning model |
| `MiniMax-M2.7` | Chat (reasoning) | Previous hosted reasoning model |
| `MiniMax-M2.7-highspeed` | Chat (reasoning) | Faster M2.7 reasoning tier |
| `MiniMax-VL-01` | Vision | Image understanding model |
| `image-01` | Image generation | Text-to-image and image-to-image editing |
@@ -79,7 +80,7 @@ Choose your preferred auth method and follow the setup steps.
</Tabs>
<Note>
OAuth setups use the `minimax-portal` provider id. Model refs follow the form `minimax-portal/MiniMax-M2.7`.
OAuth setups use the `minimax-portal` provider id. Model refs follow the form `minimax-portal/MiniMax-M3`.
</Note>
<Tip>
@@ -131,7 +132,7 @@ Choose your preferred auth method and follow the setup steps.
```json5
{
env: { MINIMAX_API_KEY: "sk-..." },
agents: { defaults: { model: { primary: "minimax/MiniMax-M2.7" } } },
agents: { defaults: { model: { primary: "minimax/MiniMax-M3" } } },
models: {
mode: "merge",
providers: {
@@ -140,6 +141,15 @@ Choose your preferred auth method and follow the setup steps.
apiKey: "${MINIMAX_API_KEY}",
api: "anthropic-messages",
models: [
{
id: "MiniMax-M3",
name: "MiniMax M3",
reasoning: true,
input: ["text", "image"],
cost: { input: 0.6, output: 2.4, cacheRead: 0.12, cacheWrite: 0 },
contextWindow: 1000000,
maxTokens: 131072,
},
{
id: "MiniMax-M2.7",
name: "MiniMax M2.7",
@@ -170,7 +180,7 @@ Choose your preferred auth method and follow the setup steps.
</Warning>
<Note>
API-key setups use the `minimax` provider id. Model refs follow the form `minimax/MiniMax-M2.7`.
API-key setups use the `minimax` provider id. Model refs follow the form `minimax/MiniMax-M3`.
</Note>
</Tab>
@@ -243,9 +253,10 @@ through the CN endpoint; the default global endpoint is
`https://api.minimax.io`.
When onboarding or API-key setup writes explicit `models.providers.minimax`
entries, OpenClaw materializes `MiniMax-M2.7` and
`MiniMax-M2.7-highspeed` as text-only chat models. Image understanding is
exposed separately through the plugin-owned `MiniMax-VL-01` media provider.
entries, OpenClaw materializes `MiniMax-M3`, `MiniMax-M2.7`, and
`MiniMax-M2.7-highspeed` as chat models. M3 advertises text and image input;
image understanding remains exposed separately through the plugin-owned
`MiniMax-VL-01` media provider.
<Note>
See [Image Generation](/tools/image-generation) for shared tool parameters, provider selection, and failover behavior.
@@ -353,7 +364,7 @@ catalog:
| `minimax-portal` | `MiniMax-VL-01` |
That is why automatic media routing can use MiniMax image understanding even
when the bundled text-provider catalog still shows text-only M2.7 chat refs.
when the bundled text-provider catalog also includes M3 image-capable chat refs.
### Web search
@@ -437,12 +448,12 @@ See [MiniMax Search](/tools/minimax-search) for full web search configuration an
- Model refs follow the auth path:
- API-key setup: `minimax/<model>`
- OAuth setup: `minimax-portal/<model>`
- Default chat model: `MiniMax-M2.7`
- Alternate chat model: `MiniMax-M2.7-highspeed`
- Onboarding and direct API-key setup write text-only model definitions for both M2.7 variants
- Default chat model: `MiniMax-M3`
- Alternate chat models: `MiniMax-M2.7`, `MiniMax-M2.7-highspeed`
- Onboarding and direct API-key setup write model definitions for M3 and both M2.7 variants
- Image understanding uses the plugin-owned `MiniMax-VL-01` media provider
- Update pricing values in `models.json` if you need exact cost tracking
- Use `openclaw models list` to confirm the current provider id, then switch with `openclaw models set minimax/MiniMax-M2.7` or `openclaw models set minimax-portal/MiniMax-M2.7`
- Use `openclaw models list` to confirm the current provider id, then switch with `openclaw models set minimax/MiniMax-M3` or `openclaw models set minimax-portal/MiniMax-M3`
<Tip>
Referral link for MiniMax Coding Plan (10% off): [MiniMax Coding Plan](https://platform.minimax.io/subscribe/coding-plan?code=DbXJTRClnb&source=link)
@@ -455,7 +466,7 @@ See [Model providers](/concepts/model-providers) for provider rules.
## Troubleshooting
<AccordionGroup>
<Accordion title='"Unknown model: minimax/MiniMax-M2.7"'>
<Accordion title='"Unknown model: minimax/MiniMax-M3"'>
This usually means the **MiniMax provider is not configured** (no matching provider entry and no MiniMax auth profile/env key found). A fix for this detection is in **2026.1.12**. Fix by:
- Upgrading to **2026.1.12** (or run from source `main`), then restarting the gateway.
@@ -465,8 +476,8 @@ See [Model providers](/concepts/model-providers) for provider rules.
Make sure the model id is **case-sensitive**:
- API-key path: `minimax/MiniMax-M2.7` or `minimax/MiniMax-M2.7-highspeed`
- OAuth path: `minimax-portal/MiniMax-M2.7` or `minimax-portal/MiniMax-M2.7-highspeed`
- API-key path: `minimax/MiniMax-M3`, `minimax/MiniMax-M2.7`, or `minimax/MiniMax-M2.7-highspeed`
- OAuth path: `minimax-portal/MiniMax-M3`, `minimax-portal/MiniMax-M2.7`, or `minimax-portal/MiniMax-M2.7-highspeed`
Then recheck with:

View File

@@ -47,7 +47,7 @@ For a high-level overview, see [Onboarding (CLI)](/start/wizard).
- More detail: [Vercel AI Gateway](/providers/vercel-ai-gateway)
- **Cloudflare AI Gateway**: prompts for Account ID, Gateway ID, and `CLOUDFLARE_AI_GATEWAY_API_KEY`.
- More detail: [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway)
- **MiniMax**: config is auto-written; hosted default is `MiniMax-M2.7`.
- **MiniMax**: config is auto-written; hosted default is `MiniMax-M3`.
API-key setup uses `minimax/...`, and OAuth setup uses
`minimax-portal/...`.
- More detail: [MiniMax](/providers/minimax)

View File

@@ -182,7 +182,7 @@ What you set:
More detail: [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway).
</Accordion>
<Accordion title="MiniMax">
Config is auto-written. Hosted default is `MiniMax-M2.7`; API-key setup uses
Config is auto-written. Hosted default is `MiniMax-M3`; API-key setup uses
`minimax/...`, and OAuth setup uses `minimax-portal/...`.
More detail: [MiniMax](/providers/minimax).
</Accordion>

View File

@@ -6,7 +6,7 @@ import {
registerProviderPlugin,
requireRegisteredProvider,
} from "openclaw/plugin-sdk/plugin-test-runtime";
import { describe, expect, it, vi } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import { registerMinimaxProviders } from "./provider-registration.js";
import { createMiniMaxWebSearchProvider } from "./src/minimax-web-search-provider.js";
@@ -26,6 +26,10 @@ const minimaxProviderPlugin = {
},
};
afterEach(() => {
vi.unstubAllEnvs();
});
describe("minimax provider hooks", () => {
it("declares CN provider auth aliases in the manifest", () => {
const pluginJson = JSON.parse(
@@ -91,7 +95,7 @@ describe("minimax provider hooks", () => {
hint: "Global endpoint - api.minimax.io",
choiceId: "minimax-global-api",
groupId: "minimax",
groupHint: "M2.7 (recommended)",
groupHint: "M3 (recommended)",
},
{
id: "api-cn",
@@ -99,7 +103,7 @@ describe("minimax provider hooks", () => {
hint: "CN endpoint - api.minimaxi.com",
choiceId: "minimax-cn-api",
groupId: "minimax",
groupHint: "M2.7 (recommended)",
groupHint: "M3 (recommended)",
},
]);
@@ -119,7 +123,7 @@ describe("minimax provider hooks", () => {
hint: "Global endpoint - api.minimax.io",
choiceId: "minimax-global-oauth",
groupId: "minimax",
groupHint: "M2.7 (recommended)",
groupHint: "M3 (recommended)",
},
{
id: "oauth-cn",
@@ -127,7 +131,7 @@ describe("minimax provider hooks", () => {
hint: "CN endpoint - api.minimaxi.com",
choiceId: "minimax-cn-oauth",
groupId: "minimax",
groupHint: "M2.7 (recommended)",
groupHint: "M3 (recommended)",
},
]);
});
@@ -173,7 +177,7 @@ describe("minimax provider hooks", () => {
});
});
it("keeps M2.7 on the Anthropic Messages route used by the empty-history guard", async () => {
it("lists M3 on the Anthropic Messages route used by the empty-history guard", async () => {
const { providers } = await registerProviderPlugin({
plugin: minimaxProviderPlugin,
id: "minimax",
@@ -193,13 +197,55 @@ describe("minimax provider hooks", () => {
expect(provider?.api).toBe("anthropic-messages");
expect(provider?.authHeader).toBe(true);
expect(provider?.baseUrl).toBe("https://api.minimax.io/anthropic");
const model = provider?.models.find((entry: { id?: string }) => entry.id === "MiniMax-M2.7");
expect(model?.id).toBe("MiniMax-M2.7");
expect(model?.input).toEqual(["text"]);
expect(model?.name).toBe("MiniMax M2.7");
const model = provider?.models.find((entry: { id?: string }) => entry.id === "MiniMax-M3");
expect(model?.id).toBe("MiniMax-M3");
expect(model?.input).toEqual(["text", "image"]);
expect(model?.name).toBe("MiniMax M3");
expect(model?.reasoning).toBe(true);
});
it("resolves M3 through the dynamic model hook before agent discovery", async () => {
const { providers } = await registerProviderPlugin({
plugin: minimaxProviderPlugin,
id: "minimax",
name: "MiniMax Provider",
});
const apiProvider = requireRegisteredProvider(providers, "minimax");
const model = apiProvider.resolveDynamicModel?.({
provider: "minimax",
modelId: "MiniMax-M3",
providerConfig: {},
} as never);
expect(model).toMatchObject({
provider: "minimax",
id: "MiniMax-M3",
api: "anthropic-messages",
baseUrl: "https://api.minimax.io/anthropic",
input: ["text", "image"],
contextWindow: 1_000_000,
});
});
it("keeps MINIMAX_API_HOST endpoint overrides on dynamic M3 resolution", async () => {
vi.stubEnv("MINIMAX_API_HOST", "https://api.minimaxi.com");
const { providers } = await registerProviderPlugin({
plugin: minimaxProviderPlugin,
id: "minimax",
name: "MiniMax Provider",
});
const apiProvider = requireRegisteredProvider(providers, "minimax");
const model = apiProvider.resolveDynamicModel?.({
provider: "minimax",
modelId: "MiniMax-M3",
providerConfig: {},
} as never);
expect(model?.baseUrl).toBe("https://api.minimaxi.com/anthropic");
});
it("owns fast-mode stream wrapping for MiniMax transports", async () => {
const { providers } = await registerProviderPlugin({
plugin: minimaxProviderPlugin,

View File

@@ -7,40 +7,43 @@ import {
MINIMAX_API_COST,
MINIMAX_API_HIGHSPEED_COST,
MINIMAX_HOSTED_MODEL_ID,
MINIMAX_M27_API_COST,
MINIMAX_M25_API_COST,
MINIMAX_M25_API_HIGHSPEED_COST,
MINIMAX_M3_CONTEXT_WINDOW,
} from "./model-definitions.js";
describe("minimax model definitions", () => {
it("uses M2.7 as default hosted model", () => {
expect(MINIMAX_HOSTED_MODEL_ID).toBe("MiniMax-M2.7");
it("uses M3 as default hosted model", () => {
expect(MINIMAX_HOSTED_MODEL_ID).toBe("MiniMax-M3");
});
it("uses the higher upstream MiniMax context and token defaults", () => {
it("uses the current upstream MiniMax context, token, and pricing defaults", () => {
expect(MINIMAX_M3_CONTEXT_WINDOW).toBe(1_000_000);
expect(DEFAULT_MINIMAX_CONTEXT_WINDOW).toBe(204800);
expect(DEFAULT_MINIMAX_MAX_TOKENS).toBe(131072);
expect(MINIMAX_API_COST).toEqual({
input: 0.3,
output: 1.2,
cacheRead: 0.06,
cacheWrite: 0.375,
input: 0.6,
output: 2.4,
cacheRead: 0.12,
cacheWrite: 0,
});
});
it("builds catalog model with name and reasoning from catalog for M2.7", () => {
it("builds catalog model with M3 metadata from the catalog", () => {
const model = buildMinimaxModelDefinition({
id: "MiniMax-M2.7",
id: "MiniMax-M3",
cost: MINIMAX_API_COST,
contextWindow: DEFAULT_MINIMAX_CONTEXT_WINDOW,
contextWindow: MINIMAX_M3_CONTEXT_WINDOW,
maxTokens: DEFAULT_MINIMAX_MAX_TOKENS,
});
expect(model).toEqual({
contextWindow: DEFAULT_MINIMAX_CONTEXT_WINDOW,
contextWindow: MINIMAX_M3_CONTEXT_WINDOW,
cost: MINIMAX_API_COST,
id: "MiniMax-M2.7",
input: ["text"],
id: "MiniMax-M3",
input: ["text", "image"],
maxTokens: DEFAULT_MINIMAX_MAX_TOKENS,
name: "MiniMax M2.7",
name: "MiniMax M3",
reasoning: true,
});
});
@@ -63,12 +66,12 @@ describe("minimax model definitions", () => {
});
});
it("builds API model definition with standard cost for M2.7", () => {
const model = buildMinimaxApiModelDefinition("MiniMax-M2.7");
it("builds API model definition with standard cost for M3", () => {
const model = buildMinimaxApiModelDefinition("MiniMax-M3");
expect(model.cost).toEqual(MINIMAX_API_COST);
expect(model.contextWindow).toBe(DEFAULT_MINIMAX_CONTEXT_WINDOW);
expect(model.contextWindow).toBe(MINIMAX_M3_CONTEXT_WINDOW);
expect(model.maxTokens).toBe(DEFAULT_MINIMAX_MAX_TOKENS);
expect(model.input).toEqual(["text"]);
expect(model.input).toEqual(["text", "image"]);
});
it("falls back to generated name for unknown model id", () => {
@@ -77,6 +80,13 @@ describe("minimax model definitions", () => {
expect(model.reasoning).toBe(false);
});
it("keeps M2.7 on its existing price and text-only metadata", () => {
const model = buildMinimaxApiModelDefinition("MiniMax-M2.7");
expect(model.input).toEqual(["text"]);
expect(model.cost).toEqual(MINIMAX_M27_API_COST);
expect(model.contextWindow).toBe(DEFAULT_MINIMAX_CONTEXT_WINDOW);
});
it("keeps M2.7 text-only on the Anthropic-compatible chat path", () => {
const model = buildMinimaxApiModelDefinition("MiniMax-M2.7");
expect(model.input).toEqual(["text"]);

View File

@@ -7,9 +7,16 @@ export const MINIMAX_CN_API_BASE_URL = "https://api.minimaxi.com/anthropic";
export const MINIMAX_HOSTED_MODEL_ID = MINIMAX_DEFAULT_MODEL_ID;
export const MINIMAX_HOSTED_MODEL_REF = `minimax/${MINIMAX_HOSTED_MODEL_ID}`;
export const DEFAULT_MINIMAX_CONTEXT_WINDOW = 204800;
export const MINIMAX_M3_CONTEXT_WINDOW = 1_000_000;
export const DEFAULT_MINIMAX_MAX_TOKENS = 131072;
export const MINIMAX_API_COST = {
input: 0.6,
output: 2.4,
cacheRead: 0.12,
cacheWrite: 0,
};
export const MINIMAX_M27_API_COST = {
input: 0.3,
output: 1.2,
cacheRead: 0.06,
@@ -49,6 +56,9 @@ export const MINIMAX_LM_STUDIO_COST = {
type MinimaxCatalogId = keyof typeof MINIMAX_TEXT_MODEL_CATALOG;
export function resolveMinimaxApiCost(modelId: string): ModelDefinitionConfig["cost"] {
if (modelId === "MiniMax-M2.7") {
return MINIMAX_M27_API_COST;
}
if (modelId === "MiniMax-M2.5-highspeed") {
return MINIMAX_M25_API_HIGHSPEED_COST;
}
@@ -74,7 +84,7 @@ export function buildMinimaxModelDefinition(params: {
id: params.id,
name: params.name ?? catalog?.name ?? `MiniMax ${params.id}`,
reasoning: params.reasoning ?? catalog?.reasoning ?? false,
input: ["text"],
input: [...(catalog?.input ?? ["text"])],
cost: params.cost,
contextWindow: params.contextWindow,
maxTokens: params.maxTokens,
@@ -85,7 +95,9 @@ export function buildMinimaxApiModelDefinition(modelId: string): ModelDefinition
return buildMinimaxModelDefinition({
id: modelId,
cost: resolveMinimaxApiCost(modelId),
contextWindow: DEFAULT_MINIMAX_CONTEXT_WINDOW,
contextWindow:
MINIMAX_TEXT_MODEL_CATALOG[modelId as MinimaxCatalogId]?.contextWindow ??
DEFAULT_MINIMAX_CONTEXT_WINDOW,
maxTokens: DEFAULT_MINIMAX_MAX_TOKENS,
});
}

View File

@@ -14,16 +14,16 @@ describe("minimax onboard", () => {
baseUrl: "https://api.minimax.io/anthropic",
api: "anthropic-messages",
authHeader: true,
models: [buildMinimaxApiModelDefinition("MiniMax-M2.7")],
models: [buildMinimaxApiModelDefinition("MiniMax-M3")],
});
expect(cfg.agents?.defaults?.models?.["minimax/MiniMax-M2.7"]).toEqual({
expect(cfg.agents?.defaults?.models?.["minimax/MiniMax-M3"]).toEqual({
alias: "Minimax",
});
expect(cfg.agents?.defaults?.model).toEqual({ primary: "minimax/MiniMax-M2.7" });
expect(cfg.agents?.defaults?.model).toEqual({ primary: "minimax/MiniMax-M3" });
});
it("keeps reasoning enabled for MiniMax-M2.7", () => {
const cfg = applyMinimaxApiConfig({}, "MiniMax-M2.7");
it("keeps reasoning enabled for MiniMax-M3", () => {
const cfg = applyMinimaxApiConfig({}, "MiniMax-M3");
expect(cfg.models?.providers?.minimax?.models[0]?.reasoning).toBe(true);
});
@@ -73,7 +73,7 @@ describe("minimax onboard", () => {
legacyApi: "openai-completions",
});
expect(provider?.authHeader).toBe(true);
expect(provider?.models.map((m) => m.id)).toEqual(["old-model", "MiniMax-M2.7"]);
expect(provider?.models.map((m) => m.id)).toEqual(["old-model", "MiniMax-M3"]);
});
it("preserves other providers when adding minimax", () => {

View File

@@ -9,6 +9,7 @@ import {
MINIMAX_API_BASE_URL,
MINIMAX_CN_API_BASE_URL,
} from "./model-definitions.js";
import { MINIMAX_DEFAULT_MODEL_ID } from "./provider-models.js";
type MinimaxApiProviderConfigParams = {
providerId: string;
@@ -61,7 +62,7 @@ function applyMinimaxApiConfigWithBaseUrl(
export function applyMinimaxApiProviderConfig(
cfg: OpenClawConfig,
modelId = "MiniMax-M2.7",
modelId = MINIMAX_DEFAULT_MODEL_ID,
): OpenClawConfig {
return applyMinimaxApiProviderConfigWithBaseUrl(cfg, {
providerId: "minimax",
@@ -72,7 +73,7 @@ export function applyMinimaxApiProviderConfig(
export function applyMinimaxApiConfig(
cfg: OpenClawConfig,
modelId = "MiniMax-M2.7",
modelId = MINIMAX_DEFAULT_MODEL_ID,
): OpenClawConfig {
return applyMinimaxApiConfigWithBaseUrl(cfg, {
providerId: "minimax",
@@ -83,7 +84,7 @@ export function applyMinimaxApiConfig(
export function applyMinimaxApiProviderConfigCn(
cfg: OpenClawConfig,
modelId = "MiniMax-M2.7",
modelId = MINIMAX_DEFAULT_MODEL_ID,
): OpenClawConfig {
return applyMinimaxApiProviderConfigWithBaseUrl(cfg, {
providerId: "minimax",
@@ -94,7 +95,7 @@ export function applyMinimaxApiProviderConfigCn(
export function applyMinimaxApiConfigCn(
cfg: OpenClawConfig,
modelId = "MiniMax-M2.7",
modelId = MINIMAX_DEFAULT_MODEL_ID,
): OpenClawConfig {
return applyMinimaxApiConfigWithBaseUrl(cfg, {
providerId: "minimax",

View File

@@ -34,7 +34,7 @@
"choiceHint": "Global endpoint - api.minimax.io",
"groupId": "minimax",
"groupLabel": "MiniMax",
"groupHint": "M2.7 (recommended)"
"groupHint": "M3 (recommended)"
},
{
"provider": "minimax",
@@ -45,7 +45,7 @@
"choiceHint": "Global endpoint - api.minimax.io",
"groupId": "minimax",
"groupLabel": "MiniMax",
"groupHint": "M2.7 (recommended)",
"groupHint": "M3 (recommended)",
"optionKey": "minimaxApiKey",
"cliFlag": "--minimax-api-key",
"cliOption": "--minimax-api-key <key>",
@@ -59,7 +59,7 @@
"choiceHint": "CN endpoint - api.minimaxi.com",
"groupId": "minimax",
"groupLabel": "MiniMax",
"groupHint": "M2.7 (recommended)"
"groupHint": "M3 (recommended)"
},
{
"provider": "minimax",
@@ -70,7 +70,7 @@
"choiceHint": "CN endpoint - api.minimaxi.com",
"groupId": "minimax",
"groupLabel": "MiniMax",
"groupHint": "M2.7 (recommended)",
"groupHint": "M3 (recommended)",
"optionKey": "minimaxApiKey",
"cliFlag": "--minimax-api-key",
"cliOption": "--minimax-api-key <key>",

View File

@@ -3,14 +3,13 @@ import type {
ModelProviderConfig,
} from "openclaw/plugin-sdk/provider-model-shared";
import {
DEFAULT_MINIMAX_CONTEXT_WINDOW,
DEFAULT_MINIMAX_MAX_TOKENS,
MINIMAX_API_BASE_URL,
resolveMinimaxApiCost,
} from "./model-definitions.js";
import { MINIMAX_TEXT_MODEL_CATALOG, MINIMAX_TEXT_MODEL_ORDER } from "./provider-models.js";
function resolveMinimaxCatalogBaseUrl(env: NodeJS.ProcessEnv = process.env): string {
export function resolveMinimaxCatalogBaseUrl(env: NodeJS.ProcessEnv = process.env): string {
const rawHost = env.MINIMAX_API_HOST?.trim();
if (!rawHost) {
return MINIMAX_API_BASE_URL;
@@ -34,6 +33,7 @@ function buildMinimaxModel(params: {
reasoning: boolean;
input: ModelDefinitionConfig["input"];
cost: ModelDefinitionConfig["cost"];
contextWindow: number;
}): ModelDefinitionConfig {
return {
id: params.id,
@@ -41,7 +41,7 @@ function buildMinimaxModel(params: {
reasoning: params.reasoning,
input: params.input,
cost: params.cost,
contextWindow: DEFAULT_MINIMAX_CONTEXT_WINDOW,
contextWindow: params.contextWindow,
maxTokens: DEFAULT_MINIMAX_MAX_TOKENS,
};
}
@@ -50,9 +50,11 @@ function buildMinimaxTextModel(params: {
id: string;
name: string;
reasoning: boolean;
input: ModelDefinitionConfig["input"];
cost: ModelDefinitionConfig["cost"];
contextWindow: number;
}): ModelDefinitionConfig {
return buildMinimaxModel({ ...params, input: ["text"] });
return buildMinimaxModel(params);
}
function buildMinimaxCatalog(): ModelDefinitionConfig[] {
@@ -62,7 +64,9 @@ function buildMinimaxCatalog(): ModelDefinitionConfig[] {
id,
name: model.name,
reasoning: model.reasoning,
input: [...model.input],
cost: resolveMinimaxApiCost(id),
contextWindow: model.contextWindow,
});
});
}

View File

@@ -4,7 +4,7 @@ const noopAuth = async () => ({ profiles: [] });
const wizardGroup = {
groupId: "minimax",
groupLabel: "MiniMax",
groupHint: "M2.7 (recommended)",
groupHint: "M3 (recommended)",
} as const;
export function createMinimaxProvider(): ProviderPlugin {

View File

@@ -1,20 +1,40 @@
import { matchesExactOrPrefix } from "openclaw/plugin-sdk/provider-model-shared";
export const MINIMAX_DEFAULT_MODEL_ID = "MiniMax-M2.7";
export const MINIMAX_DEFAULT_MODEL_ID = "MiniMax-M3";
export const MINIMAX_DEFAULT_MODEL_REF = `minimax/${MINIMAX_DEFAULT_MODEL_ID}`;
export const MINIMAX_TEXT_MODEL_ORDER = ["MiniMax-M2.7", "MiniMax-M2.7-highspeed"] as const;
export const MINIMAX_TEXT_MODEL_ORDER = [
"MiniMax-M3",
"MiniMax-M2.7",
"MiniMax-M2.7-highspeed",
] as const;
export const MINIMAX_TEXT_MODEL_CATALOG = {
"MiniMax-M2.7": { name: "MiniMax M2.7", reasoning: true },
"MiniMax-M2.7-highspeed": { name: "MiniMax M2.7 Highspeed", reasoning: true },
"MiniMax-M3": {
name: "MiniMax M3",
reasoning: true,
input: ["text", "image"],
contextWindow: 1_000_000,
},
"MiniMax-M2.7": {
name: "MiniMax M2.7",
reasoning: true,
input: ["text"],
contextWindow: 204800,
},
"MiniMax-M2.7-highspeed": {
name: "MiniMax M2.7 Highspeed",
reasoning: true,
input: ["text"],
contextWindow: 204800,
},
} as const;
export const MINIMAX_TEXT_MODEL_REFS = MINIMAX_TEXT_MODEL_ORDER.map(
(modelId) => `minimax/${modelId}`,
);
const MINIMAX_MODERN_MODEL_MATCHERS = ["minimax-m2.7"] as const;
const MINIMAX_MODERN_MODEL_MATCHERS = ["minimax-m3", "minimax-m2.7"] as const;
export function isMiniMaxModernModelId(modelId: string): boolean {
return matchesExactOrPrefix(modelId, MINIMAX_MODERN_MODEL_MATCHERS);

View File

@@ -5,6 +5,8 @@ import type {
ProviderAuthContext,
ProviderAuthResult,
ProviderCatalogContext,
ProviderResolveDynamicModelContext,
ProviderRuntimeModel,
} from "openclaw/plugin-sdk/plugin-entry";
import {
MINIMAX_OAUTH_MARKER,
@@ -14,14 +16,27 @@ import {
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import {
buildProviderReplayFamilyHooks,
normalizeModelCompat,
} from "openclaw/plugin-sdk/provider-model-shared";
import { MINIMAX_FAST_MODE_STREAM_HOOKS } from "openclaw/plugin-sdk/provider-stream-family";
import { fetchMinimaxUsage } from "openclaw/plugin-sdk/provider-usage";
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
import { isMiniMaxModernModelId, MINIMAX_DEFAULT_MODEL_ID } from "./api.js";
import {
isMiniMaxModernModelId,
MINIMAX_DEFAULT_MODEL_ID,
MINIMAX_TEXT_MODEL_CATALOG,
MINIMAX_TEXT_MODEL_ORDER,
} from "./api.js";
import { DEFAULT_MINIMAX_MAX_TOKENS, resolveMinimaxApiCost } from "./model-definitions.js";
import type { MiniMaxRegion } from "./oauth.js";
import { applyMinimaxApiConfig, applyMinimaxApiConfigCn } from "./onboard.js";
import { buildMinimaxPortalProvider, buildMinimaxProvider } from "./provider-catalog.js";
import {
buildMinimaxPortalProvider,
buildMinimaxProvider,
resolveMinimaxCatalogBaseUrl,
} from "./provider-catalog.js";
const API_PROVIDER_ID = "minimax";
const PORTAL_PROVIDER_ID = "minimax-portal";
@@ -38,7 +53,7 @@ const MINIMAX_USAGE_ENV_VAR_KEYS = [
const MINIMAX_WIZARD_GROUP = {
groupId: "minimax",
groupLabel: "MiniMax",
groupHint: "M2.7 (recommended)",
groupHint: "M3 (recommended)",
} as const;
const HYBRID_ANTHROPIC_OPENAI_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "hybrid-anthropic-openai",
@@ -86,6 +101,35 @@ function buildPortalProviderCatalog(params: { baseUrl: string; apiKey: string })
};
}
function findMinimaxCatalogModel(modelId: string) {
const normalizedModelId = modelId.trim().toLowerCase();
const catalogId = MINIMAX_TEXT_MODEL_ORDER.find((id) => id.toLowerCase() === normalizedModelId);
return catalogId ? { id: catalogId, model: MINIMAX_TEXT_MODEL_CATALOG[catalogId] } : undefined;
}
function resolveMinimaxDynamicModel(params: {
providerId: string;
ctx: ProviderResolveDynamicModelContext;
}): ProviderRuntimeModel | undefined {
const catalogModel = findMinimaxCatalogModel(params.ctx.modelId);
if (!catalogModel) {
return undefined;
}
return normalizeModelCompat({
id: catalogModel.id,
name: catalogModel.model.name,
provider: params.providerId,
api: "anthropic-messages",
baseUrl:
normalizeOptionalString(params.ctx.providerConfig?.baseUrl) ?? resolveMinimaxCatalogBaseUrl(),
reasoning: catalogModel.model.reasoning,
input: [...catalogModel.model.input],
cost: resolveMinimaxApiCost(catalogModel.id),
contextWindow: catalogModel.model.contextWindow,
maxTokens: DEFAULT_MINIMAX_MAX_TOKENS,
});
}
function resolveApiCatalog(ctx: ProviderCatalogContext) {
const apiKey = ctx.resolveProviderApiKey(API_PROVIDER_ID).apiKey;
if (!apiKey) {
@@ -165,6 +209,7 @@ function createOAuthHandler(region: MiniMaxRegion) {
agents: {
defaults: {
models: {
[portalModelRef("MiniMax-M3")]: { alias: "minimax-m3" },
[portalModelRef("MiniMax-M2.7")]: { alias: "minimax-m2.7" },
[portalModelRef("MiniMax-M2.7-highspeed")]: {
alias: "minimax-m2.7-highspeed",
@@ -267,6 +312,7 @@ export function buildMinimaxApiProviderPlugin(): ProviderPlugin {
return apiKey ? { token: apiKey } : null;
},
...MINIMAX_PROVIDER_HOOKS,
resolveDynamicModel: (ctx) => resolveMinimaxDynamicModel({ providerId: API_PROVIDER_ID, ctx }),
isModernModelRef: ({ modelId }) => isMiniMaxModernModelId(modelId),
fetchUsageSnapshot: async (ctx) =>
await fetchMinimaxUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn, {
@@ -292,6 +338,8 @@ export function buildMinimaxPortalProviderPlugin(): ProviderPlugin {
},
auth: [createMinimaxOAuthMethod("global"), createMinimaxOAuthMethod("cn")],
...MINIMAX_PROVIDER_HOOKS,
resolveDynamicModel: (ctx) =>
resolveMinimaxDynamicModel({ providerId: PORTAL_PROVIDER_ID, ctx }),
isModernModelRef: ({ modelId }) => isMiniMaxModernModelId(modelId),
};
}

View File

@@ -19,7 +19,7 @@ const HIGH_SIGNAL_LIVE_MODEL_PRIORITY = [
"anthropic/claude-opus-4-6",
"deepseek/deepseek-v4-flash",
"deepseek/deepseek-v4-pro",
"minimax/minimax-m2.7",
"minimax/minimax-m3",
"openai/gpt-5.5",
"openrouter/openai/gpt-5.2-chat",
"openrouter/minimax/minimax-m2.7",
@@ -28,7 +28,7 @@ const HIGH_SIGNAL_LIVE_MODEL_PRIORITY = [
"xai/grok-4.3",
"zai/glm-5.1",
"fireworks/accounts/fireworks/models/glm-5p1",
"minimax-portal/minimax-m2.7",
"minimax-portal/minimax-m3",
] as const;
const SMALL_LIVE_MODEL_PRIORITY = [

View File

@@ -13,7 +13,7 @@ const minimaxDoc = fs.readFileSync(path.join(repoRoot, "docs/providers/minimax.m
describe("MiniMax docs sync", () => {
it("keeps the live-testing guide on the current MiniMax default", () => {
expect(testingLiveDoc).toContain("MiniMax M2.7");
expect(testingLiveDoc).toContain("MiniMax M3");
expect(testingLiveDoc).toContain(MINIMAX_DEFAULT_MODEL_REF);
});

View File

@@ -8,7 +8,7 @@ import {
const MINIMAX_KEY = process.env.MINIMAX_API_KEY ?? "";
const MINIMAX_BASE_URL = process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/anthropic";
const MINIMAX_MODEL = process.env.MINIMAX_MODEL?.trim() || "MiniMax-M2.7";
const MINIMAX_MODEL = process.env.MINIMAX_MODEL?.trim() || "MiniMax-M3";
const LIVE = isLiveTestEnabled(["MINIMAX_LIVE_TEST"]);
const describeLive = LIVE && MINIMAX_KEY ? describe : describe.skip;
@@ -36,10 +36,10 @@ describeLive("minimax live", () => {
provider: "minimax",
baseUrl: MINIMAX_BASE_URL,
reasoning: false,
input: ["text"],
input: ["text", "image"],
// Pricing: placeholder values (per 1M tokens, multiplied by 1000 for display)
cost: { input: 15, output: 60, cacheRead: 2, cacheWrite: 10 },
contextWindow: 200000,
contextWindow: 1_000_000,
maxTokens: 8192,
};
const probeResult = await runMinimaxTextProbe(model, 128);

View File

@@ -524,6 +524,7 @@ describe("isHighSignalLiveModelRef", () => {
expect(
isHighSignalLiveModelRef({ provider: "openrouter", id: "minimax/minimax-m2.1:free" }),
).toBe(false);
expect(isHighSignalLiveModelRef({ provider: "minimax", id: "MiniMax-M3" })).toBe(true);
expect(isHighSignalLiveModelRef({ provider: "minimax", id: "MiniMax-M2.7" })).toBe(true);
expect(isHighSignalLiveModelRef({ provider: "openrouter", id: "minimax/minimax-m2.7" })).toBe(
true,
@@ -656,7 +657,7 @@ describe("isPrioritizedHighSignalLiveModelRef", () => {
{ provider: "anthropic", id: "claude-opus-4-6" },
{ provider: "deepseek", id: "deepseek-v4-flash" },
{ provider: "deepseek", id: "deepseek-v4-pro" },
{ provider: "minimax", id: "minimax-m2.7" },
{ provider: "minimax", id: "minimax-m3" },
{ provider: "openai", id: "gpt-5.5" },
{ provider: "openrouter", id: "openai/gpt-5.2-chat" },
{ provider: "openrouter", id: "minimax/minimax-m2.7" },
@@ -665,7 +666,7 @@ describe("isPrioritizedHighSignalLiveModelRef", () => {
{ provider: "xai", id: "grok-4.3" },
{ provider: "zai", id: "glm-5.1" },
{ provider: "fireworks", id: "accounts/fireworks/models/glm-5p1" },
{ provider: "minimax-portal", id: "minimax-m2.7" },
{ provider: "minimax-portal", id: "minimax-m3" },
]);
});
});
@@ -733,7 +734,7 @@ describe("selectHighSignalLiveItems", () => {
{ provider: "openai", id: "gpt-5.5" },
{ provider: "deepseek", id: "deepseek-v4-flash" },
{ provider: "deepseek", id: "deepseek-v4-pro" },
{ provider: "minimax", id: "minimax-m2.7" },
{ provider: "minimax", id: "minimax-m3" },
];
expect(
@@ -746,7 +747,7 @@ describe("selectHighSignalLiveItems", () => {
).toEqual([
{ provider: "deepseek", id: "deepseek-v4-flash" },
{ provider: "deepseek", id: "deepseek-v4-pro" },
{ provider: "minimax", id: "minimax-m2.7" },
{ provider: "minimax", id: "minimax-m3" },
]);
});

View File

@@ -2,6 +2,15 @@ import { describe, expect, it } from "vitest";
function buildMinimaxCatalog() {
return [
{
id: "MiniMax-M3",
cost: {
input: 0.6,
output: 2.4,
cacheRead: 0.12,
cacheWrite: 0,
},
},
{
id: "MiniMax-M2.7",
cost: {
@@ -30,10 +39,12 @@ describe("minimax provider catalog", () => {
"minimax-portal": { models: buildMinimaxCatalog() },
};
expect(providers?.minimax?.models?.map((model) => model.id)).toEqual([
"MiniMax-M3",
"MiniMax-M2.7",
"MiniMax-M2.7-highspeed",
]);
expect(providers?.["minimax-portal"]?.models?.map((model) => model.id)).toEqual([
"MiniMax-M3",
"MiniMax-M2.7",
"MiniMax-M2.7-highspeed",
]);

View File

@@ -717,6 +717,7 @@ export function describeMinimaxProviderDiscoveryContract(
apiKey: "minimax-key",
});
const ids = providerModelIds(provider);
expect(ids).toContain("MiniMax-M3");
expect(ids).toContain("MiniMax-M2.7");
expect(ids).toContain("MiniMax-M2.7-highspeed");
});

View File

@@ -255,9 +255,7 @@ describe("package acceptance workflow", () => {
expect(crabboxConfig.jobs?.changed?.command).toContain(
"commit -q --no-gpg-sign -m remote-check-tree",
);
expect(crabboxConfig.jobs?.changed?.command).toContain(
"env CI=1 corepack pnpm check --timed",
);
expect(crabboxConfig.jobs?.changed?.command).toContain("env CI=1 corepack pnpm check --timed");
expect(crabboxConfig.ssh?.user).toBe("crabbox");
expect(crabboxConfig.ssh?.port).toBe("22");
});
@@ -655,7 +653,7 @@ describe("package artifact reuse", () => {
"OPENCLAW_LIVE_GATEWAY_MODELS=google/gemini-3.1-pro-preview node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles",
);
expect(workflow).toContain(
"OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M2.7,minimax-portal/MiniMax-M2.7 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2",
"OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M3,minimax-portal/MiniMax-M3 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2",
);
expect(workflow).toMatch(
/suite_id: native-live-src-gateway-profiles-fireworks[\s\S]*?timeout_minutes: 30[\s\S]*?advisory: true/u,