Skip to content

Commit d7e0c8d

Browse files
author
PR Bot
committed
feat: add direct MiniMax provider support
- Add MiniMax LLM handler with direct API integration (api.minimax.io/v1) - Support models: MiniMax-M2.5, MiniMax-M2.5-highspeed - Add MINIMAX_API_KEY environment variable support - Add 'minimax' to allowed model prefixes - Add MiniMax models to openrouter model config - Add streaming and non-streaming request handling - Add MiniMax per-token pricing for accurate billing - Handle MiniMax temperature constraint (0.0, 1.0], default 1.0 - Route minimax/* models directly to MiniMax API before Fireworks fallback
1 parent e9172b1 commit d7e0c8d

File tree

5 files changed

+738
-9
lines changed

5 files changed

+738
-9
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ ANTHROPIC_API_KEY=dummy_anthropic_key
66
FIREWORKS_API_KEY=dummy_fireworks_key
77
CANOPYWAVE_API_KEY=dummy_canopywave_key
88
SILICONFLOW_API_KEY=dummy_siliconflow_key
9+
MINIMAX_API_KEY=dummy_minimax_key
910

1011
# Database & Server
1112
DATABASE_URL=postgresql://manicode_user_local:secretpassword_local@localhost:5432/manicode_db_local

common/src/constants/model-config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const ALLOWED_MODEL_PREFIXES = [
66
'openai',
77
'google',
88
'x-ai',
9+
'minimax',
910
] as const
1011

1112
export const costModes = [
@@ -47,6 +48,8 @@ export const openrouterModels = {
4748
openrouter_gemini2_5_flash_thinking:
4849
'google/gemini-2.5-flash-preview:thinking',
4950
openrouter_grok_4: 'x-ai/grok-4-07-09',
51+
openrouter_minimax_m2_5: 'minimax/minimax-m2.5',
52+
openrouter_minimax_m2_5_highspeed: 'minimax/minimax-m2.5-highspeed',
5053
} as const
5154
export type openrouterModel =
5255
(typeof openrouterModels)[keyof typeof openrouterModels]
@@ -171,6 +174,7 @@ export const providerDomains = {
171174
openai: 'chatgpt.com',
172175
deepseek: 'deepseek.com',
173176
xai: 'x.ai',
177+
minimax: 'minimax.io',
174178
} as const
175179

176180
export function getLogoForModel(modelName: string): string | undefined {
@@ -182,6 +186,7 @@ export function getLogoForModel(modelName: string): string | undefined {
182186
domain = providerDomains.deepseek
183187
else if (modelName.includes('claude')) domain = providerDomains.anthropic
184188
else if (modelName.includes('grok')) domain = providerDomains.xai
189+
else if (modelName.includes('minimax')) domain = providerDomains.minimax
185190

186191
return domain
187192
? `https://www.google.com/s2/favicons?domain=${domain}&sz=256`

packages/internal/src/env-schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const serverEnvSchema = clientEnvSchema.extend({
99
FIREWORKS_API_KEY: z.string().min(1),
1010
CANOPYWAVE_API_KEY: z.string().min(1).optional(),
1111
SILICONFLOW_API_KEY: z.string().min(1).optional(),
12+
MINIMAX_API_KEY: z.string().min(1).optional(),
1213
LINKUP_API_KEY: z.string().min(1),
1314
CONTEXT7_API_KEY: z.string().optional(),
1415
GRAVITY_API_KEY: z.string().min(1),
@@ -54,6 +55,7 @@ export const serverProcessEnv: ServerInput = {
5455
FIREWORKS_API_KEY: process.env.FIREWORKS_API_KEY,
5556
CANOPYWAVE_API_KEY: process.env.CANOPYWAVE_API_KEY,
5657
SILICONFLOW_API_KEY: process.env.SILICONFLOW_API_KEY,
58+
MINIMAX_API_KEY: process.env.MINIMAX_API_KEY,
5759
LINKUP_API_KEY: process.env.LINKUP_API_KEY,
5860
CONTEXT7_API_KEY: process.env.CONTEXT7_API_KEY,
5961
GRAVITY_API_KEY: process.env.GRAVITY_API_KEY,

web/src/app/api/v1/chat/completions/_post.ts

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ import {
5353
handleSiliconFlowStream,
5454
isSiliconFlowModel,
5555
} from '@/llm-api/siliconflow'
56+
import {
57+
MiniMaxError,
58+
handleMiniMaxNonStream,
59+
handleMiniMaxStream,
60+
isMiniMaxModel,
61+
} from '@/llm-api/minimax'
5662
import {
5763
handleOpenAINonStream,
5864
OPENAI_SUPPORTED_MODELS,
@@ -366,11 +372,22 @@ export async function postChatCompletions(params: {
366372
// Handle streaming vs non-streaming
367373
try {
368374
if (bodyStream) {
369-
// Streaming request — route to SiliconFlow/CanopyWave/Fireworks for supported models
375+
// Streaming request — route to MiniMax/SiliconFlow/CanopyWave/Fireworks for supported models
376+
const useMiniMax = isMiniMaxModel(typedBody.model)
370377
const useSiliconFlow = false // isSiliconFlowModel(typedBody.model)
371378
const useCanopyWave = false // isCanopyWaveModel(typedBody.model)
372-
const useFireworks = isFireworksModel(typedBody.model)
373-
const stream = useSiliconFlow
379+
const useFireworks = !useMiniMax && isFireworksModel(typedBody.model)
380+
const stream = useMiniMax
381+
? await handleMiniMaxStream({
382+
body: typedBody,
383+
userId,
384+
stripeCustomerId,
385+
agentId,
386+
fetch,
387+
logger,
388+
insertMessageBigquery,
389+
})
390+
: useSiliconFlow
374391
? await handleSiliconFlowStream({
375392
body: typedBody,
376393
userId,
@@ -430,12 +447,13 @@ export async function postChatCompletions(params: {
430447
},
431448
})
432449
} else {
433-
// Non-streaming request — route to SiliconFlow/CanopyWave/Fireworks for supported models
450+
// Non-streaming request — route to MiniMax/SiliconFlow/CanopyWave/Fireworks for supported models
434451
// TEMPORARILY DISABLED: route through OpenRouter
435452
const model = typedBody.model
453+
const useMiniMaxDirect = isMiniMaxModel(model)
436454
const useSiliconFlow = false // isSiliconFlowModel(model)
437455
const useCanopyWave = false // isCanopyWaveModel(model)
438-
const useFireworks = isFireworksModel(model)
456+
const useFireworks = !useMiniMaxDirect && isFireworksModel(model)
439457
const modelParts = model.split('/')
440458
const shortModelName = modelParts.length > 1 ? modelParts[1] : model
441459
const isOpenAIDirectModel =
@@ -446,7 +464,17 @@ export async function postChatCompletions(params: {
446464
const shouldUseOpenAIEndpoint =
447465
isOpenAIDirectModel && typedBody.codebuff_metadata?.n !== undefined
448466

449-
const nonStreamRequest = useSiliconFlow
467+
const nonStreamRequest = useMiniMaxDirect
468+
? handleMiniMaxNonStream({
469+
body: typedBody,
470+
userId,
471+
stripeCustomerId,
472+
agentId,
473+
fetch,
474+
logger,
475+
insertMessageBigquery,
476+
})
477+
: useSiliconFlow
450478
? handleSiliconFlowNonStream({
451479
body: typedBody,
452480
userId,
@@ -528,10 +556,14 @@ export async function postChatCompletions(params: {
528556
if (error instanceof SiliconFlowError) {
529557
siliconflowError = error
530558
}
559+
let minimaxError: MiniMaxError | undefined
560+
if (error instanceof MiniMaxError) {
561+
minimaxError = error
562+
}
531563

532564
// Log detailed error information for debugging
533565
const errorDetails = openrouterError?.toJSON()
534-
const providerLabel = siliconflowError ? 'SiliconFlow' : canopywaveError ? 'CanopyWave' : fireworksError ? 'Fireworks' : 'OpenRouter'
566+
const providerLabel = minimaxError ? 'MiniMax' : siliconflowError ? 'SiliconFlow' : canopywaveError ? 'CanopyWave' : fireworksError ? 'Fireworks' : 'OpenRouter'
535567
logger.error(
536568
{
537569
error: getErrorObject(error),
@@ -545,8 +577,8 @@ export async function postChatCompletions(params: {
545577
? typedBody.messages.length
546578
: 0,
547579
messages: typedBody.messages,
548-
providerStatusCode: (openrouterError ?? fireworksError ?? canopywaveError ?? siliconflowError)?.statusCode,
549-
providerStatusText: (openrouterError ?? fireworksError ?? canopywaveError ?? siliconflowError)?.statusText,
580+
providerStatusCode: (openrouterError ?? fireworksError ?? canopywaveError ?? siliconflowError ?? minimaxError)?.statusCode,
581+
providerStatusText: (openrouterError ?? fireworksError ?? canopywaveError ?? siliconflowError ?? minimaxError)?.statusText,
550582
openrouterErrorCode: errorDetails?.error?.code,
551583
openrouterErrorType: errorDetails?.error?.type,
552584
openrouterErrorMessage: errorDetails?.error?.message,
@@ -580,6 +612,9 @@ export async function postChatCompletions(params: {
580612
if (error instanceof SiliconFlowError) {
581613
return NextResponse.json(error.toJSON(), { status: error.statusCode })
582614
}
615+
if (error instanceof MiniMaxError) {
616+
return NextResponse.json(error.toJSON(), { status: error.statusCode })
617+
}
583618

584619
return NextResponse.json(
585620
{ error: 'Failed to process request' },

0 commit comments

Comments
 (0)