mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat(minimax): move environment_details to system message for thinking models (#10284)
This commit is contained in:
parent
bd78a63844
commit
e7c1851a8b
4 changed files with 491 additions and 4 deletions
|
|
@ -15,6 +15,8 @@ export const minimaxModels = {
|
|||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
defaultToolProtocol: "native",
|
||||
includedTools: ["search_and_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
preserveReasoning: true,
|
||||
inputPrice: 0.3,
|
||||
outputPrice: 1.2,
|
||||
|
|
@ -30,6 +32,8 @@ export const minimaxModels = {
|
|||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
defaultToolProtocol: "native",
|
||||
includedTools: ["search_and_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
preserveReasoning: true,
|
||||
inputPrice: 0.3,
|
||||
outputPrice: 1.2,
|
||||
|
|
@ -38,6 +42,23 @@ export const minimaxModels = {
|
|||
description:
|
||||
"MiniMax M2 Stable (High Concurrency, Commercial Use), a model born for Agents and code, featuring Top-tier Coding Capabilities, Powerful Agentic Performance, and Ultimate Cost-Effectiveness & Speed.",
|
||||
},
|
||||
"MiniMax-M2.1": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 192_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
defaultToolProtocol: "native",
|
||||
includedTools: ["search_and_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
preserveReasoning: true,
|
||||
inputPrice: 0.3,
|
||||
outputPrice: 1.2,
|
||||
cacheWritesPrice: 0.375,
|
||||
cacheReadsPrice: 0.03,
|
||||
description:
|
||||
"MiniMax M2.1 builds on M2 with improved overall performance for agentic coding tasks and significantly faster response times.",
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
export const minimaxDefaultModelInfo: ModelInfo = minimaxModels[minimaxDefaultModelId]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type { ApiHandlerOptions } from "../../shared/api"
|
|||
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { getModelParams } from "../transform/model-params"
|
||||
import { mergeEnvironmentDetailsForMiniMax } from "../transform/minimax-format"
|
||||
|
||||
import { BaseProvider } from "./base-provider"
|
||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
|
|
@ -87,15 +88,26 @@ export class MiniMaxHandler extends BaseProvider implements SingleCompletionHand
|
|||
// MiniMax M2 models support prompt caching
|
||||
const supportsPromptCache = info.supportsPromptCache ?? false
|
||||
|
||||
// Merge environment_details from messages that follow tool_result blocks
|
||||
// into the tool_result content. This preserves reasoning continuity for
|
||||
// thinking models by preventing user messages from interrupting the
|
||||
// reasoning context after tool use (similar to r1-format's mergeToolResultText).
|
||||
const processedMessages = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
// Build the system blocks array
|
||||
const systemBlocks: Anthropic.Messages.TextBlockParam[] = [
|
||||
supportsPromptCache
|
||||
? { text: systemPrompt, type: "text", cache_control: cacheControl }
|
||||
: { text: systemPrompt, type: "text" },
|
||||
]
|
||||
|
||||
// Prepare request parameters
|
||||
const requestParams: Anthropic.Messages.MessageCreateParams = {
|
||||
model: modelId,
|
||||
max_tokens: maxTokens ?? 16_384,
|
||||
temperature: temperature ?? 1.0,
|
||||
system: supportsPromptCache
|
||||
? [{ text: systemPrompt, type: "text", cache_control: cacheControl }]
|
||||
: [{ text: systemPrompt, type: "text" }],
|
||||
messages: supportsPromptCache ? this.addCacheControl(messages, cacheControl) : messages,
|
||||
system: systemBlocks,
|
||||
messages: supportsPromptCache ? this.addCacheControl(processedMessages, cacheControl) : processedMessages,
|
||||
stream: true,
|
||||
}
|
||||
|
||||
|
|
|
|||
336
src/api/transform/__tests__/minimax-format.spec.ts
Normal file
336
src/api/transform/__tests__/minimax-format.spec.ts
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
// npx vitest run api/transform/__tests__/minimax-format.spec.ts
|
||||
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
import { mergeEnvironmentDetailsForMiniMax } from "../minimax-format"
|
||||
|
||||
describe("mergeEnvironmentDetailsForMiniMax", () => {
|
||||
it("should pass through simple text messages unchanged", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello",
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "Hi there!",
|
||||
},
|
||||
]
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result).toEqual(messages)
|
||||
})
|
||||
|
||||
it("should pass through user messages with only tool_result blocks unchanged", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-123",
|
||||
content: "Tool result content",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result).toEqual(messages)
|
||||
})
|
||||
|
||||
it("should pass through user messages with only text blocks unchanged", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Some user message",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result).toEqual(messages)
|
||||
})
|
||||
|
||||
it("should merge text content into last tool_result when both tool_result AND text blocks exist", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-123",
|
||||
content: "Tool result content",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "<environment_details>\nCurrent Time: 2024-01-01\n</environment_details>",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
// The message should have only tool_result with merged content
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].role).toBe("user")
|
||||
const content = result[0].content as Anthropic.Messages.ToolResultBlockParam[]
|
||||
expect(content).toHaveLength(1)
|
||||
expect(content[0].type).toBe("tool_result")
|
||||
expect(content[0].tool_use_id).toBe("tool-123")
|
||||
expect(content[0].content).toBe(
|
||||
"Tool result content\n\n<environment_details>\nCurrent Time: 2024-01-01\n</environment_details>",
|
||||
)
|
||||
})
|
||||
|
||||
it("should merge multiple text blocks into last tool_result", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-123",
|
||||
content: "Tool result 1",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "First text block",
|
||||
},
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-456",
|
||||
content: "Tool result 2",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "Second text block",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
// The message should have only tool_result blocks, with text merged into the last one
|
||||
expect(result).toHaveLength(1)
|
||||
const content = result[0].content as Anthropic.Messages.ToolResultBlockParam[]
|
||||
expect(content).toHaveLength(2)
|
||||
expect(content[0].type).toBe("tool_result")
|
||||
expect(content[0].content).toBe("Tool result 1") // First one unchanged
|
||||
expect(content[1].type).toBe("tool_result")
|
||||
expect(content[1].content).toBe("Tool result 2\n\nFirst text block\n\nSecond text block") // Second has merged text
|
||||
})
|
||||
|
||||
it("should NOT merge text when images are present (cannot move images to tool_result)", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-123",
|
||||
content: "Tool result content",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "Some text",
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/png",
|
||||
data: "base64data",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
// Message should be unchanged since images are present
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result).toEqual(messages)
|
||||
})
|
||||
|
||||
it("should pass through assistant messages unchanged", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "I will help you with that.",
|
||||
},
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "tool-123",
|
||||
name: "read_file",
|
||||
input: { path: "test.ts" },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result).toEqual(messages)
|
||||
})
|
||||
|
||||
it("should handle mixed conversation with merging only for eligible messages", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Create a file",
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "I'll create the file.",
|
||||
},
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "tool-123",
|
||||
name: "write_file",
|
||||
input: { path: "test.ts", content: "// test" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-123",
|
||||
content: "File created successfully",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "<environment_details>\nCurrent Time: 2024-01-01\n</environment_details>",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "The file has been created.",
|
||||
},
|
||||
]
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
// Should have all 4 messages
|
||||
expect(result).toHaveLength(4)
|
||||
|
||||
// First user message unchanged (simple string)
|
||||
expect(result[0]).toEqual(messages[0])
|
||||
|
||||
// Assistant message unchanged
|
||||
expect(result[1]).toEqual(messages[1])
|
||||
|
||||
// Third message should have tool_result with merged environment_details
|
||||
const thirdMessage = result[2].content as Anthropic.Messages.ToolResultBlockParam[]
|
||||
expect(thirdMessage).toHaveLength(1)
|
||||
expect(thirdMessage[0].type).toBe("tool_result")
|
||||
expect(thirdMessage[0].content).toContain("File created successfully")
|
||||
expect(thirdMessage[0].content).toContain("environment_details")
|
||||
|
||||
// Fourth message unchanged
|
||||
expect(result[3]).toEqual(messages[3])
|
||||
})
|
||||
|
||||
it("should handle string content in user messages", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "Just a string message",
|
||||
},
|
||||
]
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result).toEqual(messages)
|
||||
})
|
||||
|
||||
it("should handle empty messages array", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = []
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
expect(result).toHaveLength(0)
|
||||
})
|
||||
|
||||
it("should handle tool_result with array content", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-123",
|
||||
content: [
|
||||
{ type: "text", text: "Part 1" },
|
||||
{ type: "text", text: "Part 2" },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "<environment_details>Context</environment_details>",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
const content = result[0].content as Anthropic.Messages.ToolResultBlockParam[]
|
||||
expect(content).toHaveLength(1)
|
||||
expect(content[0].type).toBe("tool_result")
|
||||
// Array content should be concatenated and then merged with text
|
||||
expect(content[0].content).toBe("Part 1\nPart 2\n\n<environment_details>Context</environment_details>")
|
||||
})
|
||||
|
||||
it("should handle tool_result with empty content", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool-123",
|
||||
content: "",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "<environment_details>Context</environment_details>",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = mergeEnvironmentDetailsForMiniMax(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
const content = result[0].content as Anthropic.Messages.ToolResultBlockParam[]
|
||||
expect(content).toHaveLength(1)
|
||||
expect(content[0].type).toBe("tool_result")
|
||||
expect(content[0].content).toBe("<environment_details>Context</environment_details>")
|
||||
})
|
||||
})
|
||||
118
src/api/transform/minimax-format.ts
Normal file
118
src/api/transform/minimax-format.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
type ContentBlock = Anthropic.Messages.ContentBlockParam
|
||||
|
||||
/**
|
||||
* Merges text content (like environment_details) that follows tool_result blocks
|
||||
* into the last tool_result's content. This preserves reasoning continuity for
|
||||
* thinking models by avoiding separate user messages after tool results.
|
||||
*
|
||||
* Key behavior:
|
||||
* - User messages with ONLY tool_result blocks: keep as-is
|
||||
* - User messages with ONLY text/image: keep as-is
|
||||
* - User messages with tool_result blocks AND text blocks: merge the text blocks
|
||||
* into the last tool_result's content
|
||||
*
|
||||
* @param messages Array of Anthropic messages
|
||||
* @returns Modified messages with text merged into tool_result content
|
||||
*/
|
||||
export function mergeEnvironmentDetailsForMiniMax(
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
): Anthropic.Messages.MessageParam[] {
|
||||
const result: Anthropic.Messages.MessageParam[] = []
|
||||
|
||||
for (const message of messages) {
|
||||
if (message.role === "user") {
|
||||
if (typeof message.content === "string") {
|
||||
// Simple string content - keep as-is
|
||||
result.push(message)
|
||||
} else if (Array.isArray(message.content)) {
|
||||
// Check if this message has both tool_result blocks and text blocks
|
||||
const toolResultBlocks: Anthropic.Messages.ToolResultBlockParam[] = []
|
||||
const textBlocks: Anthropic.Messages.TextBlockParam[] = []
|
||||
const imageBlocks: Anthropic.Messages.ImageBlockParam[] = []
|
||||
|
||||
for (const block of message.content) {
|
||||
if (block.type === "tool_result") {
|
||||
toolResultBlocks.push(block)
|
||||
} else if (block.type === "text") {
|
||||
textBlocks.push(block)
|
||||
} else if (block.type === "image") {
|
||||
imageBlocks.push(block)
|
||||
}
|
||||
}
|
||||
|
||||
// If we have tool_result blocks AND text blocks (like environment_details),
|
||||
// merge the text into the last tool_result's content
|
||||
const hasToolResults = toolResultBlocks.length > 0
|
||||
const hasTextBlocks = textBlocks.length > 0
|
||||
const hasImageBlocks = imageBlocks.length > 0
|
||||
|
||||
if (hasToolResults && hasTextBlocks && !hasImageBlocks) {
|
||||
// Merge text content into the last tool_result
|
||||
const textContent = textBlocks.map((b) => b.text).join("\n\n")
|
||||
const modifiedToolResults = [...toolResultBlocks]
|
||||
const lastToolResult = modifiedToolResults[modifiedToolResults.length - 1]
|
||||
|
||||
// Get existing content as string
|
||||
let existingContent: string
|
||||
if (typeof lastToolResult.content === "string") {
|
||||
existingContent = lastToolResult.content
|
||||
} else if (Array.isArray(lastToolResult.content)) {
|
||||
existingContent =
|
||||
lastToolResult.content
|
||||
?.map((c) => {
|
||||
if (c.type === "text") return c.text
|
||||
if (c.type === "image") return "(image)"
|
||||
return ""
|
||||
})
|
||||
.join("\n") ?? ""
|
||||
} else {
|
||||
existingContent = ""
|
||||
}
|
||||
|
||||
// Merge text into the last tool_result
|
||||
modifiedToolResults[modifiedToolResults.length - 1] = {
|
||||
...lastToolResult,
|
||||
content: existingContent ? `${existingContent}\n\n${textContent}` : textContent,
|
||||
}
|
||||
|
||||
result.push({
|
||||
...message,
|
||||
content: modifiedToolResults as ContentBlock[],
|
||||
})
|
||||
} else {
|
||||
// Keep the message as-is if:
|
||||
// - Only tool_result blocks (no text to merge)
|
||||
// - Only text/image blocks (no tool results)
|
||||
// - Has images (can't merge into tool_result)
|
||||
result.push(message)
|
||||
}
|
||||
} else {
|
||||
// Unknown format - keep as-is
|
||||
result.push(message)
|
||||
}
|
||||
} else {
|
||||
// Assistant messages - keep as-is
|
||||
result.push(message)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use mergeEnvironmentDetailsForMiniMax instead. This function extracted
|
||||
* environment_details to the system prompt, but the new approach merges them into
|
||||
* tool_result content like r1-format does with mergeToolResultText.
|
||||
*/
|
||||
export function extractEnvironmentDetailsForMiniMax(messages: Anthropic.Messages.MessageParam[]): {
|
||||
messages: Anthropic.Messages.MessageParam[]
|
||||
extractedSystemContent: string[]
|
||||
} {
|
||||
// For backwards compatibility, just return the merged messages with empty extracted content
|
||||
return {
|
||||
messages: mergeEnvironmentDetailsForMiniMax(messages),
|
||||
extractedSystemContent: [],
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue