diff --git a/open-sse/utils/reasoningContentInjector.js b/open-sse/utils/reasoningContentInjector.js index 5d9f238f..a6829b72 100644 --- a/open-sse/utils/reasoningContentInjector.js +++ b/open-sse/utils/reasoningContentInjector.js @@ -1,4 +1,4 @@ -// Some thinking-mode providers (DeepSeek, Kimi, ...) require reasoning_content +// Some thinking-mode providers (DeepSeek, Kimi, MiniMax, ...) require reasoning_content // to be echoed back on assistant messages. Clients in OpenAI format don't send it, // so we inject a non-empty placeholder to satisfy upstream validation. @@ -6,7 +6,9 @@ const PLACEHOLDER = " "; // Provider-level rules: keyed by executor.provider const PROVIDER_RULES = { - deepseek: { scope: "all" } + deepseek: { scope: "all" }, + minimax: { scope: "all" }, + "minimax-cn": { scope: "all" } }; // Model-level rules: matched by predicate against model id diff --git a/tests/unit/reasoningContentInjector.test.js b/tests/unit/reasoningContentInjector.test.js index 56399ac5..b0e360ca 100644 --- a/tests/unit/reasoningContentInjector.test.js +++ b/tests/unit/reasoningContentInjector.test.js @@ -84,6 +84,69 @@ describe("injectReasoningContent — DeepSeek thinking round-trip", () => { }); }); +describe("injectReasoningContent — MiniMax thinking round-trip", () => { + const minimaxAssistantMsg = { role: "assistant", content: "here is a response", reasoning_content: "" }; + const minimaxAssistantWithToolCall = { + role: "assistant", + content: "", + tool_calls: [{ id: "call_x", type: "function", function: { name: "get_weather", arguments: "{}" } }], + reasoning_content: "", + }; + + it("injects reasoning_content on a minimax assistant message that lacks it", () => { + const out = injectReasoningContent({ + provider: "minimax", + model: "MiniMax-M2.7", + body: bodyWith([{ role: "user", content: "hi" }, minimaxAssistantMsg]), + }); + const assistant = out.messages.find((m) => m.role === "assistant"); + expect(typeof assistant.reasoning_content).toBe("string"); + expect(assistant.reasoning_content.length).toBeGreaterThan(0); + }); + + it("injects reasoning_content on minimax assistant message with tool_calls but no reasoning_content", () => { + const out = injectReasoningContent({ + provider: "minimax", + model: "MiniMax-M2.7", + body: bodyWith([{ role: "user", content: "hi" }, minimaxAssistantWithToolCall]), + }); + const assistant = out.messages.find((m) => m.role === "assistant"); + expect(typeof assistant.reasoning_content).toBe("string"); + expect(assistant.reasoning_content.length).toBeGreaterThan(0); + }); + + it("applies provider-level rule for provider 'minimax-cn' (scope all)", () => { + const out = injectReasoningContent({ + provider: "minimax-cn", + model: "MiniMax-M2.5", + body: bodyWith([{ role: "assistant", content: "answer" }]), + }); + expect(out.messages[0].reasoning_content).toBeDefined(); + }); + + it("preserves an existing reasoning_content on minimax instead of overwriting", () => { + const original = "MiniMax chain of thought reasoning"; + const out = injectReasoningContent({ + provider: "minimax", + model: "MiniMax-M2.7", + body: bodyWith([{ ...minimaxAssistantMsg, reasoning_content: original }]), + }); + expect(out.messages[0].reasoning_content).toBe(original); + }); + + it("DefaultExecutor transformRequest runs the injector for minimax", () => { + const { DefaultExecutor } = require("../../open-sse/executors/default.js"); + const executor = new DefaultExecutor("minimax"); + const out = executor.transformRequest( + "MiniMax-M2.7", + bodyWith([{ role: "user", content: "hi" }, minimaxAssistantMsg]), + ); + const assistant = out.messages.find((m) => m.role === "assistant"); + expect(typeof assistant.reasoning_content).toBe("string"); + expect(assistant.reasoning_content.length).toBeGreaterThan(0); + }); +}); + describe("OpenCodeExecutor — issue #1543 regression", () => { it("runs the injector so deepseek-v4-flash-free round-trips reasoning_content", () => { const executor = new OpenCodeExecutor();