diff --git a/cli/src/config/schema.json b/cli/src/config/schema.json index 55892de7f9b..fa3791374e8 100644 --- a/cli/src/config/schema.json +++ b/cli/src/config/schema.json @@ -257,6 +257,7 @@ "qwen-code", "gemini-cli", "zai", + "minimax", "unbound", "requesty", "roo", @@ -1207,6 +1208,27 @@ } } }, + { + "if": { + "properties": { "provider": { "const": "minimax" } } + }, + "then": { + "properties": { + "minimaxBaseUrl": { + "type": "string", + "description": "MiniMax base URL" + }, + "minimaxApiKey": { + "type": "string", + "description": "MiniMax API key" + }, + "apiModelId": { + "type": "string", + "description": "MiniMax model ID" + } + } + } + }, { "if": { "properties": { "provider": { "const": "doubao" } } @@ -1294,6 +1316,48 @@ } } }, + { + "if": { + "properties": { + "provider": { "const": "minimax" }, + "minimaxBaseUrl": { "type": "string", "minLength": 1 } + }, + "required": ["minimaxBaseUrl"] + }, + "then": { + "properties": { + "minimaxBaseUrl": { "minLength": 1 } + } + } + }, + { + "if": { + "properties": { + "provider": { "const": "minimax" }, + "minimaxApiKey": { "type": "string", "minLength": 1 } + }, + "required": ["minimaxApiKey"] + }, + "then": { + "properties": { + "minimaxApiKey": { "minLength": 10 } + } + } + }, + { + "if": { + "properties": { + "provider": { "const": "minimax" }, + "apiModelId": { "type": "string", "minLength": 1 } + }, + "required": ["apiModelId"] + }, + "then": { + "properties": { + "apiModelId": { "minLength": 1 } + } + } + }, { "if": { "properties": { "provider": { "const": "chutes" } } diff --git a/cli/src/constants/providers/__tests__/models.test.ts b/cli/src/constants/providers/__tests__/models.test.ts index b6b5dabaedc..b126f9b3ace 100644 --- a/cli/src/constants/providers/__tests__/models.test.ts +++ b/cli/src/constants/providers/__tests__/models.test.ts @@ -42,6 +42,7 @@ describe("Static Provider Models", () => { "cerebras", "sambanova", "zai", + "minimax", "fireworks", "featherless", "roo", diff --git a/cli/src/constants/providers/labels.ts b/cli/src/constants/providers/labels.ts index 24b4f03b269..7f93ad901dd 100644 --- a/cli/src/constants/providers/labels.ts +++ b/cli/src/constants/providers/labels.ts @@ -36,6 +36,7 @@ export const PROVIDER_LABELS: Record = { "qwen-code": "Qwen Code", "gemini-cli": "Gemini CLI", zai: "Zai", + minimax: "MiniMax", unbound: "Unbound", requesty: "Requesty", roo: "Roo", diff --git a/cli/src/constants/providers/models.ts b/cli/src/constants/providers/models.ts index baa5e157366..8d482d6ebce 100644 --- a/cli/src/constants/providers/models.ts +++ b/cli/src/constants/providers/models.ts @@ -45,6 +45,8 @@ import { claudeCodeDefaultModelId, geminiCliModels, geminiCliDefaultModelId, + minimaxModels, + minimaxDefaultModelId, } from "@roo-code/types" /** @@ -117,6 +119,7 @@ export const PROVIDER_TO_ROUTER_NAME: Record = moonshot: null, deepseek: null, doubao: null, + minimax: null, "qwen-code": null, "human-relay": null, "fake-ai": null, @@ -162,6 +165,7 @@ export const PROVIDER_MODEL_FIELD: Record = { moonshot: null, deepseek: null, doubao: null, + minimax: null, "qwen-code": null, "human-relay": null, "fake-ai": null, @@ -239,6 +243,7 @@ export const DEFAULT_MODEL_IDS: Partial> = { sambanova: sambaNovaDefaultModelId, featherless: featherlessDefaultModelId, deepinfra: "deepseek-ai/DeepSeek-R1-0528", + minimax: "MiniMax-M2", zai: internationalZAiDefaultModelId, roo: rooDefaultModelId, "gemini-cli": geminiCliDefaultModelId, @@ -302,6 +307,11 @@ export function getModelsByProvider(params: { models: moonshotModels as ModelRecord, defaultModel: moonshotDefaultModelId, } + case "minimax": + return { + models: minimaxModels as ModelRecord, + defaultModel: minimaxDefaultModelId, + } case "deepseek": return { models: deepSeekModels as ModelRecord, diff --git a/cli/src/constants/providers/settings.ts b/cli/src/constants/providers/settings.ts index 82071db233b..e6a2dcdf821 100644 --- a/cli/src/constants/providers/settings.ts +++ b/cli/src/constants/providers/settings.ts @@ -410,6 +410,18 @@ export const FIELD_REGISTRY: Record = { placeholder: "Enter API line...", }, + // Minimax fields + minimaxBaseUrl: { + label: "Base URL", + type: "text", + placeholder: "Enter MiniMax base URL...", + }, + minimaxApiKey: { + label: "API Key", + type: "password", + placeholder: "Enter MiniMax API key...", + }, + // Unbound fields unboundApiKey: { label: "API Key", @@ -767,7 +779,11 @@ export const getProviderSettings = (provider: ProviderName, config: ProviderSett type: "text", }, ] - + case "minimax": + return [ + createFieldConfig("minimaxBaseUrl", config, "https://api.minimax.io/anthropic"), + createFieldConfig("minimaxApiKey", config), + ] case "fake-ai": return [ { @@ -825,6 +841,7 @@ export const PROVIDER_DEFAULT_MODELS: Record = { "vercel-ai-gateway": "gpt-4o", "virtual-quota-fallback": "gpt-4o", "human-relay": "human", + minimax: "MiniMax-M2", "fake-ai": "fake-model", } diff --git a/cli/src/constants/providers/validation.ts b/cli/src/constants/providers/validation.ts index 1637a081a07..9b6101cc79d 100644 --- a/cli/src/constants/providers/validation.ts +++ b/cli/src/constants/providers/validation.ts @@ -44,4 +44,5 @@ export const PROVIDER_REQUIRED_FIELDS: Record = { vertex: [], // Has special validation logic (either/or fields) "vscode-lm": [], // Has nested object validation "virtual-quota-fallback": [], // Has array validation + minimax: ["minimaxBaseUrl", "minimaxApiKey", "apiModelId"], } diff --git a/cli/src/services/logs.ts b/cli/src/services/logs.ts index 8ac6190fb14..2d7fc14dad5 100644 --- a/cli/src/services/logs.ts +++ b/cli/src/services/logs.ts @@ -78,7 +78,7 @@ export class LogsService { stack: error.stack, // Include any additional enumerable properties ...Object.getOwnPropertyNames(error) - .filter(key => key !== "message" && key !== "name" && key !== "stack") + .filter((key) => key !== "message" && key !== "name" && key !== "stack") .reduce( (acc, key) => { acc[key] = (error as any)[key] diff --git a/cli/src/types/messages.ts b/cli/src/types/messages.ts index d4acbd296ac..2c0ec856d6e 100644 --- a/cli/src/types/messages.ts +++ b/cli/src/types/messages.ts @@ -103,6 +103,7 @@ export type ProviderName = | "io-intelligence" | "roo" | "vercel-ai-gateway" + | "minimax" // Provider Settings Entry for profile metadata export interface ProviderSettingsEntry { @@ -320,6 +321,10 @@ export interface ProviderSettings { vercelAiGatewayApiKey?: string vercelAiGatewayModelId?: string + // MiniMax AI + minimaxBaseUrl?: "https://api.minimax.io/anthropic" | "https://api.minimaxi.com/anthropic" + minimaxApiKey?: string + // Allow additional fields for extensibility [key: string]: any } diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index f4d728d4ff3..cfd03b86b8e 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -220,6 +220,7 @@ export const SECRET_STATE_KEYS = [ "deepInfraApiKey", "codeIndexOpenAiKey", "codeIndexQdrantApiKey", + "minimaxApiKey", // kilocode_change start "kilocodeToken", "syntheticApiKey", diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 5ef923a0f65..5fd14b722a7 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -28,6 +28,7 @@ import { vscodeLlmModels, xaiModels, internationalZAiModels, + minimaxModels, } from "./providers/index.js" import { toolUseStylesSchema } from "./kilocode/native-function-calling.js" @@ -141,6 +142,7 @@ export const providerNames = [ "groq", "mistral", "moonshot", + "minimax", "openai-native", "qwen-code", "roo", @@ -411,6 +413,13 @@ const sambaNovaSchema = apiModelIdProviderModelSchema.extend({ sambaNovaApiKey: z.string().optional(), }) +const minimaxSchema = apiModelIdProviderModelSchema.extend({ + minimaxBaseUrl: z + .union([z.literal("https://api.minimax.io/anthropic"), z.literal("https://api.minimaxi.com/anthropic")]) + .optional(), + minimaxApiKey: z.string().optional(), +}) + // kilocode_change start const ovhcloudSchema = baseProviderSettingsSchema.extend({ ovhCloudAiEndpointsApiKey: z.string().optional(), @@ -518,6 +527,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv humanRelaySchema.merge(z.object({ apiProvider: z.literal("human-relay") })), fakeAiSchema.merge(z.object({ apiProvider: z.literal("fake-ai") })), xaiSchema.merge(z.object({ apiProvider: z.literal("xai") })), + minimaxSchema.merge(z.object({ apiProvider: z.literal("minimax") })), // kilocode_change start geminiCliSchema.merge(z.object({ apiProvider: z.literal("gemini-cli") })), kilocodeSchema.merge(z.object({ apiProvider: z.literal("kilocode") })), @@ -570,6 +580,7 @@ export const providerSettingsSchema = z.object({ ...humanRelaySchema.shape, ...fakeAiSchema.shape, ...xaiSchema.shape, + ...minimaxSchema.shape, ...groqSchema.shape, ...huggingFaceSchema.shape, ...chutesSchema.shape, @@ -660,6 +671,7 @@ export const modelIdKeysByProvider: Record = { unbound: "unboundModelId", requesty: "requestyModelId", xai: "apiModelId", + minimax: "apiModelId", groq: "apiModelId", chutes: "apiModelId", litellm: "litellmModelId", @@ -799,6 +811,11 @@ export const MODELS_BY_PROVIDER: Record< }, xai: { id: "xai", label: "xAI (Grok)", models: Object.keys(xaiModels) }, zai: { id: "zai", label: "Zai", models: Object.keys(internationalZAiModels) }, + minimax: { + id: "minimax", + label: "MiniMax", + models: Object.keys(minimaxModels), + }, // Dynamic providers; models pulled from remote APIs. glama: { id: "glama", label: "Glama", models: [] }, diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index 087cfb4e6ef..4cfefd8103f 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -35,3 +35,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 00000000000..d331012a585 --- /dev/null +++ b/packages/types/src/providers/minimax.ts @@ -0,0 +1,23 @@ +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-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, + }, +} as const satisfies Record + +export const MINIMAX_DEFAULT_TEMPERATURE = 1.0 +export const MINIMAX_DEFAULT_MAX_TOKENS = 16384 diff --git a/src/api/index.ts b/src/api/index.ts index 029bb50e1c2..a49e807fae0 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -41,6 +41,7 @@ import { DoubaoHandler, ZAiHandler, FireworksHandler, + MiniMaxHandler, SyntheticHandler, // kilocode_change RooHandler, FeatherlessHandler, @@ -202,6 +203,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) // kilocode_change start case "ovhcloud": return new OVHcloudAIEndpointsHandler(options) diff --git a/src/api/providers/__tests__/minimax.spec.ts b/src/api/providers/__tests__/minimax.spec.ts new file mode 100644 index 00000000000..e4f900293aa --- /dev/null +++ b/src/api/providers/__tests__/minimax.spec.ts @@ -0,0 +1,336 @@ +// npx vitest run src/api/providers/__tests__/minimax.spec.ts + +import { MiniMaxHandler } from "../minimax" +import { ApiHandlerOptions } from "../../../shared/api" +import { + minimaxDefaultModelId, + minimaxModels, + MINIMAX_DEFAULT_TEMPERATURE, + MINIMAX_DEFAULT_MAX_TOKENS, +} from "@roo-code/types" + +const mockCreate = vitest.fn() + +vitest.mock("@anthropic-ai/sdk", () => { + const mockAnthropicConstructor = vitest.fn().mockImplementation(() => ({ + messages: { + create: mockCreate.mockImplementation(async (options) => { + if (!options.stream) { + return { + id: "test-completion", + content: [{ type: "text", text: "Test response from MiniMax" }], + role: "assistant", + model: options.model, + usage: { + input_tokens: 10, + output_tokens: 5, + }, + } + } + return { + async *[Symbol.asyncIterator]() { + yield { + type: "message_start", + message: { + usage: { + input_tokens: 100, + output_tokens: 50, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + }, + }, + } + yield { + type: "content_block_start", + index: 0, + content_block: { + type: "text", + text: "Hello", + }, + } + yield { + type: "content_block_delta", + delta: { + type: "text_delta", + text: " from MiniMax", + }, + } + }, + } + }), + countTokens: vitest.fn().mockResolvedValue({ input_tokens: 42 }), + }, + })) + + return { + Anthropic: mockAnthropicConstructor, + } +}) + +// Import after mock +import { Anthropic } from "@anthropic-ai/sdk" + +const mockAnthropicConstructor = vitest.mocked(Anthropic) + +describe("MiniMaxHandler", () => { + let handler: MiniMaxHandler + let mockOptions: ApiHandlerOptions + + beforeEach(() => { + mockOptions = { + minimaxApiKey: "test-minimax-api-key", + apiModelId: minimaxDefaultModelId, + } + handler = new MiniMaxHandler(mockOptions) + vitest.clearAllMocks() + }) + + describe("constructor", () => { + it("should initialize with provided options", () => { + expect(handler).toBeInstanceOf(MiniMaxHandler) + expect(handler.getModel().id).toBe(minimaxDefaultModelId) + }) + + it("should use default international base URL", () => { + new MiniMaxHandler(mockOptions) + expect(mockAnthropicConstructor).toHaveBeenCalledWith( + expect.objectContaining({ + baseURL: "https://api.minimax.io/anthropic", + apiKey: "test-minimax-api-key", + }), + ) + }) + + it("should use custom base URL if provided", () => { + const customBaseUrl = "https://api.minimaxi.com/anthropic" + new MiniMaxHandler({ + ...mockOptions, + minimaxBaseUrl: customBaseUrl, + }) + expect(mockAnthropicConstructor).toHaveBeenCalledWith( + expect.objectContaining({ + baseURL: customBaseUrl, + }), + ) + }) + + it("should use China base URL when provided", () => { + const chinaBaseUrl = "https://api.minimaxi.com/anthropic" + new MiniMaxHandler({ + ...mockOptions, + minimaxBaseUrl: chinaBaseUrl, + }) + expect(mockAnthropicConstructor).toHaveBeenCalledWith( + expect.objectContaining({ + baseURL: chinaBaseUrl, + apiKey: "test-minimax-api-key", + }), + ) + }) + + it("should initialize without API key", () => { + const handlerWithoutKey = new MiniMaxHandler({ + ...mockOptions, + minimaxApiKey: undefined, + }) + expect(handlerWithoutKey).toBeInstanceOf(MiniMaxHandler) + }) + }) + + describe("createMessage", () => { + const systemPrompt = "You are a helpful assistant." + + it("should stream messages successfully", async () => { + const stream = handler.createMessage(systemPrompt, [ + { + role: "user", + content: [{ type: "text" as const, text: "Hello MiniMax" }], + }, + ]) + + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + // Verify usage information + const usageChunk = chunks.find((chunk) => chunk.type === "usage") + expect(usageChunk).toBeDefined() + expect(usageChunk?.inputTokens).toBe(100) + expect(usageChunk?.outputTokens).toBe(50) + + // Verify text content + const textChunks = chunks.filter((chunk) => chunk.type === "text") + expect(textChunks).toHaveLength(2) + expect(textChunks[0].text).toBe("Hello") + expect(textChunks[1].text).toBe(" from MiniMax") + + // Verify API call + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: minimaxDefaultModelId, + max_tokens: 38400, + temperature: MINIMAX_DEFAULT_TEMPERATURE, + system: [{ text: systemPrompt, type: "text" }], + stream: true, + }), + ) + }) + + it("should handle multiple messages", async () => { + const stream = handler.createMessage(systemPrompt, [ + { + role: "user", + content: [{ type: "text" as const, text: "First message" }], + }, + { + role: "assistant", + content: [{ type: "text" as const, text: "Response" }], + }, + { + role: "user", + content: [{ type: "text" as const, text: "Second message" }], + }, + ]) + + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(chunks.length).toBeGreaterThan(0) + expect(mockCreate).toHaveBeenCalled() + }) + }) + + describe("completePrompt", () => { + it("should complete prompt successfully", async () => { + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("Test response from MiniMax") + expect(mockCreate).toHaveBeenCalledWith({ + model: minimaxDefaultModelId, + messages: [{ role: "user", content: "Test prompt" }], + max_tokens: MINIMAX_DEFAULT_MAX_TOKENS, + temperature: MINIMAX_DEFAULT_TEMPERATURE, + thinking: undefined, + stream: false, + }) + }) + + it("should handle API errors", async () => { + mockCreate.mockRejectedValueOnce(new Error("MiniMax API Error")) + await expect(handler.completePrompt("Test prompt")).rejects.toThrow("MiniMax API Error") + }) + + it("should handle non-text content", async () => { + mockCreate.mockImplementationOnce(async () => ({ + content: [{ type: "image" }], + })) + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("") + }) + + it("should handle empty response", async () => { + mockCreate.mockImplementationOnce(async () => ({ + content: [{ type: "text", text: "" }], + })) + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("") + }) + }) + + describe("getModel", () => { + it("should return default model if no model ID is provided", () => { + const handlerWithoutModel = new MiniMaxHandler({ + ...mockOptions, + apiModelId: undefined, + }) + const model = handlerWithoutModel.getModel() + expect(model.id).toBe(minimaxDefaultModelId) + expect(model.info).toBeDefined() + }) + + it("should return MiniMax-M2 as default model", () => { + const model = handler.getModel() + expect(model.id).toBe("MiniMax-M2") + expect(model.info).toEqual(minimaxModels["MiniMax-M2"]) + }) + + it("should return correct model configuration for MiniMax-M2", () => { + const model = handler.getModel() + expect(model.id).toBe("MiniMax-M2") + expect(model.info.maxTokens).toBe(128_000) + expect(model.info.contextWindow).toBe(192_000) + expect(model.info.supportsImages).toBe(false) + expect(model.info.supportsPromptCache).toBe(false) + expect(model.info.inputPrice).toBe(0.3) + expect(model.info.outputPrice).toBe(1.2) + }) + + it("should use correct default temperature", () => { + const model = handler.getModel() + expect(model.temperature).toBe(0) + }) + + it("should use correct default max tokens", () => { + const model = handler.getModel() + expect(model.maxTokens).toBe(38400) + }) + }) + + describe("countTokens", () => { + it("should count tokens using Anthropic API", async () => { + // Create a fresh handler to get the Anthropic instance + const testHandler = new MiniMaxHandler(mockOptions) + const anthropicInstance = + mockAnthropicConstructor.mock.results[mockAnthropicConstructor.mock.results.length - 1]?.value + + const content = [{ type: "text" as const, text: "Test content for MiniMax" }] + const result = await testHandler.countTokens(content) + + expect(result).toBe(42) + expect(anthropicInstance?.messages.countTokens).toHaveBeenCalledWith({ + model: minimaxDefaultModelId, + messages: [{ role: "user", content }], + }) + }) + + it("should fallback to base implementation on error", async () => { + // Create a fresh handler to get the Anthropic instance + const testHandler = new MiniMaxHandler(mockOptions) + const anthropicInstance = + mockAnthropicConstructor.mock.results[mockAnthropicConstructor.mock.results.length - 1]?.value + + if (anthropicInstance) { + anthropicInstance.messages.countTokens.mockRejectedValueOnce(new Error("API error")) + } + + const content = [{ type: "text" as const, text: "Test content" }] + const result = await testHandler.countTokens(content) + + // Should not throw and return some number from fallback + expect(typeof result).toBe("number") + }) + }) + + describe("Model Configuration", () => { + it("should have correct model configuration", () => { + expect(minimaxDefaultModelId).toBe("MiniMax-M2") + expect(minimaxModels["MiniMax-M2"]).toEqual({ + maxTokens: 128_000, + contextWindow: 192_000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0.3, + outputPrice: 1.2, + cacheWritesPrice: 0, + cacheReadsPrice: 0, + }) + }) + + it("should have correct default constants", () => { + expect(MINIMAX_DEFAULT_TEMPERATURE).toBe(1.0) + expect(MINIMAX_DEFAULT_MAX_TOKENS).toBe(16384) + }) + }) +}) diff --git a/src/api/providers/index.ts b/src/api/providers/index.ts index 1e87b24e4ea..46454004c83 100644 --- a/src/api/providers/index.ts +++ b/src/api/providers/index.ts @@ -40,3 +40,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 00000000000..2f6398bf0a3 --- /dev/null +++ b/src/api/providers/minimax.ts @@ -0,0 +1,249 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming" + +import { + type ModelInfo, + MINIMAX_DEFAULT_MAX_TOKENS, + MINIMAX_DEFAULT_TEMPERATURE, + MinimaxModelId, + minimaxDefaultModelId, + minimaxModels, +} from "@roo-code/types" + +import type { ApiHandlerOptions } from "../../shared/api" + +import { ApiStream } from "../transform/stream" +import { getModelParams } from "../transform/model-params" + +import { BaseProvider } from "./base-provider" +import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index" +import { calculateApiCostAnthropic } from "../../shared/cost" + +export class MiniMaxHandler extends BaseProvider implements SingleCompletionHandler { + private options: ApiHandlerOptions + private client: Anthropic + + constructor(options: ApiHandlerOptions) { + super() + this.options = options + + this.client = new Anthropic({ + baseURL: this.options.minimaxBaseUrl || "https://api.minimax.io/anthropic", + apiKey: this.options.minimaxApiKey, + }) + } + + async *createMessage( + systemPrompt: string, + messages: Anthropic.Messages.MessageParam[], + metadata?: ApiHandlerCreateMessageMetadata, + ): ApiStream { + let stream: AnthropicStream + let { id: modelId, maxTokens } = this.getModel() + + stream = await this.client.messages.create({ + model: modelId, + max_tokens: maxTokens ?? MINIMAX_DEFAULT_MAX_TOKENS, + temperature: MINIMAX_DEFAULT_TEMPERATURE, + system: [{ text: systemPrompt, type: "text" }], + messages, + stream: true, + }) + + let inputTokens = 0 + let outputTokens = 0 + let cacheWriteTokens = 0 + let cacheReadTokens = 0 + let thinkingDeltaAccumulator = "" + let thinkText = "" + let thinkSignature = "" + for await (const chunk of stream) { + switch (chunk.type) { + case "message_start": { + // Tells us cache reads/writes/input/output. + const { + input_tokens = 0, + output_tokens = 0, + cache_creation_input_tokens, + cache_read_input_tokens, + } = chunk.message.usage + + yield { + type: "usage", + inputTokens: input_tokens, + outputTokens: output_tokens, + cacheWriteTokens: cache_creation_input_tokens || undefined, + cacheReadTokens: cache_read_input_tokens || undefined, + } + + inputTokens += input_tokens + outputTokens += output_tokens + cacheWriteTokens += cache_creation_input_tokens || 0 + cacheReadTokens += cache_read_input_tokens || 0 + + break + } + case "message_delta": + // Tells us stop_reason, stop_sequence, and output tokens + // along the way and at the end of the message. + yield { + type: "usage", + inputTokens: 0, + outputTokens: chunk.usage.output_tokens || 0, + } + + break + case "message_stop": + // No usage data, just an indicator that the message is done. + break + case "content_block_start": + switch (chunk.content_block.type) { + case "thinking": + // We may receive multiple text blocks, in which + // case just insert a line break between them. + if (chunk.index > 0) { + yield { type: "reasoning", text: "\n" } + } + + yield { type: "reasoning", text: chunk.content_block.thinking } + thinkText = chunk.content_block.thinking + thinkSignature = chunk.content_block.signature + if (thinkText && thinkSignature) { + yield { + type: "ant_thinking", + thinking: thinkText, + signature: thinkSignature, + } + } + break + case "redacted_thinking": + // Content is encrypted, and we don't want to pass placeholder text back to the API + yield { + type: "reasoning", + text: "[Redacted thinking block]", + } + yield { + type: "ant_redacted_thinking", + data: chunk.content_block.data, + } + break + case "text": + // We may receive multiple text blocks, in which + // case just insert a line break between them. + if (chunk.index > 0) { + yield { type: "text", text: "\n" } + } + + yield { type: "text", text: chunk.content_block.text } + break + } + break + case "content_block_delta": + switch (chunk.delta.type) { + case "thinking_delta": + yield { type: "reasoning", text: chunk.delta.thinking } + thinkingDeltaAccumulator += chunk.delta.thinking + break + case "signature_delta": + // It's used when sending the thinking block back to the API + // API expects this in completed form, not as array of deltas + if (thinkingDeltaAccumulator && chunk.delta.signature) { + yield { + type: "ant_thinking", + thinking: thinkingDeltaAccumulator, + signature: chunk.delta.signature, + } + } + break + case "text_delta": + yield { type: "text", text: chunk.delta.text } + break + } + + break + case "content_block_stop": + break + } + } + + if (inputTokens > 0 || outputTokens > 0 || cacheWriteTokens > 0 || cacheReadTokens > 0) { + yield { + type: "usage", + inputTokens: 0, + outputTokens: 0, + totalCost: calculateApiCostAnthropic( + this.getModel().info, + inputTokens, + outputTokens, + cacheWriteTokens, + cacheReadTokens, + ), + } + } + } + + getModel() { + const modelId = this.options.apiModelId + let id = modelId && modelId in minimaxModels ? (modelId as MinimaxModelId) : minimaxDefaultModelId + let info: ModelInfo = minimaxModels[id] + + const params = getModelParams({ + format: "anthropic", + modelId: id, + model: info, + settings: this.options, + }) + + // The `:thinking` suffix indicates that the model is a "Hybrid" + // reasoning model and that reasoning is required to be enabled. + // The actual model ID honored by Anthropic's API does not have this + // suffix. + return { + id, + info, + ...params, + } + } + + async completePrompt(prompt: string) { + let { id: model } = this.getModel() + + const message = await this.client.messages.create({ + model, + max_tokens: MINIMAX_DEFAULT_MAX_TOKENS, + thinking: undefined, + temperature: MINIMAX_DEFAULT_TEMPERATURE, + messages: [{ role: "user", content: prompt }], + stream: false, + }) + + const content = message.content.find(({ type }) => type === "text") + return content?.type === "text" ? content.text : "" + } + + /** + * Counts tokens for the given content using Anthropic's API + * + * @param content The content blocks to count tokens for + * @returns A promise resolving to the token count + */ + override async countTokens(content: Array): Promise { + try { + // Use the current model + const { id: model } = this.getModel() + + const response = await this.client.messages.countTokens({ + model, + messages: [{ role: "user", content: content }], + }) + + return response.input_tokens + } catch (error) { + // Log error but fallback to tiktoken estimation + console.warn("Anthropic token counting failed, using fallback", error) + + // Use the base provider's implementation as fallback + return super.countTokens(content) + } + } +} diff --git a/src/api/transform/stream.ts b/src/api/transform/stream.ts index 031639cd29e..0246619b5dc 100644 --- a/src/api/transform/stream.ts +++ b/src/api/transform/stream.ts @@ -9,6 +9,8 @@ export type ApiStreamChunk = | ApiStreamReasoningChunk | ApiStreamGroundingChunk | ApiStreamError + | ApiStreamAnthropicThinkingChunk + | ApiStreamAnthropicRedactedThinkingChunk export interface ApiStreamError { type: "error" @@ -26,6 +28,17 @@ export interface ApiStreamReasoningChunk { text: string } +export interface ApiStreamAnthropicThinkingChunk { + type: "ant_thinking" + thinking: string + signature: string +} + +export interface ApiStreamAnthropicRedactedThinkingChunk { + type: "ant_redacted_thinking" + data: string +} + export interface ApiStreamUsageChunk { type: "usage" inputTokens: number diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index d92e7a38ef8..1c9f597143d 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -2057,6 +2057,10 @@ export class Task extends EventEmitter implements TaskLike { let reasoningMessage = "" let pendingGroundingSources: GroundingSource[] = [] this.isStreaming = true + const antThinkingContent: ( + | Anthropic.Messages.RedactedThinkingBlock + | Anthropic.Messages.ThinkingBlock + )[] = [] try { const iterator = stream[Symbol.asyncIterator]() @@ -2143,6 +2147,19 @@ export class Task extends EventEmitter implements TaskLike { presentAssistantMessage(this) break } + case "ant_thinking": + antThinkingContent.push({ + type: "thinking", + thinking: chunk.thinking, + signature: chunk.signature, + }) + break + case "ant_redacted_thinking": + antThinkingContent.push({ + type: "redacted_thinking", + data: chunk.data, + }) + break } if (this.abort) { @@ -2474,6 +2491,9 @@ export class Task extends EventEmitter implements TaskLike { // kilocode_change start: also add tool calls to history const assistantMessageContent = new Array() + if (antThinkingContent.length > 0) { + assistantMessageContent.push(...antThinkingContent) + } if (assistantMessage) { assistantMessageContent.push({ type: "text", text: assistantMessage }) } diff --git a/src/shared/ProfileValidator.ts b/src/shared/ProfileValidator.ts index 9b13047d016..4c1eb6d919c 100644 --- a/src/shared/ProfileValidator.ts +++ b/src/shared/ProfileValidator.ts @@ -66,6 +66,7 @@ export class ProfileValidator { case "deepseek": case "xai": case "zai": + case "minimax": case "groq": case "sambanova": case "chutes": diff --git a/src/shared/__tests__/checkExistApiConfig.spec.ts b/src/shared/__tests__/checkExistApiConfig.spec.ts index 7696f00cc0c..740974a955f 100644 --- a/src/shared/__tests__/checkExistApiConfig.spec.ts +++ b/src/shared/__tests__/checkExistApiConfig.spec.ts @@ -58,6 +58,7 @@ describe("checkExistKey", () => { vsCodeLmModelSelector: undefined, requestyApiKey: undefined, unboundApiKey: undefined, + minimaxApiKey: undefined, } expect(checkExistKey(config)).toBe(false) }) diff --git a/webview-ui/src/components/kilocode/hooks/useProviderModels.ts b/webview-ui/src/components/kilocode/hooks/useProviderModels.ts index 3978bcc413d..26c7ae75408 100644 --- a/webview-ui/src/components/kilocode/hooks/useProviderModels.ts +++ b/webview-ui/src/components/kilocode/hooks/useProviderModels.ts @@ -50,6 +50,8 @@ import { cerebrasModels, cerebrasDefaultModelId, ovhCloudAiEndpointsDefaultModelId, // kilocode_change + minimaxModels, + minimaxDefaultModelId, } from "@roo-code/types" import type { ModelRecord, RouterModels } from "@roo/api" import { useRouterModels } from "../../ui/hooks/useRouterModels" @@ -277,6 +279,12 @@ export const getModelsByProvider = ({ defaultModel: deepInfraDefaultModelId, } } + case "minimax": { + return { + models: minimaxModels, + defaultModel: minimaxDefaultModelId, + } + } // kilocode_change start case "ovhcloud": { return { diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 99cc3bf7041..ccd3f35eca4 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -22,6 +22,7 @@ import { geminiCliDefaultModelId, deepSeekDefaultModelId, moonshotDefaultModelId, + minimaxDefaultModelId, mistralDefaultModelId, xaiDefaultModelId, groqDefaultModelId, @@ -102,6 +103,7 @@ import { Synthetic, // kilocode_change end ZAi, + MiniMax, Fireworks, Featherless, VercelAiGateway, @@ -396,6 +398,7 @@ const ApiOptions = ({ ? mainlandZAiDefaultModelId : internationalZAiDefaultModelId, }, + minimax: { field: "apiModelId", default: minimaxDefaultModelId }, fireworks: { field: "apiModelId", default: fireworksDefaultModelId }, synthetic: { field: "apiModelId", default: syntheticDefaultModelId }, // kilocode_change featherless: { field: "apiModelId", default: featherlessDefaultModelId }, @@ -438,7 +441,16 @@ const ApiOptions = ({ // kilocode_change start // Providers that don't have documentation pages yet - const excludedProviders = ["gemini-cli", "moonshot", "chutes", "cerebras", "litellm", "zai", "qwen-code"] + const excludedProviders = [ + "gemini-cli", + "moonshot", + "chutes", + "cerebras", + "litellm", + "zai", + "qwen-code", + "minimax", + ] // Skip documentation link when the provider is excluded because documentation is not available if (excludedProviders.includes(selectedProvider)) { @@ -672,6 +684,10 @@ const ApiOptions = ({ )} + {selectedProvider === "minimax" && ( + + )} + {selectedProvider === "groq" && ( )} diff --git a/webview-ui/src/components/settings/constants.ts b/webview-ui/src/components/settings/constants.ts index 1abe88c84ae..a826381c82a 100644 --- a/webview-ui/src/components/settings/constants.ts +++ b/webview-ui/src/components/settings/constants.ts @@ -25,6 +25,7 @@ import { syntheticModels, // kilocode_change rooModels, featherlessModels, + minimaxModels, } from "@roo-code/types" export const MODELS_BY_PROVIDER: Partial>> = { @@ -52,6 +53,7 @@ export const MODELS_BY_PROVIDER: Partial a.label.localeCompare(b.label)) PROVIDERS.unshift({ value: "kilocode", label: "Kilo Gateway" }) // kilocode_change 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 00000000000..6b7139fbed4 --- /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 0f0483d9241..17ed718e8c4 100644 --- a/webview-ui/src/components/settings/providers/index.ts +++ b/webview-ui/src/components/settings/providers/index.ts @@ -36,3 +36,4 @@ export { Synthetic } from "./Synthetic" // kilocode_change 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 8baa418b999..e5ec5613415 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -63,6 +63,8 @@ import { vercelAiGatewayDefaultModelId, BEDROCK_1M_CONTEXT_MODEL_IDS, deepInfraDefaultModelId, + minimaxDefaultModelId, + minimaxModels, ovhCloudAiEndpointsDefaultModelId, // kilocode_change } from "@roo-code/types" @@ -456,6 +458,11 @@ function getSelectedModel({ const info = routerModels.ovhcloud[id] return { id, info } } + case "minimax": { + const id = apiConfiguration.apiModelId ?? minimaxDefaultModelId + const info = minimaxModels[id as keyof typeof minimaxModels] + return { id, info } + } // kilocode_change end // case "anthropic": // case "human-relay": diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 955f19f006e..823ed432d30 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -308,6 +308,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/cs/settings.json b/webview-ui/src/i18n/locales/cs/settings.json index b5e7bdd4666..4c0b59ff07d 100644 --- a/webview-ui/src/i18n/locales/cs/settings.json +++ b/webview-ui/src/i18n/locales/cs/settings.json @@ -317,6 +317,9 @@ "moonshotBaseUrl": "Vstupní bod Moonshot", "zaiEntrypoint": "Vstupní bod Z AI", "zaiEntrypointDescription": "Vyberte prosím vhodný vstupní bod API podle vaší polohy. Pokud jste v Číně, vyberte open.bigmodel.cn. Jinak vyberte api.z.ai.", + "minimaxApiKey": "Klíč API MiniMax", + "getMiniMaxApiKey": "Získat klíč API MiniMax", + "minimaxBaseUrl": "Vstupní bod MiniMax", "geminiApiKey": "Klíč API Gemini", "getGroqApiKey": "Získat klíč API Groq", "groqApiKey": "Klíč API Groq", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 44a2b1779d5..04dc3ff3bbd 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -308,6 +308,9 @@ "getZaiApiKey": "Z AI API-Schlüssel erhalten", "zaiEntrypoint": "Z AI Einstiegspunkt", "zaiEntrypointDescription": "Bitte wähle den entsprechenden API-Einstiegspunkt basierend auf deinem Standort aus. Wenn du dich in China befindest, wähle open.bigmodel.cn. Andernfalls wähle 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 b7b680f4bd6..d047b4f9868 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -313,6 +313,9 @@ "getZaiApiKey": "Get Z AI API Key", "zaiEntrypoint": "Z AI Entrypoint", "zaiEntrypointDescription": "Please select the appropriate API entrypoint based on your location. If you are in China, choose open.bigmodel.cn. Otherwise, choose api.z.ai.", + "minimaxApiKey": "MiniMax API Key", + "getMiniMaxApiKey": "Get MiniMax API Key", + "minimaxBaseUrl": "MiniMax Entrypoint", "geminiApiKey": "Gemini API Key", "getGroqApiKey": "Get Groq API Key", "groqApiKey": "Groq API Key", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 1babe57927f..59bb96f0de8 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -308,6 +308,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/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index b1390a48973..0c6875e347f 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -308,6 +308,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", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 0b0b1fc21f0..c0c3abc5cbc 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -308,6 +308,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 68665e42007..5ea3be85bce 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -308,6 +308,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 f2e7f9f49a5..a6427facae5 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -309,6 +309,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 3bf58affbaa..ddbe6f8560b 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -309,6 +309,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 d00c16cae58..2245f48ca68 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -308,6 +308,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 cb0e2727071..f0c01ab0ac2 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -308,6 +308,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 16cec354ab6..5b8f5e7f8c4 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -308,6 +308,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 b68614c9526..97c7ad0a042 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -308,6 +308,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 c45304408be..9814ff18547 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -308,6 +308,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/th/settings.json b/webview-ui/src/i18n/locales/th/settings.json index c094ea024d6..9069ab6cfa2 100644 --- a/webview-ui/src/i18n/locales/th/settings.json +++ b/webview-ui/src/i18n/locales/th/settings.json @@ -310,6 +310,9 @@ "moonshotBaseUrl": "จุดเข้าใช้งาน Moonshot", "zaiEntrypoint": "จุดเข้าใช้งาน Z AI", "zaiEntrypointDescription": "โปรดเลือกจุดเข้าใช้งาน API ที่เหมาะสมตามตำแหน่งของคุณ หากคุณอยู่ในจีน ให้เลือก open.bigmodel.cn มิฉะนั้น ให้เลือก api.z.ai", + "minimaxApiKey": "คีย์ API ของ MiniMax", + "getMiniMaxApiKey": "รับคีย์ API ของ MiniMax", + "minimaxBaseUrl": "จุดเข้าใช้งาน MiniMax", "geminiApiKey": "คีย์ API ของ Gemini", "geminiParameters": { "urlContext": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index dcd7cdcaae3..fd4bd57c11f 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -309,6 +309,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/uk/settings.json b/webview-ui/src/i18n/locales/uk/settings.json index b3d42ae8c02..476ed09b3c5 100644 --- a/webview-ui/src/i18n/locales/uk/settings.json +++ b/webview-ui/src/i18n/locales/uk/settings.json @@ -316,6 +316,9 @@ "moonshotBaseUrl": "Точка входу Moonshot", "zaiEntrypoint": "Точка входу Z AI", "zaiEntrypointDescription": "Будь ласка, виберіть відповідну точку входу API залежно від вашого місцезнаходження. Якщо ви в Китаї, виберіть open.bigmodel.cn. Інакше виберіть api.z.ai.", + "minimaxApiKey": "Ключ API MiniMax", + "getMiniMaxApiKey": "Отримати ключ API MiniMax", + "minimaxBaseUrl": "Точка входу MiniMax", "geminiApiKey": "Ключ API Gemini", "geminiUrlContext": "Включати URL у контекст", "geminiUrlContextDescription": "Коли увімкнено, URL будуть включені в контекст при надсиланні запитів до Gemini", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 889d30f8583..3e39bde2ddd 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -308,6 +308,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 eaea7c687f2..533b1e6eb87 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -309,6 +309,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/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index ce10506e52f..8687881b9d5 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -309,6 +309,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 金鑰",