From 3358018883d1ab7a9d3377d0e6d9fa6c615d2eed Mon Sep 17 00:00:00 2001 From: xiaose Date: Thu, 23 Oct 2025 13:18:41 +0800 Subject: [PATCH 01/13] feat: add minimax provider --- packages/types/src/global-settings.ts | 1 + packages/types/src/provider-settings.ts | 17 ++ packages/types/src/providers/index.ts | 1 + packages/types/src/providers/minimax.ts | 22 ++ src/api/index.ts | 3 + src/api/providers/__tests__/minimax.spec.ts | 282 ++++++++++++++++++ src/api/providers/index.ts | 1 + src/api/providers/minimax.ts | 19 ++ .../src/components/settings/ApiOptions.tsx | 7 + .../src/components/settings/constants.ts | 3 + .../components/settings/providers/MiniMax.tsx | 73 +++++ .../components/settings/providers/index.ts | 1 + .../components/ui/hooks/useSelectedModel.ts | 7 + webview-ui/src/i18n/locales/ca/settings.json | 3 + webview-ui/src/i18n/locales/de/settings.json | 3 + webview-ui/src/i18n/locales/en/settings.json | 3 + webview-ui/src/i18n/locales/es/settings.json | 3 + webview-ui/src/i18n/locales/hi/settings.json | 3 + webview-ui/src/i18n/locales/id/settings.json | 3 + webview-ui/src/i18n/locales/it/settings.json | 3 + webview-ui/src/i18n/locales/ja/settings.json | 3 + webview-ui/src/i18n/locales/ko/settings.json | 3 + webview-ui/src/i18n/locales/nl/settings.json | 3 + webview-ui/src/i18n/locales/pl/settings.json | 3 + .../src/i18n/locales/pt-BR/settings.json | 3 + webview-ui/src/i18n/locales/ru/settings.json | 3 + webview-ui/src/i18n/locales/tr/settings.json | 3 + webview-ui/src/i18n/locales/vi/settings.json | 3 + .../src/i18n/locales/zh-CN/settings.json | 6 +- .../src/i18n/locales/zh-TW/settings.json | 3 + 30 files changed, 488 insertions(+), 3 deletions(-) create mode 100644 packages/types/src/providers/minimax.ts create mode 100644 src/api/providers/__tests__/minimax.spec.ts create mode 100644 src/api/providers/minimax.ts create mode 100644 webview-ui/src/components/settings/providers/MiniMax.tsx diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index a56a00fc355a..2fc2f370bb14 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -186,6 +186,7 @@ export const SECRET_STATE_KEYS = [ "doubaoApiKey", "moonshotApiKey", "mistralApiKey", + "minimaxApiKey", "unboundApiKey", "requestyApiKey", "xaiApiKey", diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 5262e7602d68..d65976eb47e0 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -25,6 +25,7 @@ import { vscodeLlmModels, xaiModels, internationalZAiModels, + minimaxModels, } from "./providers/index.js" /** @@ -131,6 +132,7 @@ export const providerNames = [ "groq", "mistral", "moonshot", + "minimax", "openai-native", "qwen-code", "roo", @@ -327,6 +329,13 @@ const moonshotSchema = apiModelIdProviderModelSchema.extend({ moonshotApiKey: z.string().optional(), }) +const minimaxSchema = apiModelIdProviderModelSchema.extend({ + minimaxBaseUrl: z + .union([z.literal("https://api.minimax.io/v1"), z.literal("https://api.minimaxi.com/v1")]) + .optional(), + minimaxApiKey: z.string().optional(), +}) + const unboundSchema = baseProviderSettingsSchema.extend({ unboundApiKey: z.string().optional(), unboundModelId: z.string().optional(), @@ -435,6 +444,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv deepInfraSchema.merge(z.object({ apiProvider: z.literal("deepinfra") })), doubaoSchema.merge(z.object({ apiProvider: z.literal("doubao") })), moonshotSchema.merge(z.object({ apiProvider: z.literal("moonshot") })), + minimaxSchema.merge(z.object({ apiProvider: z.literal("minimax") })), unboundSchema.merge(z.object({ apiProvider: z.literal("unbound") })), requestySchema.merge(z.object({ apiProvider: z.literal("requesty") })), humanRelaySchema.merge(z.object({ apiProvider: z.literal("human-relay") })), @@ -476,6 +486,7 @@ export const providerSettingsSchema = z.object({ ...deepInfraSchema.shape, ...doubaoSchema.shape, ...moonshotSchema.shape, + ...minimaxSchema.shape, ...unboundSchema.shape, ...requestySchema.shape, ...humanRelaySchema.shape, @@ -560,6 +571,7 @@ export const modelIdKeysByProvider: Record = { "gemini-cli": "apiModelId", mistral: "apiModelId", moonshot: "apiModelId", + minimax: "apiModelId", deepseek: "apiModelId", deepinfra: "deepInfraModelId", doubao: "apiModelId", @@ -671,6 +683,11 @@ export const MODELS_BY_PROVIDER: Record< label: "Moonshot", models: Object.keys(moonshotModels), }, + minimax: { + id: "minimax", + label: "MiniMax", + models: Object.keys(minimaxModels), + }, "openai-native": { id: "openai-native", label: "OpenAI", diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index 21e43aaa99a6..2a1a3f986aa5 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -30,3 +30,4 @@ export * from "./xai.js" export * from "./vercel-ai-gateway.js" export * from "./zai.js" export * from "./deepinfra.js" +export * from "./minimax.js" diff --git a/packages/types/src/providers/minimax.ts b/packages/types/src/providers/minimax.ts new file mode 100644 index 000000000000..cef8c19caf36 --- /dev/null +++ b/packages/types/src/providers/minimax.ts @@ -0,0 +1,22 @@ +import type { ModelInfo } from "../model.js" + +// Minimax +// https://www.minimax.io/platform/document/text_api_intro +// https://www.minimax.io/platform/document/pricing +export type MinimaxModelId = keyof typeof minimaxModels +export const minimaxDefaultModelId: MinimaxModelId = "MiniMax-M1" + +export const minimaxModels = { + "MiniMax-M1": { + maxTokens: 25_600, + contextWindow: 1_000_192, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0.4, + outputPrice: 2.2, + cacheWritesPrice: 0, + cacheReadsPrice: 0, + }, +} as const satisfies Record + +export const MINIMAX_DEFAULT_TEMPERATURE = 1.0 diff --git a/src/api/index.ts b/src/api/index.ts index ac0096767624..351f4ef1befe 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -40,6 +40,7 @@ import { FeatherlessHandler, VercelAiGatewayHandler, DeepInfraHandler, + MiniMaxHandler, } from "./providers" import { NativeOllamaHandler } from "./providers/native-ollama" @@ -165,6 +166,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler { return new FeatherlessHandler(options) case "vercel-ai-gateway": return new VercelAiGatewayHandler(options) + case "minimax": + return new MiniMaxHandler(options) default: apiProvider satisfies "gemini-cli" | undefined return new AnthropicHandler(options) diff --git a/src/api/providers/__tests__/minimax.spec.ts b/src/api/providers/__tests__/minimax.spec.ts new file mode 100644 index 000000000000..c1aac1563841 --- /dev/null +++ b/src/api/providers/__tests__/minimax.spec.ts @@ -0,0 +1,282 @@ +// npx vitest run src/api/providers/__tests__/minimax.spec.ts + +vitest.mock("vscode", () => ({ + workspace: { + getConfiguration: vitest.fn().mockReturnValue({ + get: vitest.fn().mockReturnValue(600), // Default timeout in seconds + }), + }, +})) + +import OpenAI from "openai" +import { Anthropic } from "@anthropic-ai/sdk" + +import { type MinimaxModelId, minimaxDefaultModelId, minimaxModels } from "@roo-code/types" + +import { MiniMaxHandler } from "../minimax" + +vitest.mock("openai", () => { + const createMock = vitest.fn() + return { + default: vitest.fn(() => ({ chat: { completions: { create: createMock } } })), + } +}) + +describe("MiniMaxHandler", () => { + let handler: MiniMaxHandler + let mockCreate: any + + beforeEach(() => { + vitest.clearAllMocks() + mockCreate = (OpenAI as unknown as any)().chat.completions.create + }) + + describe("International MiniMax (default)", () => { + beforeEach(() => { + handler = new MiniMaxHandler({ + minimaxApiKey: "test-minimax-api-key", + minimaxBaseUrl: "https://api.minimax.io/v1", + }) + }) + + it("should use the correct international MiniMax base URL by default", () => { + new MiniMaxHandler({ minimaxApiKey: "test-minimax-api-key" }) + expect(OpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + baseURL: "https://api.minimax.io/v1", + }), + ) + }) + + it("should use the provided API key", () => { + const minimaxApiKey = "test-minimax-api-key" + new MiniMaxHandler({ minimaxApiKey }) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: minimaxApiKey })) + }) + + it("should return default model when no model is specified", () => { + const model = handler.getModel() + expect(model.id).toBe(minimaxDefaultModelId) + expect(model.info).toEqual(minimaxModels[minimaxDefaultModelId]) + }) + + it("should return specified model when valid model is provided", () => { + const testModelId: MinimaxModelId = "MiniMax-M1" + const handlerWithModel = new MiniMaxHandler({ + apiModelId: testModelId, + minimaxApiKey: "test-minimax-api-key", + }) + const model = handlerWithModel.getModel() + expect(model.id).toBe(testModelId) + expect(model.info).toEqual(minimaxModels[testModelId]) + }) + + it("should return MiniMax-M1 model with correct configuration", () => { + const testModelId: MinimaxModelId = "MiniMax-M1" + const handlerWithModel = new MiniMaxHandler({ + apiModelId: testModelId, + minimaxApiKey: "test-minimax-api-key", + }) + const model = handlerWithModel.getModel() + expect(model.id).toBe(testModelId) + expect(model.info).toEqual(minimaxModels[testModelId]) + expect(model.info.contextWindow).toBe(1_000_192) + expect(model.info.maxTokens).toBe(25_600) + expect(model.info.supportsPromptCache).toBe(false) + }) + }) + + describe("China MiniMax", () => { + beforeEach(() => { + handler = new MiniMaxHandler({ + minimaxApiKey: "test-minimax-api-key", + minimaxBaseUrl: "https://api.minimaxi.com/v1", + }) + }) + + it("should use the correct China MiniMax base URL", () => { + new MiniMaxHandler({ + minimaxApiKey: "test-minimax-api-key", + minimaxBaseUrl: "https://api.minimaxi.com/v1", + }) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.minimaxi.com/v1" })) + }) + + it("should use the provided API key for China", () => { + const minimaxApiKey = "test-minimax-api-key" + new MiniMaxHandler({ minimaxApiKey, minimaxBaseUrl: "https://api.minimaxi.com/v1" }) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: minimaxApiKey })) + }) + + it("should return default model when no model is specified", () => { + const model = handler.getModel() + expect(model.id).toBe(minimaxDefaultModelId) + expect(model.info).toEqual(minimaxModels[minimaxDefaultModelId]) + }) + }) + + describe("Default behavior", () => { + it("should default to international base URL when none is specified", () => { + const handlerDefault = new MiniMaxHandler({ minimaxApiKey: "test-minimax-api-key" }) + expect(OpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + baseURL: "https://api.minimax.io/v1", + }), + ) + + const model = handlerDefault.getModel() + expect(model.id).toBe(minimaxDefaultModelId) + expect(model.info).toEqual(minimaxModels[minimaxDefaultModelId]) + }) + + it("should use undefined as default API key when none is specified", () => { + new MiniMaxHandler({}) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: undefined })) + }) + + it("should default to MiniMax-M1 model", () => { + const handlerDefault = new MiniMaxHandler({ minimaxApiKey: "test-minimax-api-key" }) + const model = handlerDefault.getModel() + expect(model.id).toBe("MiniMax-M1") + }) + }) + + describe("API Methods", () => { + beforeEach(() => { + handler = new MiniMaxHandler({ minimaxApiKey: "test-minimax-api-key" }) + }) + + it("completePrompt method should return text from MiniMax API", async () => { + const expectedResponse = "This is a test response from MiniMax" + mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] }) + const result = await handler.completePrompt("test prompt") + expect(result).toBe(expectedResponse) + }) + + it("should handle errors in completePrompt", async () => { + const errorMessage = "MiniMax API error" + mockCreate.mockRejectedValueOnce(new Error(errorMessage)) + await expect(handler.completePrompt("test prompt")).rejects.toThrow() + }) + + it("createMessage should yield text content from stream", async () => { + const testContent = "This is test content from MiniMax stream" + + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: vitest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: testContent } }] }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ type: "text", text: testContent }) + }) + + it("createMessage should yield usage data from stream", async () => { + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: vitest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { + choices: [{ delta: {} }], + usage: { prompt_tokens: 10, completion_tokens: 20 }, + }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ type: "usage", inputTokens: 10, outputTokens: 20 }) + }) + + it("createMessage should pass correct parameters to MiniMax client", async () => { + const modelId: MinimaxModelId = "MiniMax-M1" + const modelInfo = minimaxModels[modelId] + const handlerWithModel = new MiniMaxHandler({ + apiModelId: modelId, + minimaxApiKey: "test-minimax-api-key", + }) + + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + async next() { + return { done: true } + }, + }), + } + }) + + const systemPrompt = "Test system prompt for MiniMax" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message for MiniMax" }] + + const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages) + await messageGenerator.next() + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: modelId, + max_tokens: modelInfo.maxTokens, + temperature: 0, + messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), + stream: true, + stream_options: { include_usage: true }, + }), + undefined, + ) + }) + + it("should use temperature 0 by default", async () => { + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + async next() { + return { done: true } + }, + }), + } + }) + + const messageGenerator = handler.createMessage("test", []) + await messageGenerator.next() + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + temperature: 0, + }), + undefined, + ) + }) + }) + + describe("Model Configuration", () => { + it("should correctly configure MiniMax-M1 model properties", () => { + const model = minimaxModels["MiniMax-M1"] + expect(model.maxTokens).toBe(25_600) + expect(model.contextWindow).toBe(1_000_192) + expect(model.supportsImages).toBe(false) + expect(model.supportsPromptCache).toBe(false) + expect(model.inputPrice).toBe(0.4) + expect(model.outputPrice).toBe(2.2) + }) + }) +}) diff --git a/src/api/providers/index.ts b/src/api/providers/index.ts index 85d877b6bc78..533023d0374b 100644 --- a/src/api/providers/index.ts +++ b/src/api/providers/index.ts @@ -34,3 +34,4 @@ export { RooHandler } from "./roo" export { FeatherlessHandler } from "./featherless" export { VercelAiGatewayHandler } from "./vercel-ai-gateway" export { DeepInfraHandler } from "./deepinfra" +export { MiniMaxHandler } from "./minimax" diff --git a/src/api/providers/minimax.ts b/src/api/providers/minimax.ts new file mode 100644 index 000000000000..8a8e8c14e5b4 --- /dev/null +++ b/src/api/providers/minimax.ts @@ -0,0 +1,19 @@ +import { type MinimaxModelId, minimaxDefaultModelId, minimaxModels } from "@roo-code/types" + +import type { ApiHandlerOptions } from "../../shared/api" + +import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" + +export class MiniMaxHandler extends BaseOpenAiCompatibleProvider { + constructor(options: ApiHandlerOptions) { + super({ + ...options, + providerName: "MiniMax", + baseURL: options.minimaxBaseUrl ?? "https://api.minimax.io/v1", + apiKey: options.minimaxApiKey, + defaultProviderModelId: minimaxDefaultModelId, + providerModels: minimaxModels, + defaultTemperature: 1.0, + }) + } +} diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 37c1c286b983..ec934439ce68 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -37,6 +37,7 @@ import { rooDefaultModelId, vercelAiGatewayDefaultModelId, deepInfraDefaultModelId, + minimaxDefaultModelId, } from "@roo-code/types" import { vscode } from "@src/utils/vscode" @@ -95,6 +96,7 @@ import { Featherless, VercelAiGateway, DeepInfra, + MiniMax, } from "./providers" import { MODELS_BY_PROVIDER, PROVIDERS } from "./constants" @@ -335,6 +337,7 @@ const ApiOptions = ({ deepseek: { field: "apiModelId", default: deepSeekDefaultModelId }, doubao: { field: "apiModelId", default: doubaoDefaultModelId }, moonshot: { field: "apiModelId", default: moonshotDefaultModelId }, + minimax: { field: "apiModelId", default: minimaxDefaultModelId }, mistral: { field: "apiModelId", default: mistralDefaultModelId }, xai: { field: "apiModelId", default: xaiDefaultModelId }, groq: { field: "apiModelId", default: groqDefaultModelId }, @@ -587,6 +590,10 @@ const ApiOptions = ({ )} + {selectedProvider === "minimax" && ( + + )} + {selectedProvider === "vscode-lm" && ( )} diff --git a/webview-ui/src/components/settings/constants.ts b/webview-ui/src/components/settings/constants.ts index ae336730ff51..3a174aa1a9ee 100644 --- a/webview-ui/src/components/settings/constants.ts +++ b/webview-ui/src/components/settings/constants.ts @@ -21,6 +21,7 @@ import { fireworksModels, rooModels, featherlessModels, + minimaxModels, } from "@roo-code/types" export const MODELS_BY_PROVIDER: Partial>> = { @@ -44,6 +45,7 @@ export const MODELS_BY_PROVIDER: Partial a.label.localeCompare(b.label)) diff --git a/webview-ui/src/components/settings/providers/MiniMax.tsx b/webview-ui/src/components/settings/providers/MiniMax.tsx new file mode 100644 index 000000000000..4055be7d1791 --- /dev/null +++ b/webview-ui/src/components/settings/providers/MiniMax.tsx @@ -0,0 +1,73 @@ +import { useCallback } from "react" +import { VSCodeTextField, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react" + +import type { ProviderSettings } from "@roo-code/types" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" + +import { inputEventTransform } from "../transforms" +import { cn } from "@/lib/utils" + +type MiniMaxProps = { + apiConfiguration: ProviderSettings + setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void +} + +export const MiniMax = ({ apiConfiguration, setApiConfigurationField }: MiniMaxProps) => { + const { t } = useAppTranslation() + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ProviderSettings[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> +
+ + + + api.minimax.io + + + api.minimaxi.com + + +
+
+ + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.minimaxApiKey && ( + + {t("settings:providers.getMiniMaxApiKey")} + + )} +
+ + ) +} diff --git a/webview-ui/src/components/settings/providers/index.ts b/webview-ui/src/components/settings/providers/index.ts index fe0e6cecf961..4e10e0f0a51c 100644 --- a/webview-ui/src/components/settings/providers/index.ts +++ b/webview-ui/src/components/settings/providers/index.ts @@ -30,3 +30,4 @@ export { Fireworks } from "./Fireworks" export { Featherless } from "./Featherless" export { VercelAiGateway } from "./VercelAiGateway" export { DeepInfra } from "./DeepInfra" +export { MiniMax } from "./MiniMax" diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index a3ce1e63e4e1..2e40c2715665 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -12,6 +12,8 @@ import { deepSeekModels, moonshotDefaultModelId, moonshotModels, + minimaxDefaultModelId, + minimaxModels, geminiDefaultModelId, geminiModels, mistralDefaultModelId, @@ -237,6 +239,11 @@ function getSelectedModel({ const info = moonshotModels[id as keyof typeof moonshotModels] return { id, info } } + case "minimax": { + const id = apiConfiguration.apiModelId ?? minimaxDefaultModelId + const info = minimaxModels[id as keyof typeof minimaxModels] + return { id, info } + } case "zai": { const isChina = apiConfiguration.zaiApiLine === "china_coding" const models = isChina ? mainlandZAiModels : internationalZAiModels diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 86fc5863333c..bd76c06da916 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Obtenir clau API de Z AI", "zaiEntrypoint": "Punt d'entrada de Z AI", "zaiEntrypointDescription": "Si us plau, seleccioneu el punt d'entrada de l'API apropiat segons la vostra ubicació. Si sou a la Xina, trieu open.bigmodel.cn. Altrament, trieu api.z.ai.", + "minimaxApiKey": "Clau API de MiniMax", + "getMiniMaxApiKey": "Obtenir clau API de MiniMax", + "minimaxBaseUrl": "Punt d'entrada de MiniMax", "geminiApiKey": "Clau API de Gemini", "getGroqApiKey": "Obtenir clau API de Groq", "groqApiKey": "Clau API de Groq", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index cbbc21af51e8..e0a7ee90c977 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Z AI API-Schlüssel erhalten", "zaiEntrypoint": "Z AI Einstiegspunkt", "zaiEntrypointDescription": "Bitte wählen Sie den entsprechenden API-Einstiegspunkt basierend auf Ihrem Standort. Wenn Sie sich in China befinden, wählen Sie open.bigmodel.cn. Andernfalls wählen Sie api.z.ai.", + "minimaxApiKey": "MiniMax API-Schlüssel", + "getMiniMaxApiKey": "MiniMax API-Schlüssel erhalten", + "minimaxBaseUrl": "MiniMax-Einstiegspunkt", "geminiApiKey": "Gemini API-Schlüssel", "getGroqApiKey": "Groq API-Schlüssel erhalten", "groqApiKey": "Groq API-Schlüssel", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 9312d568b145..e11d325c9d89 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -298,6 +298,9 @@ "moonshotApiKey": "Moonshot API Key", "getMoonshotApiKey": "Get Moonshot API Key", "moonshotBaseUrl": "Moonshot Entrypoint", + "minimaxApiKey": "MiniMax API Key", + "getMiniMaxApiKey": "Get MiniMax API Key", + "minimaxBaseUrl": "MiniMax Entrypoint", "zaiApiKey": "Z AI API Key", "getZaiApiKey": "Get Z AI API Key", "zaiEntrypoint": "Z AI Entrypoint", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index c8dac4f8c3ea..67ab2e23107d 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Obtener clave API de Z AI", "zaiEntrypoint": "Punto de entrada de Z AI", "zaiEntrypointDescription": "Por favor, seleccione el punto de entrada de API apropiado según su ubicación. Si está en China, elija open.bigmodel.cn. De lo contrario, elija api.z.ai.", + "minimaxApiKey": "Clave API de MiniMax", + "getMiniMaxApiKey": "Obtener clave API de MiniMax", + "minimaxBaseUrl": "Punto de entrada de MiniMax", "geminiApiKey": "Clave API de Gemini", "getGroqApiKey": "Obtener clave API de Groq", "groqApiKey": "Clave API de Groq", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 3c463369784b..010da67c1823 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Z AI API कुंजी प्राप्त करें", "zaiEntrypoint": "Z AI प्रवेश बिंदु", "zaiEntrypointDescription": "कृपया अपने स्थान के आधार पर उपयुक्त API प्रवेश बिंदु का चयन करें। यदि आप चीन में हैं, तो open.bigmodel.cn चुनें। अन्यथा, api.z.ai चुनें।", + "minimaxApiKey": "MiniMax API कुंजी", + "getMiniMaxApiKey": "MiniMax API कुंजी प्राप्त करें", + "minimaxBaseUrl": "MiniMax प्रवेश बिंदु", "geminiApiKey": "Gemini API कुंजी", "getGroqApiKey": "Groq API कुंजी प्राप्त करें", "groqApiKey": "Groq API कुंजी", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 48604b7b383e..4b6eae99c1f7 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -301,6 +301,9 @@ "getZaiApiKey": "Dapatkan Kunci API Z AI", "zaiEntrypoint": "Titik Masuk Z AI", "zaiEntrypointDescription": "Silakan pilih titik masuk API yang sesuai berdasarkan lokasi Anda. Jika Anda berada di China, pilih open.bigmodel.cn. Jika tidak, pilih api.z.ai.", + "minimaxApiKey": "Kunci API MiniMax", + "getMiniMaxApiKey": "Dapatkan Kunci API MiniMax", + "minimaxBaseUrl": "Titik Masuk MiniMax", "geminiApiKey": "Gemini API Key", "getGroqApiKey": "Dapatkan Groq API Key", "groqApiKey": "Groq API Key", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 66fdd761b772..1ce817cf5813 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Ottieni chiave API Z AI", "zaiEntrypoint": "Punto di ingresso Z AI", "zaiEntrypointDescription": "Si prega di selezionare il punto di ingresso API appropriato in base alla propria posizione. Se ti trovi in Cina, scegli open.bigmodel.cn. Altrimenti, scegli api.z.ai.", + "minimaxApiKey": "Chiave API MiniMax", + "getMiniMaxApiKey": "Ottieni chiave API MiniMax", + "minimaxBaseUrl": "Punto di ingresso MiniMax", "geminiApiKey": "Chiave API Gemini", "getGroqApiKey": "Ottieni chiave API Groq", "groqApiKey": "Chiave API Groq", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index dd390ca0dad9..823e201fb458 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Z AI APIキーを取得", "zaiEntrypoint": "Z AI エントリーポイント", "zaiEntrypointDescription": "お住まいの地域に応じて適切な API エントリーポイントを選択してください。中国にお住まいの場合は open.bigmodel.cn を選択してください。それ以外の場合は api.z.ai を選択してください。", + "minimaxApiKey": "MiniMax APIキー", + "getMiniMaxApiKey": "MiniMax APIキーを取得", + "minimaxBaseUrl": "MiniMax エントリーポイント", "geminiApiKey": "Gemini APIキー", "getGroqApiKey": "Groq APIキーを取得", "groqApiKey": "Groq APIキー", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 6cfd63ff3f04..7a21eefda856 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Z AI API 키 받기", "zaiEntrypoint": "Z AI 엔트리포인트", "zaiEntrypointDescription": "위치에 따라 적절한 API 엔트리포인트를 선택하세요. 중국에 있다면 open.bigmodel.cn을 선택하세요. 그렇지 않으면 api.z.ai를 선택하세요.", + "minimaxApiKey": "MiniMax API 키", + "getMiniMaxApiKey": "MiniMax API 키 받기", + "minimaxBaseUrl": "MiniMax 엔트리포인트", "geminiApiKey": "Gemini API 키", "getGroqApiKey": "Groq API 키 받기", "groqApiKey": "Groq API 키", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 974781f05632..9a770e85f8e1 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Z AI API-sleutel ophalen", "zaiEntrypoint": "Z AI-ingangspunt", "zaiEntrypointDescription": "Selecteer het juiste API-ingangspunt op basis van uw locatie. Als u zich in China bevindt, kies dan open.bigmodel.cn. Anders kiest u api.z.ai.", + "minimaxApiKey": "MiniMax API-sleutel", + "getMiniMaxApiKey": "MiniMax API-sleutel ophalen", + "minimaxBaseUrl": "MiniMax-ingangspunt", "geminiApiKey": "Gemini API-sleutel", "getGroqApiKey": "Groq API-sleutel ophalen", "groqApiKey": "Groq API-sleutel", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 6974cbd47e29..3addc2e78ce7 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Uzyskaj klucz API Z AI", "zaiEntrypoint": "Punkt wejścia Z AI", "zaiEntrypointDescription": "Wybierz odpowiedni punkt wejścia API w zależności od swojej lokalizacji. Jeśli jesteś w Chinach, wybierz open.bigmodel.cn. W przeciwnym razie wybierz api.z.ai.", + "minimaxApiKey": "Klucz API MiniMax", + "getMiniMaxApiKey": "Uzyskaj klucz API MiniMax", + "minimaxBaseUrl": "Punkt wejścia MiniMax", "geminiApiKey": "Klucz API Gemini", "getGroqApiKey": "Uzyskaj klucz API Groq", "groqApiKey": "Klucz API Groq", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index b449e7f35f57..dc9a9037bf36 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Obter chave de API Z AI", "zaiEntrypoint": "Ponto de entrada Z AI", "zaiEntrypointDescription": "Selecione o ponto de entrada da API apropriado com base na sua localização. Se você estiver na China, escolha open.bigmodel.cn. Caso contrário, escolha api.z.ai.", + "minimaxApiKey": "Chave de API MiniMax", + "getMiniMaxApiKey": "Obter chave de API MiniMax", + "minimaxBaseUrl": "Ponto de entrada MiniMax", "geminiApiKey": "Chave de API Gemini", "getGroqApiKey": "Obter chave de API Groq", "groqApiKey": "Chave de API Groq", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 7efd9d6cf487..ae69c206d5c3 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Получить Z AI API-ключ", "zaiEntrypoint": "Точка входа Z AI", "zaiEntrypointDescription": "Пожалуйста, выберите подходящую точку входа API в зависимости от вашего местоположения. Если вы находитесь в Китае, выберите open.bigmodel.cn. В противном случае выберите api.z.ai.", + "minimaxApiKey": "MiniMax API-ключ", + "getMiniMaxApiKey": "Получить MiniMax API-ключ", + "minimaxBaseUrl": "Точка входа MiniMax", "geminiApiKey": "Gemini API-ключ", "getGroqApiKey": "Получить Groq API-ключ", "groqApiKey": "Groq API-ключ", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 07d997769218..1a09bcc195d0 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Z AI API Anahtarı Al", "zaiEntrypoint": "Z AI Giriş Noktası", "zaiEntrypointDescription": "Konumunuza göre uygun API giriş noktasını seçin. Çin'de iseniz open.bigmodel.cn'yi seçin. Aksi takdirde api.z.ai'yi seçin.", + "minimaxApiKey": "MiniMax API Anahtarı", + "getMiniMaxApiKey": "MiniMax API Anahtarı Al", + "minimaxBaseUrl": "MiniMax Giriş Noktası", "geminiApiKey": "Gemini API Anahtarı", "getGroqApiKey": "Groq API Anahtarı Al", "groqApiKey": "Groq API Anahtarı", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index bb90147e1249..8a5efce8e634 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Lấy khóa API Z AI", "zaiEntrypoint": "Điểm vào Z AI", "zaiEntrypointDescription": "Vui lòng chọn điểm vào API phù hợp dựa trên vị trí của bạn. Nếu bạn ở Trung Quốc, hãy chọn open.bigmodel.cn. Ngược lại, hãy chọn api.z.ai.", + "minimaxApiKey": "Khóa API MiniMax", + "getMiniMaxApiKey": "Lấy khóa API MiniMax", + "minimaxBaseUrl": "Điểm vào MiniMax", "geminiApiKey": "Khóa API Gemini", "getGroqApiKey": "Lấy khóa API Groq", "groqApiKey": "Khóa API Groq", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index f6aa59145090..5dd3b877639c 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -293,9 +293,9 @@ "moonshotApiKey": "Moonshot API 密钥", "getMoonshotApiKey": "获取 Moonshot API 密钥", "moonshotBaseUrl": "Moonshot 服务站点", - "zaiApiKey": "Z AI API 密钥", - "getZaiApiKey": "获取 Z AI API 密钥", - "zaiEntrypoint": "Z AI 服务站点", + "minimaxApiKey": "MiniMax API 密钥", + "getMiniMaxApiKey": "获取 MiniMax API 密钥", + "minimaxBaseUrl": "MiniMax 服务站点", "zaiEntrypointDescription": "请根据您的位置选择适当的 API 服务站点。如果您在中国,请选择 open.bigmodel.cn。否则,请选择 api.z.ai。", "geminiApiKey": "Gemini API 密钥", "getGroqApiKey": "获取 Groq API 密钥", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index b2b1789ccc83..7d44fa544b14 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "取得 Z AI API 金鑰", "zaiEntrypoint": "Z AI 服務端點", "zaiEntrypointDescription": "請根據您的位置選擇適當的 API 服務端點。如果您在中國,請選擇 open.bigmodel.cn。否則,請選擇 api.z.ai。", + "minimaxApiKey": "MiniMax API 金鑰", + "getMiniMaxApiKey": "取得 MiniMax API 金鑰", + "minimaxBaseUrl": "MiniMax 服務端點", "geminiApiKey": "Gemini API 金鑰", "getGroqApiKey": "取得 Groq API 金鑰", "groqApiKey": "Groq API 金鑰", From 6e723429f1b5921c6f9a32c2243aaedd1997bcd9 Mon Sep 17 00:00:00 2001 From: xiaose Date: Thu, 23 Oct 2025 15:17:30 +0800 Subject: [PATCH 02/13] feat: param --- packages/types/src/providers/minimax.ts | 4 ++-- webview-ui/src/i18n/locales/fr/settings.json | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/types/src/providers/minimax.ts b/packages/types/src/providers/minimax.ts index cef8c19caf36..0a977491fdb6 100644 --- a/packages/types/src/providers/minimax.ts +++ b/packages/types/src/providers/minimax.ts @@ -8,8 +8,8 @@ export const minimaxDefaultModelId: MinimaxModelId = "MiniMax-M1" export const minimaxModels = { "MiniMax-M1": { - maxTokens: 25_600, - contextWindow: 1_000_192, + maxTokens: 40_000, + contextWindow: 1_000_000, supportsImages: false, supportsPromptCache: false, inputPrice: 0.4, diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 2d48631b5d1d..dd2f66fcec0a 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -297,6 +297,9 @@ "getZaiApiKey": "Obtenir la clé API Z AI", "zaiEntrypoint": "Point d'entrée Z AI", "zaiEntrypointDescription": "Veuillez sélectionner le point d'entrée API approprié en fonction de votre emplacement. Si vous êtes en Chine, choisissez open.bigmodel.cn. Sinon, choisissez api.z.ai.", + "minimaxApiKey": "Clé API MiniMax", + "getMiniMaxApiKey": "Obtenir la clé API MiniMax", + "minimaxBaseUrl": "Point d'entrée MiniMax", "geminiApiKey": "Clé API Gemini", "getGroqApiKey": "Obtenir la clé API Groq", "groqApiKey": "Clé API Groq", From 03e53847e39e857eb4726a49014c93aee723c149 Mon Sep 17 00:00:00 2001 From: xiaose Date: Sat, 25 Oct 2025 12:41:09 +0800 Subject: [PATCH 03/13] fea: add m2 --- packages/types/src/providers/minimax.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/types/src/providers/minimax.ts b/packages/types/src/providers/minimax.ts index 0a977491fdb6..0a95f16119ff 100644 --- a/packages/types/src/providers/minimax.ts +++ b/packages/types/src/providers/minimax.ts @@ -4,9 +4,19 @@ import type { ModelInfo } from "../model.js" // https://www.minimax.io/platform/document/text_api_intro // https://www.minimax.io/platform/document/pricing export type MinimaxModelId = keyof typeof minimaxModels -export const minimaxDefaultModelId: MinimaxModelId = "MiniMax-M1" +export const minimaxDefaultModelId: MinimaxModelId = "MiniMax-M2" export const minimaxModels = { + "MiniMax-M2": { + maxTokens: 128_000, + contextWindow: 192_000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0.3, + outputPrice: 1.2, + cacheWritesPrice: 0, + cacheReadsPrice: 0, + }, "MiniMax-M1": { maxTokens: 40_000, contextWindow: 1_000_000, From c6ab1f88d9fc8079d4a94adc78041f928526d727 Mon Sep 17 00:00:00 2001 From: xiaose Date: Sat, 25 Oct 2025 18:19:42 +0800 Subject: [PATCH 04/13] feat: format model --- packages/types/src/providers/minimax.ts | 10 ---------- src/api/providers/__tests__/minimax.spec.ts | 16 ++++++++-------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/types/src/providers/minimax.ts b/packages/types/src/providers/minimax.ts index 0a95f16119ff..a01f45e3bacd 100644 --- a/packages/types/src/providers/minimax.ts +++ b/packages/types/src/providers/minimax.ts @@ -17,16 +17,6 @@ export const minimaxModels = { cacheWritesPrice: 0, cacheReadsPrice: 0, }, - "MiniMax-M1": { - maxTokens: 40_000, - contextWindow: 1_000_000, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0.4, - outputPrice: 2.2, - cacheWritesPrice: 0, - cacheReadsPrice: 0, - }, } as const satisfies Record export const MINIMAX_DEFAULT_TEMPERATURE = 1.0 diff --git a/src/api/providers/__tests__/minimax.spec.ts b/src/api/providers/__tests__/minimax.spec.ts index c1aac1563841..552b4fff7630 100644 --- a/src/api/providers/__tests__/minimax.spec.ts +++ b/src/api/providers/__tests__/minimax.spec.ts @@ -61,7 +61,7 @@ describe("MiniMaxHandler", () => { }) it("should return specified model when valid model is provided", () => { - const testModelId: MinimaxModelId = "MiniMax-M1" + const testModelId: MinimaxModelId = "MiniMax-M2" const handlerWithModel = new MiniMaxHandler({ apiModelId: testModelId, minimaxApiKey: "test-minimax-api-key", @@ -71,8 +71,8 @@ describe("MiniMaxHandler", () => { expect(model.info).toEqual(minimaxModels[testModelId]) }) - it("should return MiniMax-M1 model with correct configuration", () => { - const testModelId: MinimaxModelId = "MiniMax-M1" + it("should return MiniMax-M2 model with correct configuration", () => { + const testModelId: MinimaxModelId = "MiniMax-M2" const handlerWithModel = new MiniMaxHandler({ apiModelId: testModelId, minimaxApiKey: "test-minimax-api-key", @@ -134,10 +134,10 @@ describe("MiniMaxHandler", () => { expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: undefined })) }) - it("should default to MiniMax-M1 model", () => { + it("should default to MiniMax-M2 model", () => { const handlerDefault = new MiniMaxHandler({ minimaxApiKey: "test-minimax-api-key" }) const model = handlerDefault.getModel() - expect(model.id).toBe("MiniMax-M1") + expect(model.id).toBe("MiniMax-M2") }) }) @@ -209,7 +209,7 @@ describe("MiniMaxHandler", () => { }) it("createMessage should pass correct parameters to MiniMax client", async () => { - const modelId: MinimaxModelId = "MiniMax-M1" + const modelId: MinimaxModelId = "" const modelInfo = minimaxModels[modelId] const handlerWithModel = new MiniMaxHandler({ apiModelId: modelId, @@ -269,8 +269,8 @@ describe("MiniMaxHandler", () => { }) describe("Model Configuration", () => { - it("should correctly configure MiniMax-M1 model properties", () => { - const model = minimaxModels["MiniMax-M1"] + it("should correctly configure MiniMax-M2 model properties", () => { + const model = minimaxModels["MiniMax-M2"] expect(model.maxTokens).toBe(25_600) expect(model.contextWindow).toBe(1_000_192) expect(model.supportsImages).toBe(false) From 8cd9e534aa9fcc3c5b50badee7b16228bc4e1add Mon Sep 17 00:00:00 2001 From: xiaose Date: Sat, 25 Oct 2025 18:22:36 +0800 Subject: [PATCH 05/13] feat: format model --- src/api/providers/__tests__/minimax.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/providers/__tests__/minimax.spec.ts b/src/api/providers/__tests__/minimax.spec.ts index 552b4fff7630..526d9b64f791 100644 --- a/src/api/providers/__tests__/minimax.spec.ts +++ b/src/api/providers/__tests__/minimax.spec.ts @@ -209,7 +209,7 @@ describe("MiniMaxHandler", () => { }) it("createMessage should pass correct parameters to MiniMax client", async () => { - const modelId: MinimaxModelId = "" + const modelId: MinimaxModelId = "MiniMax-M2" const modelInfo = minimaxModels[modelId] const handlerWithModel = new MiniMaxHandler({ apiModelId: modelId, From d8c44a4f4ac310165faac5626b1584ef9ad7e583 Mon Sep 17 00:00:00 2001 From: xiaose Date: Sat, 25 Oct 2025 19:10:21 +0800 Subject: [PATCH 06/13] feat: format test --- src/api/providers/__tests__/minimax.spec.ts | 23 ++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/api/providers/__tests__/minimax.spec.ts b/src/api/providers/__tests__/minimax.spec.ts index 526d9b64f791..f831f9ffa778 100644 --- a/src/api/providers/__tests__/minimax.spec.ts +++ b/src/api/providers/__tests__/minimax.spec.ts @@ -80,8 +80,8 @@ describe("MiniMaxHandler", () => { const model = handlerWithModel.getModel() expect(model.id).toBe(testModelId) expect(model.info).toEqual(minimaxModels[testModelId]) - expect(model.info.contextWindow).toBe(1_000_192) - expect(model.info.maxTokens).toBe(25_600) + expect(model.info.contextWindow).toBe(192_000) + expect(model.info.maxTokens).toBe(128_000) expect(model.info.supportsPromptCache).toBe(false) }) }) @@ -129,11 +129,6 @@ describe("MiniMaxHandler", () => { expect(model.info).toEqual(minimaxModels[minimaxDefaultModelId]) }) - it("should use undefined as default API key when none is specified", () => { - new MiniMaxHandler({}) - expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: undefined })) - }) - it("should default to MiniMax-M2 model", () => { const handlerDefault = new MiniMaxHandler({ minimaxApiKey: "test-minimax-api-key" }) const model = handlerDefault.getModel() @@ -236,7 +231,7 @@ describe("MiniMaxHandler", () => { expect.objectContaining({ model: modelId, max_tokens: modelInfo.maxTokens, - temperature: 0, + temperature: 1, messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), stream: true, stream_options: { include_usage: true }, @@ -245,7 +240,7 @@ describe("MiniMaxHandler", () => { ) }) - it("should use temperature 0 by default", async () => { + it("should use temperature 1 by default", async () => { mockCreate.mockImplementationOnce(() => { return { [Symbol.asyncIterator]: () => ({ @@ -261,7 +256,7 @@ describe("MiniMaxHandler", () => { expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ - temperature: 0, + temperature: 1, }), undefined, ) @@ -271,12 +266,12 @@ describe("MiniMaxHandler", () => { describe("Model Configuration", () => { it("should correctly configure MiniMax-M2 model properties", () => { const model = minimaxModels["MiniMax-M2"] - expect(model.maxTokens).toBe(25_600) - expect(model.contextWindow).toBe(1_000_192) + expect(model.maxTokens).toBe(128_000) + expect(model.contextWindow).toBe(192_000) expect(model.supportsImages).toBe(false) expect(model.supportsPromptCache).toBe(false) - expect(model.inputPrice).toBe(0.4) - expect(model.outputPrice).toBe(2.2) + expect(model.inputPrice).toBe(0.3) + expect(model.outputPrice).toBe(1.2) }) }) }) From 3e405a857ce229a9e283449fcc78ed1a8cfd6d5c Mon Sep 17 00:00:00 2001 From: xiaose Date: Sat, 25 Oct 2025 19:15:36 +0800 Subject: [PATCH 07/13] feat: doc --- webview-ui/src/i18n/locales/zh-CN/settings.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 004b19c6d416..f8cdfb1be5e1 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -296,6 +296,9 @@ "minimaxApiKey": "MiniMax API 密钥", "getMiniMaxApiKey": "获取 MiniMax API 密钥", "minimaxBaseUrl": "MiniMax 服务站点", + "zaiApiKey": "Z AI API 密钥", + "getZaiApiKey": "获取 Z AI API 密钥", + "zaiEntrypoint": "Z AI 服务站点", "zaiEntrypointDescription": "请根据您的位置选择适当的 API 服务站点。如果您在中国,请选择 open.bigmodel.cn。否则,请选择 api.z.ai。", "geminiApiKey": "Gemini API 密钥", "getGroqApiKey": "获取 Groq API 密钥", From 7fa7e161d41967c7b3050a8f0de27309c54176bc Mon Sep 17 00:00:00 2001 From: xiaose Date: Tue, 28 Oct 2025 23:09:17 +0800 Subject: [PATCH 08/13] feat: fix test --- src/api/providers/__tests__/minimax.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/providers/__tests__/minimax.spec.ts b/src/api/providers/__tests__/minimax.spec.ts index f831f9ffa778..317b992a3273 100644 --- a/src/api/providers/__tests__/minimax.spec.ts +++ b/src/api/providers/__tests__/minimax.spec.ts @@ -230,7 +230,7 @@ describe("MiniMaxHandler", () => { expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ model: modelId, - max_tokens: modelInfo.maxTokens, + max_tokens: Math.min(modelInfo.maxTokens, Math.ceil(modelInfo.contextWindow * 0.2)), temperature: 1, messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), stream: true, From d291030de514cb2611492a45417084b79c45b9d8 Mon Sep 17 00:00:00 2001 From: xiaose Date: Wed, 29 Oct 2025 13:01:43 +0800 Subject: [PATCH 09/13] feat: format code --- packages/types/src/providers/minimax.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/types/src/providers/minimax.ts b/packages/types/src/providers/minimax.ts index a01f45e3bacd..2dc458e5fa45 100644 --- a/packages/types/src/providers/minimax.ts +++ b/packages/types/src/providers/minimax.ts @@ -16,6 +16,8 @@ export const minimaxModels = { outputPrice: 1.2, cacheWritesPrice: 0, cacheReadsPrice: 0, + description: + "MiniMax M2, a model born for Agents and code. At only 8% of the price of Claude Sonnet and twice the speed, it's available for free for a limited time!", }, } as const satisfies Record From 7a860f6bf177366ac94326e96e2ccb3e6a7e4ee2 Mon Sep 17 00:00:00 2001 From: xiaose Date: Wed, 29 Oct 2025 13:03:16 +0800 Subject: [PATCH 10/13] feat: code --- packages/types/src/providers/minimax.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/providers/minimax.ts b/packages/types/src/providers/minimax.ts index 2dc458e5fa45..ca0879a1f235 100644 --- a/packages/types/src/providers/minimax.ts +++ b/packages/types/src/providers/minimax.ts @@ -17,7 +17,7 @@ export const minimaxModels = { cacheWritesPrice: 0, cacheReadsPrice: 0, description: - "MiniMax M2, a model born for Agents and code. At only 8% of the price of Claude Sonnet and twice the speed, it's available for free for a limited time!", + "MiniMax M2, a model born for Agents and code, featuring Top-tier Coding Capabilities, Powerful Agentic Performance, and Ultimate Cost-Effectiveness & Speed.", }, } as const satisfies Record From a40c25fbf1d58814ffcddc8248e4a83362a5698f Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 29 Oct 2025 09:33:00 -0400 Subject: [PATCH 11/13] Handle thinking --- src/api/providers/__tests__/minimax.spec.ts | 37 ++++++++++++++++++ src/api/providers/minimax.ts | 43 +++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/api/providers/__tests__/minimax.spec.ts b/src/api/providers/__tests__/minimax.spec.ts index 317b992a3273..bdd2fb267899 100644 --- a/src/api/providers/__tests__/minimax.spec.ts +++ b/src/api/providers/__tests__/minimax.spec.ts @@ -178,6 +178,43 @@ describe("MiniMaxHandler", () => { expect(firstChunk.value).toEqual({ type: "text", text: testContent }) }) + it("should handle reasoning tags () from stream", async () => { + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: vitest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: "Let me think" } }] }, + }) + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: " about this" } }] }, + }) + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: "The answer is 42" } }] }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const chunks = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + // XmlMatcher yields chunks as they're processed + expect(chunks).toEqual([ + { type: "reasoning", text: "Let me think" }, + { type: "reasoning", text: " about this" }, + { type: "text", text: "The answer is 42" }, + ]) + }) + it("createMessage should yield usage data from stream", async () => { mockCreate.mockImplementationOnce(() => { return { diff --git a/src/api/providers/minimax.ts b/src/api/providers/minimax.ts index 8a8e8c14e5b4..23722f597648 100644 --- a/src/api/providers/minimax.ts +++ b/src/api/providers/minimax.ts @@ -1,6 +1,10 @@ +import { Anthropic } from "@anthropic-ai/sdk" import { type MinimaxModelId, minimaxDefaultModelId, minimaxModels } from "@roo-code/types" import type { ApiHandlerOptions } from "../../shared/api" +import { XmlMatcher } from "../../utils/xml-matcher" +import { ApiStream } from "../transform/stream" +import type { ApiHandlerCreateMessageMetadata } from "../index" import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" @@ -16,4 +20,43 @@ export class MiniMaxHandler extends BaseOpenAiCompatibleProvider defaultTemperature: 1.0, }) } + + override async *createMessage( + systemPrompt: string, + messages: Anthropic.Messages.MessageParam[], + metadata?: ApiHandlerCreateMessageMetadata, + ): ApiStream { + const stream = await this.createStream(systemPrompt, messages, metadata) + + const matcher = new XmlMatcher( + "think", + (chunk) => + ({ + type: chunk.matched ? "reasoning" : "text", + text: chunk.data, + }) as const, + ) + + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta + + if (delta?.content) { + for (const matcherChunk of matcher.update(delta.content)) { + yield matcherChunk + } + } + + if (chunk.usage) { + yield { + type: "usage", + inputTokens: chunk.usage.prompt_tokens || 0, + outputTokens: chunk.usage.completion_tokens || 0, + } + } + } + + for (const chunk of matcher.final()) { + yield chunk + } + } } From 903255d25a400c42cf0d3689db0e6843bf6bf479 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 29 Oct 2025 09:34:38 -0400 Subject: [PATCH 12/13] Update packages/types/src/providers/minimax.ts --- packages/types/src/providers/minimax.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/providers/minimax.ts b/packages/types/src/providers/minimax.ts index ca0879a1f235..825431b48242 100644 --- a/packages/types/src/providers/minimax.ts +++ b/packages/types/src/providers/minimax.ts @@ -8,7 +8,7 @@ export const minimaxDefaultModelId: MinimaxModelId = "MiniMax-M2" export const minimaxModels = { "MiniMax-M2": { - maxTokens: 128_000, + maxTokens: 16_384, contextWindow: 192_000, supportsImages: false, supportsPromptCache: false, From f7db71b6160414f47e12be0a6e57e79d1d12d158 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 29 Oct 2025 09:39:28 -0400 Subject: [PATCH 13/13] Fix tests --- src/api/providers/__tests__/minimax.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/providers/__tests__/minimax.spec.ts b/src/api/providers/__tests__/minimax.spec.ts index bdd2fb267899..c488aea8812d 100644 --- a/src/api/providers/__tests__/minimax.spec.ts +++ b/src/api/providers/__tests__/minimax.spec.ts @@ -81,7 +81,7 @@ describe("MiniMaxHandler", () => { expect(model.id).toBe(testModelId) expect(model.info).toEqual(minimaxModels[testModelId]) expect(model.info.contextWindow).toBe(192_000) - expect(model.info.maxTokens).toBe(128_000) + expect(model.info.maxTokens).toBe(16_384) expect(model.info.supportsPromptCache).toBe(false) }) }) @@ -303,7 +303,7 @@ describe("MiniMaxHandler", () => { describe("Model Configuration", () => { it("should correctly configure MiniMax-M2 model properties", () => { const model = minimaxModels["MiniMax-M2"] - expect(model.maxTokens).toBe(128_000) + expect(model.maxTokens).toBe(16_384) expect(model.contextWindow).toBe(192_000) expect(model.supportsImages).toBe(false) expect(model.supportsPromptCache).toBe(false)