Skip to content

Commit 627e98a

Browse files
committed
feat: add MiniMax AI provider support
- Add MiniMax type definitions with models abab5.5s-chat, abab6.5s-chat, and abab6.5g-chat - Implement MiniMaxHandler extending OpenAiHandler for API compatibility - Add MiniMax to provider settings schema and validations - Include comprehensive test coverage for MiniMax provider - Support MiniMax-M2 models for coding, reasoning, and AI-assisted development tasks
1 parent 97331bc commit 627e98a

File tree

9 files changed

+266
-0
lines changed

9 files changed

+266
-0
lines changed

packages/types/src/provider-settings.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
geminiModels,
1616
groqModels,
1717
ioIntelligenceModels,
18+
miniMaxModels,
1819
mistralModels,
1920
moonshotModels,
2021
openAiNativeModels,
@@ -125,6 +126,7 @@ export const providerNames = [
125126
"doubao",
126127
"deepseek",
127128
"featherless",
129+
"minimax",
128130
"fireworks",
129131
"gemini",
130132
"gemini-cli",
@@ -327,6 +329,11 @@ const moonshotSchema = apiModelIdProviderModelSchema.extend({
327329
moonshotApiKey: z.string().optional(),
328330
})
329331

332+
const miniMaxSchema = apiModelIdProviderModelSchema.extend({
333+
miniMaxBaseUrl: z.string().optional(),
334+
miniMaxApiKey: z.string().optional(),
335+
})
336+
330337
const unboundSchema = baseProviderSettingsSchema.extend({
331338
unboundApiKey: z.string().optional(),
332339
unboundModelId: z.string().optional(),
@@ -433,6 +440,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
433440
mistralSchema.merge(z.object({ apiProvider: z.literal("mistral") })),
434441
deepSeekSchema.merge(z.object({ apiProvider: z.literal("deepseek") })),
435442
deepInfraSchema.merge(z.object({ apiProvider: z.literal("deepinfra") })),
443+
miniMaxSchema.merge(z.object({ apiProvider: z.literal("minimax") })),
436444
doubaoSchema.merge(z.object({ apiProvider: z.literal("doubao") })),
437445
moonshotSchema.merge(z.object({ apiProvider: z.literal("moonshot") })),
438446
unboundSchema.merge(z.object({ apiProvider: z.literal("unbound") })),
@@ -474,6 +482,7 @@ export const providerSettingsSchema = z.object({
474482
...mistralSchema.shape,
475483
...deepSeekSchema.shape,
476484
...deepInfraSchema.shape,
485+
...miniMaxSchema.shape,
477486
...doubaoSchema.shape,
478487
...moonshotSchema.shape,
479488
...unboundSchema.shape,
@@ -562,6 +571,7 @@ export const modelIdKeysByProvider: Record<TypicalProvider, ModelIdKey> = {
562571
moonshot: "apiModelId",
563572
deepseek: "apiModelId",
564573
deepinfra: "deepInfraModelId",
574+
minimax: "apiModelId",
565575
doubao: "apiModelId",
566576
"qwen-code": "apiModelId",
567577
unbound: "unboundModelId",
@@ -639,6 +649,11 @@ export const MODELS_BY_PROVIDER: Record<
639649
label: "DeepSeek",
640650
models: Object.keys(deepSeekModels),
641651
},
652+
minimax: {
653+
id: "minimax",
654+
label: "MiniMax",
655+
models: Object.keys(miniMaxModels),
656+
},
642657
doubao: { id: "doubao", label: "Doubao", models: Object.keys(doubaoModels) },
643658
featherless: {
644659
id: "featherless",

packages/types/src/providers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export * from "./io-intelligence.js"
1515
export * from "./lite-llm.js"
1616
export * from "./lm-studio.js"
1717
export * from "./mistral.js"
18+
export * from "./minimax.js"
1819
export * from "./moonshot.js"
1920
export * from "./ollama.js"
2021
export * from "./openai.js"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { ModelInfo } from "../model.js"
2+
3+
// https://docs.minimaxi.com/docs/api
4+
export type MiniMaxModelId = keyof typeof miniMaxModels
5+
6+
export const miniMaxDefaultModelId: MiniMaxModelId = "abab5.5s-chat"
7+
8+
export const miniMaxModels = {
9+
"abab5.5s-chat": {
10+
maxTokens: 8192, // 8K max output
11+
contextWindow: 128_000,
12+
supportsImages: false,
13+
supportsPromptCache: false,
14+
inputPrice: 5, // $5 per million tokens
15+
outputPrice: 15, // $15 per million tokens
16+
description: `MiniMax-M2 is a high-performance model optimized for coding, reasoning, and general AI-assisted development tasks. It offers strong capabilities in code generation, debugging, and technical problem-solving.`,
17+
},
18+
"abab6.5s-chat": {
19+
maxTokens: 8192, // 8K max output
20+
contextWindow: 245_000,
21+
supportsImages: false,
22+
supportsPromptCache: false,
23+
inputPrice: 10, // $10 per million tokens
24+
outputPrice: 30, // $30 per million tokens
25+
description: `MiniMax-M2 Pro is an advanced version with extended context window and enhanced reasoning capabilities, ideal for complex coding projects and comprehensive code analysis.`,
26+
},
27+
"abab6.5g-chat": {
28+
maxTokens: 8192, // 8K max output
29+
contextWindow: 245_000,
30+
supportsImages: true,
31+
supportsPromptCache: false,
32+
inputPrice: 10, // $10 per million tokens
33+
outputPrice: 30, // $30 per million tokens
34+
description: `MiniMax-M2 Vision adds multimodal capabilities to the Pro model, supporting image understanding alongside code generation and reasoning tasks.`,
35+
},
36+
} as const satisfies Record<string, ModelInfo>
37+
38+
export const MINIMAX_DEFAULT_TEMPERATURE = 0.7

src/api/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
GeminiHandler,
1818
OpenAiNativeHandler,
1919
DeepSeekHandler,
20+
MiniMaxHandler,
2021
MoonshotHandler,
2122
MistralHandler,
2223
VsCodeLmHandler,
@@ -117,6 +118,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
117118
return new OpenAiNativeHandler(options)
118119
case "deepseek":
119120
return new DeepSeekHandler(options)
121+
case "minimax":
122+
return new MiniMaxHandler(options)
120123
case "doubao":
121124
return new DoubaoHandler(options)
122125
case "qwen-code":
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest"
2+
import OpenAI from "openai"
3+
import { miniMaxDefaultModelId, miniMaxModels } from "@roo-code/types"
4+
5+
import type { ApiHandlerOptions } from "../../../shared/api"
6+
7+
import { MiniMaxHandler } from "../minimax"
8+
9+
vi.mock("openai")
10+
11+
describe("MiniMaxHandler", () => {
12+
let handler: MiniMaxHandler
13+
let mockOptions: ApiHandlerOptions
14+
15+
beforeEach(() => {
16+
mockOptions = {
17+
apiModelId: "abab5.5s-chat",
18+
miniMaxApiKey: "test-api-key",
19+
}
20+
handler = new MiniMaxHandler(mockOptions)
21+
vi.clearAllMocks()
22+
})
23+
24+
describe("constructor", () => {
25+
it("should initialize with provided options", () => {
26+
expect(handler).toBeInstanceOf(MiniMaxHandler)
27+
expect(handler.getModel().id).toBe(mockOptions.apiModelId)
28+
})
29+
30+
it("should handle missing API key", () => {
31+
expect(() => {
32+
new MiniMaxHandler({
33+
...mockOptions,
34+
miniMaxApiKey: undefined,
35+
})
36+
}).not.toThrow()
37+
})
38+
39+
it("should use default model ID if not provided", () => {
40+
const handlerWithoutModel = new MiniMaxHandler({
41+
...mockOptions,
42+
apiModelId: undefined,
43+
})
44+
expect(handlerWithoutModel.getModel().id).toBe(miniMaxDefaultModelId)
45+
})
46+
47+
it("should use default base URL if not provided", () => {
48+
const handlerWithoutBaseUrl = new MiniMaxHandler({
49+
...mockOptions,
50+
miniMaxBaseUrl: undefined,
51+
})
52+
expect(handlerWithoutBaseUrl).toBeInstanceOf(MiniMaxHandler)
53+
// The base URL is passed to OpenAI client internally
54+
})
55+
56+
it("should use custom base URL if provided", () => {
57+
const customBaseUrl = "https://custom.minimax.com/v1"
58+
const handlerWithCustomUrl = new MiniMaxHandler({
59+
...mockOptions,
60+
miniMaxBaseUrl: customBaseUrl,
61+
})
62+
expect(handlerWithCustomUrl).toBeInstanceOf(MiniMaxHandler)
63+
// The custom base URL is passed to OpenAI client
64+
})
65+
66+
it("should set includeMaxTokens to true", () => {
67+
// Create a new handler and verify OpenAI client was called with includeMaxTokens
68+
const _handler = new MiniMaxHandler(mockOptions)
69+
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: mockOptions.miniMaxApiKey }))
70+
// includeMaxTokens is an internal property passed to super constructor
71+
})
72+
})
73+
74+
describe("getModel", () => {
75+
it("should return correct model info for abab5.5s-chat", () => {
76+
const model = handler.getModel()
77+
expect(model.id).toBe("abab5.5s-chat")
78+
expect(model.info).toEqual(miniMaxModels["abab5.5s-chat"])
79+
})
80+
81+
it("should return correct model info for abab6.5s-chat", () => {
82+
const handlerWithPro = new MiniMaxHandler({
83+
...mockOptions,
84+
apiModelId: "abab6.5s-chat",
85+
})
86+
const model = handlerWithPro.getModel()
87+
expect(model.id).toBe("abab6.5s-chat")
88+
expect(model.info).toEqual(miniMaxModels["abab6.5s-chat"])
89+
})
90+
91+
it("should return correct model info for abab6.5g-chat", () => {
92+
const handlerWithVision = new MiniMaxHandler({
93+
...mockOptions,
94+
apiModelId: "abab6.5g-chat",
95+
})
96+
const model = handlerWithVision.getModel()
97+
expect(model.id).toBe("abab6.5g-chat")
98+
expect(model.info).toEqual(miniMaxModels["abab6.5g-chat"])
99+
expect(model.info.supportsImages).toBe(true)
100+
})
101+
102+
it("should return provided model ID with default model info if model does not exist", () => {
103+
const handlerWithInvalidModel = new MiniMaxHandler({
104+
...mockOptions,
105+
apiModelId: "invalid-model",
106+
})
107+
const model = handlerWithInvalidModel.getModel()
108+
expect(model.id).toBe("invalid-model")
109+
// Should fallback to default model info
110+
expect(model.info).toEqual(miniMaxModels[miniMaxDefaultModelId])
111+
})
112+
113+
it("should return default model if no model ID is provided", () => {
114+
const handlerWithoutModel = new MiniMaxHandler({
115+
...mockOptions,
116+
apiModelId: undefined,
117+
})
118+
const model = handlerWithoutModel.getModel()
119+
expect(model.id).toBe(miniMaxDefaultModelId)
120+
expect(model.info).toEqual(miniMaxModels[miniMaxDefaultModelId])
121+
})
122+
})
123+
124+
describe("model capabilities", () => {
125+
it("should correctly report image support for models", () => {
126+
const textOnlyModel = new MiniMaxHandler({
127+
...mockOptions,
128+
apiModelId: "abab5.5s-chat",
129+
})
130+
expect(textOnlyModel.getModel().info.supportsImages).toBe(false)
131+
132+
const visionModel = new MiniMaxHandler({
133+
...mockOptions,
134+
apiModelId: "abab6.5g-chat",
135+
})
136+
expect(visionModel.getModel().info.supportsImages).toBe(true)
137+
})
138+
139+
it("should report no prompt cache support for all models", () => {
140+
const models = ["abab5.5s-chat", "abab6.5s-chat", "abab6.5g-chat"]
141+
142+
models.forEach((modelId) => {
143+
const handler = new MiniMaxHandler({
144+
...mockOptions,
145+
apiModelId: modelId,
146+
})
147+
expect(handler.getModel().info.supportsPromptCache).toBe(false)
148+
})
149+
})
150+
151+
it("should have correct context windows for each model", () => {
152+
const contextWindows = {
153+
"abab5.5s-chat": 128_000,
154+
"abab6.5s-chat": 245_000,
155+
"abab6.5g-chat": 245_000,
156+
}
157+
158+
Object.entries(contextWindows).forEach(([modelId, expectedWindow]) => {
159+
const handler = new MiniMaxHandler({
160+
...mockOptions,
161+
apiModelId: modelId,
162+
})
163+
expect(handler.getModel().info.contextWindow).toBe(expectedWindow)
164+
})
165+
})
166+
167+
it("should have correct max tokens for all models", () => {
168+
const models = ["abab5.5s-chat", "abab6.5s-chat", "abab6.5g-chat"]
169+
170+
models.forEach((modelId) => {
171+
const handler = new MiniMaxHandler({
172+
...mockOptions,
173+
apiModelId: modelId,
174+
})
175+
expect(handler.getModel().info.maxTokens).toBe(8192)
176+
})
177+
})
178+
})
179+
})

src/api/providers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export { ChutesHandler } from "./chutes"
66
export { ClaudeCodeHandler } from "./claude-code"
77
export { DeepSeekHandler } from "./deepseek"
88
export { DoubaoHandler } from "./doubao"
9+
export { MiniMaxHandler } from "./minimax"
910
export { MoonshotHandler } from "./moonshot"
1011
export { FakeAIHandler } from "./fake-ai"
1112
export { GeminiHandler } from "./gemini"

src/api/providers/minimax.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { miniMaxModels, miniMaxDefaultModelId, MINIMAX_DEFAULT_TEMPERATURE } from "@roo-code/types"
2+
3+
import type { ApiHandlerOptions } from "../../shared/api"
4+
5+
import { getModelParams } from "../transform/model-params"
6+
7+
import { OpenAiHandler } from "./openai"
8+
9+
export class MiniMaxHandler extends OpenAiHandler {
10+
constructor(options: ApiHandlerOptions) {
11+
super({
12+
...options,
13+
openAiApiKey: options.miniMaxApiKey ?? "not-provided",
14+
openAiModelId: options.apiModelId ?? miniMaxDefaultModelId,
15+
openAiBaseUrl: options.miniMaxBaseUrl ?? "https://api.minimaxi.com/v1",
16+
openAiStreamingEnabled: true,
17+
includeMaxTokens: true,
18+
})
19+
}
20+
21+
override getModel() {
22+
const id = this.options.apiModelId ?? miniMaxDefaultModelId
23+
const info = miniMaxModels[id as keyof typeof miniMaxModels] || miniMaxModels[miniMaxDefaultModelId]
24+
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
25+
return { id, info, ...params }
26+
}
27+
}

src/shared/ProfileValidator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export class ProfileValidator {
6464
case "gemini":
6565
case "mistral":
6666
case "deepseek":
67+
case "minimax":
6768
case "xai":
6869
case "groq":
6970
case "sambanova":

src/shared/__tests__/ProfileValidator.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ describe("ProfileValidator", () => {
189189
"gemini",
190190
"mistral",
191191
"deepseek",
192+
"minimax",
192193
"xai",
193194
"groq",
194195
"chutes",

0 commit comments

Comments
 (0)