From 8f0f0417d4d5f2dad6b5a767285fb4b00e4988f8 Mon Sep 17 00:00:00 2001 From: octo-patch <266937838+octo-patch@users.noreply.github.com> Date: Mon, 19 Oct 2026 23:12:32 +0800 Subject: [PATCH] feat(minimax): add Flash preset with protocol-specific thinking --- apps/app/src/app/defaults/models.ts | 2 +- .../domains/settings/minimax-config.tsx | 4 +- .../domains/settings/minimax-provider.ts | 48 +++++++--- apps/app/tests/minimax-provider.test.ts | 89 +++++++++---------- 4 files changed, 84 insertions(+), 59 deletions(-) diff --git a/apps/app/src/app/defaults/models.ts b/apps/app/src/app/defaults/models.ts index 2083bb56c..26c386aad 100644 --- a/apps/app/src/app/defaults/models.ts +++ b/apps/app/src/app/defaults/models.ts @@ -18,8 +18,8 @@ export const RECOMMENDED_MODEL_PATTERNS: string[] = [ "gpt-5.5", "kimi-k2.6", "glm-5.2", + "minimax-m3.1-flash", "minimax-m3", - "minimax-m2.7", ]; /** diff --git a/apps/app/src/react-app/domains/settings/minimax-config.tsx b/apps/app/src/react-app/domains/settings/minimax-config.tsx index e51bfaf4e..531e552e9 100644 --- a/apps/app/src/react-app/domains/settings/minimax-config.tsx +++ b/apps/app/src/react-app/domains/settings/minimax-config.tsx @@ -63,7 +63,7 @@ function formatContextWindow(value: number) { export function MiniMaxConfig(props: MiniMaxConfigProps) { const [apiKey, setApiKey] = useState(""); const [endpointId, setEndpointId] = useState("global-openai"); - const [defaultModelId, setDefaultModelId] = useState(MINIMAX_PROVIDER.models[0].id); + const [defaultModelId, setDefaultModelId] = useState(MINIMAX_PROVIDER.models[0].id); const [setDefault, setSetDefault] = useState(true); const [localError, setLocalError] = useState(null); @@ -192,7 +192,9 @@ export function MiniMaxConfig(props: MiniMaxConfigProps) {
Context: {formatContextWindow(model.contextWindow)} - Input: {model.inputModalities.join(", ")}
+
Max output: {formatContextWindow(model.maxOutputTokens)}
Thinking: {model.thinking.join(", ")}
+
{model.pricingNote}
))} diff --git a/apps/app/src/react-app/domains/settings/minimax-provider.ts b/apps/app/src/react-app/domains/settings/minimax-provider.ts index a73e24957..58fd0eebe 100644 --- a/apps/app/src/react-app/domains/settings/minimax-provider.ts +++ b/apps/app/src/react-app/domains/settings/minimax-provider.ts @@ -7,6 +7,8 @@ export type MiniMaxEndpointId = type MiniMaxModelPreset = { id: string; contextWindow: number; + maxOutputTokens: number; + pricingNote: string; pricingUsdPerMillionTokens: { input: number; output: number; @@ -31,28 +33,33 @@ export const MINIMAX_PROVIDER = { name: "MiniMax", models: [ { - id: "MiniMax-M3", + id: "MiniMax-M3.1-Flash", contextWindow: 1_000_000, + maxOutputTokens: 524_288, + pricingNote: "USD per million tokens; cache-write pricing is not published.", pricingUsdPerMillionTokens: { - input: 0.6, - output: 2.4, - cacheRead: 0.12, + input: 0.15, + output: 0.6, + cacheRead: 0.015, cacheWrite: null, }, inputModalities: ["text", "image", "video"], - thinking: ["adaptive", "disabled"], + thinking: ["adaptive"], }, { - id: "MiniMax-M2.7", - contextWindow: 204_800, + id: "MiniMax-M3", + contextWindow: 1_000_000, + maxOutputTokens: 524_288, + // OpenCode's flat cost schema cannot represent the request-size pricing tiers. + pricingNote: "Standard pricing for input <=512k; above 512k: $0.60 input, $2.40 output, $0.12 cache read per million tokens. Priority costs 1.5x. Cache-write pricing is not published.", pricingUsdPerMillionTokens: { input: 0.3, output: 1.2, cacheRead: 0.06, - cacheWrite: 0.375, + cacheWrite: null, }, - inputModalities: ["text"], - thinking: ["always_on"], + inputModalities: ["text", "image", "video"], + thinking: ["adaptive", "disabled"], }, ] satisfies readonly MiniMaxModelPreset[], } as const; @@ -102,11 +109,32 @@ export function buildMiniMaxProviderConfig(endpointId: MiniMaxEndpointId) { const endpoint = getMiniMaxEndpoint(endpointId); const models = Object.fromEntries( MINIMAX_PROVIDER.models.map((model) => { + const flash = model.id === "MiniMax-M3.1-Flash"; + const effortOptions = (effort: string) => endpoint.protocol === "anthropic" + ? { thinking: { type: "adaptive" }, effort } + : { thinking: { type: "adaptive" }, reasoningEffort: effort }; const modelConfig = { id: model.id, name: model.id, attachment: model.inputModalities.some((modality) => modality !== "text"), reasoning: model.thinking.length > 0, + limit: { context: model.contextWindow, output: model.maxOutputTokens }, + options: flash + ? effortOptions("max") + : { thinking: { type: endpoint.protocol === "anthropic" ? "disabled" : "adaptive" } }, + variants: flash + ? { + // Override OpenCode's inherited M3 variants: Flash cannot disable thinking. + none: { disabled: true }, + thinking: { disabled: true }, + ...Object.fromEntries(["low", "medium", "high", "xhigh", "max"].map( + (effort) => [effort, effortOptions(effort)], + )), + } + : { + none: { thinking: { type: "disabled" } }, + thinking: { thinking: { type: "adaptive" } }, + }, cost: { input: model.pricingUsdPerMillionTokens.input, output: model.pricingUsdPerMillionTokens.output, diff --git a/apps/app/tests/minimax-provider.test.ts b/apps/app/tests/minimax-provider.test.ts index 21660a3df..94847230e 100644 --- a/apps/app/tests/minimax-provider.test.ts +++ b/apps/app/tests/minimax-provider.test.ts @@ -45,54 +45,48 @@ describe("MiniMax provider preset", () => { ]); }); - test("keeps both current model fact sets without inventing an output limit", () => { - expect(MINIMAX_PROVIDER.models).toEqual([ - { - id: "MiniMax-M3", - contextWindow: 1_000_000, - pricingUsdPerMillionTokens: { - input: 0.6, - output: 2.4, - cacheRead: 0.12, - cacheWrite: null, - }, - inputModalities: ["text", "image", "video"], - thinking: ["adaptive", "disabled"], - }, - { - id: "MiniMax-M2.7", - contextWindow: 204_800, - pricingUsdPerMillionTokens: { - input: 0.3, - output: 1.2, - cacheRead: 0.06, - cacheWrite: 0.375, - }, - inputModalities: ["text"], - thinking: ["always_on"], - }, + test("offers Flash first and carries verified limits and baseline prices", () => { + expect(MINIMAX_PROVIDER.models.map((model) => model.id)).toEqual([ + "MiniMax-M3.1-Flash", "MiniMax-M3", ]); - const models = buildMiniMaxProviderConfig("global-openai").models; - expect(models?.["MiniMax-M3"]).toMatchObject({ - attachment: true, - reasoning: true, - cost: { input: 0.6, output: 2.4, cache_read: 0.12 }, - modalities: { input: ["text", "image", "video"] }, - }); - expect(models?.["MiniMax-M3"]?.cost).not.toHaveProperty("cache_write"); - expect(models?.["MiniMax-M3"]).not.toHaveProperty("limit"); - expect(models?.["MiniMax-M2.7"]).toMatchObject({ - attachment: false, - reasoning: true, - cost: { - input: 0.3, - output: 1.2, - cache_read: 0.06, - cache_write: 0.375, - }, - modalities: { input: ["text"] }, - }); + expect(Object.keys(models)).toEqual(["MiniMax-M3.1-Flash", "MiniMax-M3"]); + for (const model of Object.values(models)) { + expect(model).toMatchObject({ + attachment: true, + reasoning: true, + limit: { context: 1_000_000, output: 524_288 }, + modalities: { input: ["text", "image", "video"] }, + }); + expect(model.cost).not.toHaveProperty("cache_write"); + } + expect(models["MiniMax-M3.1-Flash"].cost).toEqual({ input: 0.15, output: 0.6, cache_read: 0.015 }); + expect(models["MiniMax-M3"].cost).toEqual({ input: 0.3, output: 1.2, cache_read: 0.06 }); + expect(MINIMAX_PROVIDER.models[1].pricingNote).toContain("above 512k"); + expect(MINIMAX_PROVIDER.models[1].pricingNote).toContain("1.5x"); + }); + + test("maps effort by protocol and suppresses inherited Flash off variants", () => { + for (const endpoint of MINIMAX_ENDPOINTS) { + const models = buildMiniMaxProviderConfig(endpoint.id).models; + const flash = models["MiniMax-M3.1-Flash"]; + const effortField = endpoint.protocol === "anthropic" ? "effort" : "reasoningEffort"; + expect(flash.options).toEqual({ thinking: { type: "adaptive" }, [effortField]: "max" }); + expect(flash.variants.none).toEqual({ disabled: true }); + expect(flash.variants.thinking).toEqual({ disabled: true }); + for (const effort of ["low", "medium", "high", "xhigh", "max"]) { + expect(flash.variants[effort]).toEqual({ thinking: { type: "adaptive" }, [effortField]: effort }); + } + expect(models["MiniMax-M3"].options).toEqual({ + thinking: { type: endpoint.protocol === "anthropic" ? "disabled" : "adaptive" }, + }); + expect(models["MiniMax-M3"].variants).toEqual({ + none: { thinking: { type: "disabled" } }, + thinking: { thinking: { type: "adaptive" } }, + }); + expect(JSON.stringify(models)).not.toContain("service_tier"); + expect(JSON.stringify(models)).not.toContain("reasoning_split"); + } }); test("builds the selected OpenCode provider package and API endpoint", () => { @@ -126,7 +120,8 @@ describe("MiniMax provider preset", () => { ref: "ipollowork.minimax.settings", location: "settings-detail", }); + expect(isRecommendedModel("MiniMax-M3.1-Flash")).toBe(true); expect(isRecommendedModel("MiniMax-M3")).toBe(true); - expect(isRecommendedModel("MiniMax-M2.7")).toBe(true); + expect(isRecommendedModel("MiniMax-M2.7")).toBe(false); }); });