From 4e8d69076d45eba9ce63c84d19d93bae84f1e6de Mon Sep 17 00:00:00 2001 From: can1357 Date: Sat, 9 May 2026 04:07:39 +0200 Subject: [PATCH] fix(ai): minimax thinking MiniMax descriptors carried thinkingFormat: "zai", which made openai-completions emit thinking: { type: "enabled" } in plan mode. MiniMax's OpenAI-compatible endpoint rejects that field with `400 invalid params, invalid chat setting (2013)`. Drop thinkingFormat from both minimax-coding-plan descriptors and add supportsReasoningEffort: false (MiniMax also ignores reasoning_effort). Normalize bundled minimax-code/minimax-code-cn entries inside applyGeneratedModelPolicy so a regenerated models.json cannot reintroduce the bad flag. Fixes #955 --- packages/ai/src/model-thinking.ts | 10 ++++ packages/ai/src/models.json | 32 ++++++------ .../ai/src/provider-models/openai-compat.ts | 4 +- packages/ai/test/issue-955-repro.test.ts | 51 +++++++++++++++++++ 4 files changed, 79 insertions(+), 18 deletions(-) create mode 100644 packages/ai/test/issue-955-repro.test.ts diff --git a/packages/ai/src/model-thinking.ts b/packages/ai/src/model-thinking.ts index d39bff11a..3264551d4 100644 --- a/packages/ai/src/model-thinking.ts +++ b/packages/ai/src/model-thinking.ts @@ -314,6 +314,16 @@ function applyGeneratedModelPolicy(model: ApiModel): void { model.maxTokens = copilotLimits.maxTokens; } + if (model.api === "openai-completions" && (model.provider === "minimax-code" || model.provider === "minimax-code-cn")) { + model.compat = { + ...model.compat, + supportsStore: false, + supportsDeveloperRole: false, + supportsReasoningEffort: false, + reasoningContentField: "reasoning_content", + }; + delete model.compat.thinkingFormat; + } const parsedModel = parseKnownModel(model.id); const applyPatchToolType = inferGeneratedApplyPatchToolType(model, parsedModel); if (applyPatchToolType) { diff --git a/packages/ai/src/models.json b/packages/ai/src/models.json index f34c28e05..51d45d316 100644 --- a/packages/ai/src/models.json +++ b/packages/ai/src/models.json @@ -18451,7 +18451,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { @@ -18481,7 +18481,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { @@ -18508,7 +18508,7 @@ }, "compat": { "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "contextWindow": 1000000, @@ -18540,7 +18540,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { @@ -18570,7 +18570,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { @@ -18597,7 +18597,7 @@ }, "compat": { "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "contextWindow": 204800, @@ -18629,7 +18629,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { @@ -18659,7 +18659,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { @@ -18691,7 +18691,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { @@ -18721,7 +18721,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { @@ -18748,7 +18748,7 @@ }, "compat": { "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "contextWindow": 1000000, @@ -18780,7 +18780,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { @@ -18810,7 +18810,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { @@ -18837,7 +18837,7 @@ }, "compat": { "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "contextWindow": 204800, @@ -18869,7 +18869,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { @@ -18899,7 +18899,7 @@ "compat": { "supportsStore": false, "supportsDeveloperRole": false, - "thinkingFormat": "zai", + "supportsReasoningEffort": false, "reasoningContentField": "reasoning_content" }, "thinking": { diff --git a/packages/ai/src/provider-models/openai-compat.ts b/packages/ai/src/provider-models/openai-compat.ts index c22f9b10a..b56e3265c 100644 --- a/packages/ai/src/provider-models/openai-compat.ts +++ b/packages/ai/src/provider-models/openai-compat.ts @@ -2112,7 +2112,7 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_CODING_PLANS: readonly ModelsDevProviderDe compat: { supportsStore: false, supportsDeveloperRole: false, - thinkingFormat: "zai", + supportsReasoningEffort: false, reasoningContentField: "reasoning_content", }, }), @@ -2120,7 +2120,7 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_CODING_PLANS: readonly ModelsDevProviderDe compat: { supportsStore: false, supportsDeveloperRole: false, - thinkingFormat: "zai", + supportsReasoningEffort: false, reasoningContentField: "reasoning_content", }, }), diff --git a/packages/ai/test/issue-955-repro.test.ts b/packages/ai/test/issue-955-repro.test.ts new file mode 100644 index 000000000..cd59363b3 --- /dev/null +++ b/packages/ai/test/issue-955-repro.test.ts @@ -0,0 +1,51 @@ +import { afterEach, describe, expect, it } from "bun:test"; +import { getBundledModel } from "@oh-my-pi/pi-ai/models"; +import { streamOpenAICompletions } from "@oh-my-pi/pi-ai/providers/openai-completions"; +import type { Context, Model } from "@oh-my-pi/pi-ai/types"; + +const originalFetch = global.fetch; + +afterEach(() => { + global.fetch = originalFetch; +}); + +const context: Context = { + systemPrompt: ["stable instructions", "cacheable policy"], + messages: [{ role: "user", content: "hello", timestamp: Date.now() }], +}; + +function abortedSignal(): AbortSignal { + const controller = new AbortController(); + controller.abort(); + return controller.signal; +} + +async function capturePayload(model: Model<"openai-completions">): Promise> { + const { promise, resolve } = Promise.withResolvers>(); + streamOpenAICompletions(model, context, { + apiKey: "test-key", + signal: abortedSignal(), + reasoning: "high", + onPayload: payload => resolve(payload as Record), + }); + return await promise; +} + +describe("issue #955 — MiniMax coding-plan plan mode payload", () => { + it.each([ + ["minimax-code", "MiniMax-M2.5"], + ["minimax-code-cn", "MiniMax-M2.5"], + ] as const)("omits unsupported thinking fields for %s/%s", async (provider, modelId) => { + const model = getBundledModel(provider, modelId) as Model<"openai-completions">; + const body = await capturePayload(model); + + expect(body.model).toBe(modelId); + expect(body.messages).toEqual([ + { role: "system", content: "stable instructions\n\ncacheable policy" }, + { role: "user", content: "hello" }, + ]); + expect(body.thinking).toBeUndefined(); + expect(body.reasoning).toBeUndefined(); + expect(body.reasoning_effort).toBeUndefined(); + }); +});