Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/app/src/app/defaults/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
];

/**
Expand Down
4 changes: 3 additions & 1 deletion apps/app/src/react-app/domains/settings/minimax-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function formatContextWindow(value: number) {
export function MiniMaxConfig(props: MiniMaxConfigProps) {
const [apiKey, setApiKey] = useState("");
const [endpointId, setEndpointId] = useState<MiniMaxEndpointId>("global-openai");
const [defaultModelId, setDefaultModelId] = useState(MINIMAX_PROVIDER.models[0].id);
const [defaultModelId, setDefaultModelId] = useState<string>(MINIMAX_PROVIDER.models[0].id);
const [setDefault, setSetDefault] = useState(true);
const [localError, setLocalError] = useState<string | null>(null);

Expand Down Expand Up @@ -192,7 +192,9 @@ export function MiniMaxConfig(props: MiniMaxConfigProps) {
<div className="mt-1 text-muted-foreground">
Context: {formatContextWindow(model.contextWindow)} - Input: {model.inputModalities.join(", ")}
</div>
<div className="mt-1 text-muted-foreground">Max output: {formatContextWindow(model.maxOutputTokens)}</div>
<div className="mt-1 text-muted-foreground">Thinking: {model.thinking.join(", ")}</div>
<div className="mt-1 text-muted-foreground">{model.pricingNote}</div>
</div>
))}
</div>
Expand Down
48 changes: 38 additions & 10 deletions apps/app/src/react-app/domains/settings/minimax-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export type MiniMaxEndpointId =
type MiniMaxModelPreset = {
id: string;
contextWindow: number;
maxOutputTokens: number;
pricingNote: string;
pricingUsdPerMillionTokens: {
input: number;
output: number;
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
89 changes: 42 additions & 47 deletions apps/app/tests/minimax-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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);
});
});
Loading