mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
fix: add model ID mapping for MiniMax China endpoint (minimaxi.com)
- Map MiniMax-M2 to abab7-chat for China endpoint - Map MiniMax-M2-Stable to abab7-chat-hd for China endpoint - Override createStream and completePrompt methods to handle model mapping - Add comprehensive tests for both endpoints Fixes #9100
This commit is contained in:
parent
2abdad6d55
commit
b3d41d5709
2 changed files with 189 additions and 1 deletions
|
|
@ -131,6 +131,79 @@ describe("MiniMaxHandler", () => {
|
|||
expect(model.id).toBe(minimaxDefaultModelId)
|
||||
expect(model.info).toEqual(minimaxModels[minimaxDefaultModelId])
|
||||
})
|
||||
|
||||
it("should map MiniMax-M2 to abab7-chat for China endpoint", async () => {
|
||||
const handlerWithModel = new MiniMaxHandler({
|
||||
apiModelId: "MiniMax-M2",
|
||||
minimaxApiKey: "test-minimax-api-key",
|
||||
minimaxBaseUrl: "https://api.minimaxi.com/v1",
|
||||
})
|
||||
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
async next() {
|
||||
return { done: true }
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const messageGenerator = handlerWithModel.createMessage("test", [])
|
||||
await messageGenerator.next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "abab7-chat", // Mapped model ID for China endpoint
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it("should map MiniMax-M2-Stable to abab7-chat-hd for China endpoint", async () => {
|
||||
const handlerWithModel = new MiniMaxHandler({
|
||||
apiModelId: "MiniMax-M2-Stable",
|
||||
minimaxApiKey: "test-minimax-api-key",
|
||||
minimaxBaseUrl: "https://api.minimaxi.com/v1",
|
||||
})
|
||||
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
async next() {
|
||||
return { done: true }
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const messageGenerator = handlerWithModel.createMessage("test", [])
|
||||
await messageGenerator.next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "abab7-chat-hd", // Mapped model ID for China endpoint
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it("should map model ID correctly in completePrompt for China endpoint", async () => {
|
||||
const handlerWithModel = new MiniMaxHandler({
|
||||
apiModelId: "MiniMax-M2",
|
||||
minimaxApiKey: "test-minimax-api-key",
|
||||
minimaxBaseUrl: "https://api.minimaxi.com/v1",
|
||||
})
|
||||
|
||||
mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: "test response" } }] })
|
||||
await handlerWithModel.completePrompt("test prompt")
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "abab7-chat", // Mapped model ID for China endpoint
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Default behavior", () => {
|
||||
|
|
@ -258,6 +331,35 @@ describe("MiniMaxHandler", () => {
|
|||
)
|
||||
})
|
||||
|
||||
it("should not apply model ID mapping for international endpoint", async () => {
|
||||
const modelId: MinimaxModelId = "MiniMax-M2"
|
||||
const handlerWithModel = new MiniMaxHandler({
|
||||
apiModelId: modelId,
|
||||
minimaxApiKey: "test-minimax-api-key",
|
||||
minimaxBaseUrl: "https://api.minimax.io/v1",
|
||||
})
|
||||
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
async next() {
|
||||
return { done: true }
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const messageGenerator = handlerWithModel.createMessage("test", [])
|
||||
await messageGenerator.next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: modelId, // Original model ID for international endpoint
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it("should use temperature 1 by default", async () => {
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,105 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
|
||||
import { type MinimaxModelId, minimaxDefaultModelId, minimaxModels } from "@roo-code/types"
|
||||
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { getModelMaxOutputTokens } from "../../shared/api"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { handleOpenAIError } from "./utils/openai-error-handler"
|
||||
import type { ApiHandlerCreateMessageMetadata } from "../index"
|
||||
|
||||
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
|
||||
|
||||
export class MiniMaxHandler extends BaseOpenAiCompatibleProvider<MinimaxModelId> {
|
||||
private readonly isChinaEndpoint: boolean
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
const baseURL = options.minimaxBaseUrl ?? "https://api.minimax.io/v1"
|
||||
const isChinaEndpoint = baseURL.includes("minimaxi.com")
|
||||
|
||||
super({
|
||||
...options,
|
||||
providerName: "MiniMax",
|
||||
baseURL: options.minimaxBaseUrl ?? "https://api.minimax.io/v1",
|
||||
baseURL,
|
||||
apiKey: options.minimaxApiKey,
|
||||
defaultProviderModelId: minimaxDefaultModelId,
|
||||
providerModels: minimaxModels,
|
||||
defaultTemperature: 1.0,
|
||||
})
|
||||
|
||||
this.isChinaEndpoint = isChinaEndpoint
|
||||
}
|
||||
|
||||
// Override createStream to handle model ID mapping for China endpoint
|
||||
protected override createStream(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
requestOptions?: OpenAI.RequestOptions,
|
||||
) {
|
||||
const { id: modelId, info } = this.getModel()
|
||||
|
||||
// Map model IDs for China endpoint - they use different model names
|
||||
let apiModelId = modelId as string
|
||||
if (this.isChinaEndpoint) {
|
||||
const chinaModelMapping: Record<string, string> = {
|
||||
"MiniMax-M2": "abab7-chat",
|
||||
"MiniMax-M2-Stable": "abab7-chat-hd",
|
||||
}
|
||||
apiModelId = chinaModelMapping[modelId] || modelId.toLowerCase()
|
||||
}
|
||||
|
||||
// Centralized cap: clamp to 20% of the context window (unless provider-specific exceptions apply)
|
||||
const max_tokens =
|
||||
getModelMaxOutputTokens({
|
||||
modelId,
|
||||
model: info,
|
||||
settings: this.options,
|
||||
format: "openai",
|
||||
}) ?? undefined
|
||||
|
||||
const temperature = this.options.modelTemperature ?? 1.0
|
||||
|
||||
const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
||||
model: apiModelId,
|
||||
max_tokens,
|
||||
temperature,
|
||||
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
}
|
||||
|
||||
try {
|
||||
return this.client.chat.completions.create(params, requestOptions)
|
||||
} catch (error) {
|
||||
throw handleOpenAIError(error, this.providerName)
|
||||
}
|
||||
}
|
||||
|
||||
// Override completePrompt to handle model ID mapping for China endpoint
|
||||
override async completePrompt(prompt: string): Promise<string> {
|
||||
const { id: modelId } = this.getModel()
|
||||
|
||||
// Map model IDs for China endpoint
|
||||
let apiModelId = modelId as string
|
||||
if (this.isChinaEndpoint) {
|
||||
const chinaModelMapping: Record<string, string> = {
|
||||
"MiniMax-M2": "abab7-chat",
|
||||
"MiniMax-M2-Stable": "abab7-chat-hd",
|
||||
}
|
||||
apiModelId = chinaModelMapping[modelId] || modelId.toLowerCase()
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.client.chat.completions.create({
|
||||
model: apiModelId,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
})
|
||||
|
||||
return response.choices[0]?.message.content || ""
|
||||
} catch (error) {
|
||||
throw handleOpenAIError(error, this.providerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue