Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 15 additions & 0 deletions packages/types/src/providers/minimax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ 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",
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]
Expand Down
25 changes: 21 additions & 4 deletions src/api/providers/minimax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { ApiHandlerOptions } from "../../shared/api"

import { ApiStream } from "../transform/stream"
import { getModelParams } from "../transform/model-params"
import { extractEnvironmentDetailsForMiniMax } from "../transform/minimax-format"

import { BaseProvider } from "./base-provider"
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
Expand Down Expand Up @@ -87,15 +88,31 @@ export class MiniMaxHandler extends BaseProvider implements SingleCompletionHand
// MiniMax M2 models support prompt caching
const supportsPromptCache = info.supportsPromptCache ?? false

// Extract environment_details from messages that follow tool_result blocks
// and move them to the system prompt. This preserves reasoning continuity
// for thinking models by preventing user messages from interrupting the
// reasoning context after tool use.
const { messages: processedMessages, extractedSystemContent } = extractEnvironmentDetailsForMiniMax(messages)

// Build the system blocks array - start with the main system prompt
const systemBlocks: Anthropic.Messages.TextBlockParam[] = [
supportsPromptCache
? { text: systemPrompt, type: "text", cache_control: cacheControl }
: { text: systemPrompt, type: "text" },
]

// Add any extracted environment_details as additional system text blocks
for (const extractedText of extractedSystemContent) {
systemBlocks.push({ text: extractedText, 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,
}

Expand Down
294 changes: 294 additions & 0 deletions src/api/transform/__tests__/minimax-format.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
// npx vitest run api/transform/__tests__/minimax-format.spec.ts

import { Anthropic } from "@anthropic-ai/sdk"

import { extractEnvironmentDetailsForMiniMax } from "../minimax-format"

describe("extractEnvironmentDetailsForMiniMax", () => {
it("should pass through simple text messages unchanged", () => {
const messages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: "Hello",
},
{
role: "assistant",
content: "Hi there!",
},
]

const result = extractEnvironmentDetailsForMiniMax(messages)

expect(result.messages).toHaveLength(2)
expect(result.messages).toEqual(messages)
expect(result.extractedSystemContent).toHaveLength(0)
})

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 = extractEnvironmentDetailsForMiniMax(messages)

expect(result.messages).toHaveLength(1)
expect(result.messages).toEqual(messages)
expect(result.extractedSystemContent).toHaveLength(0)
})

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 = extractEnvironmentDetailsForMiniMax(messages)

expect(result.messages).toHaveLength(1)
expect(result.messages).toEqual(messages)
expect(result.extractedSystemContent).toHaveLength(0)
})

it("should extract text content from user messages with tool_result AND text blocks", () => {
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 = extractEnvironmentDetailsForMiniMax(messages)

// The text should be extracted
expect(result.extractedSystemContent).toHaveLength(1)
expect(result.extractedSystemContent[0]).toBe(
"<environment_details>\nCurrent Time: 2024-01-01\n</environment_details>",
)

// The message should only contain tool_result
expect(result.messages).toHaveLength(1)
expect(result.messages[0].role).toBe("user")
const content = result.messages[0].content as Anthropic.Messages.ContentBlockParam[]
expect(content).toHaveLength(1)
expect(content[0].type).toBe("tool_result")
})

it("should extract multiple text blocks from a single message", () => {
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 = extractEnvironmentDetailsForMiniMax(messages)

// Both text blocks should be extracted
expect(result.extractedSystemContent).toHaveLength(2)
expect(result.extractedSystemContent[0]).toBe("First text block")
expect(result.extractedSystemContent[1]).toBe("Second text block")

// The message should only contain tool_result blocks
expect(result.messages).toHaveLength(1)
const content = result.messages[0].content as Anthropic.Messages.ContentBlockParam[]
expect(content).toHaveLength(2)
expect(content[0].type).toBe("tool_result")
expect(content[1].type).toBe("tool_result")
})

it("should NOT extract text when images are present (cannot move images to system)", () => {
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 = extractEnvironmentDetailsForMiniMax(messages)

// Nothing should be extracted since images are present
expect(result.extractedSystemContent).toHaveLength(0)

// Message should be unchanged
expect(result.messages).toHaveLength(1)
expect(result.messages).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 = extractEnvironmentDetailsForMiniMax(messages)

expect(result.messages).toHaveLength(1)
expect(result.messages).toEqual(messages)
expect(result.extractedSystemContent).toHaveLength(0)
})

it("should handle mixed conversation with extraction 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 = extractEnvironmentDetailsForMiniMax(messages)

// Should extract the environment_details from the third message
expect(result.extractedSystemContent).toHaveLength(1)
expect(result.extractedSystemContent[0]).toContain("environment_details")

// Should have all 4 messages
expect(result.messages).toHaveLength(4)

// First user message unchanged (simple string)
expect(result.messages[0]).toEqual(messages[0])

// Assistant message unchanged
expect(result.messages[1]).toEqual(messages[1])

// Third message should have only tool_result
const thirdMessage = result.messages[2].content as Anthropic.Messages.ContentBlockParam[]
expect(thirdMessage).toHaveLength(1)
expect(thirdMessage[0].type).toBe("tool_result")

// Fourth message unchanged
expect(result.messages[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 = extractEnvironmentDetailsForMiniMax(messages)

expect(result.messages).toHaveLength(1)
expect(result.messages).toEqual(messages)
expect(result.extractedSystemContent).toHaveLength(0)
})

it("should handle empty messages array", () => {
const messages: Anthropic.Messages.MessageParam[] = []

const result = extractEnvironmentDetailsForMiniMax(messages)

expect(result.messages).toHaveLength(0)
expect(result.extractedSystemContent).toHaveLength(0)
})
})
Loading
Loading