feat(llm): Minimax LLM provider for Chinese and International Users (#756)

* feat(llm): Minimax Chinese and International Users providers

* fix(llm): Patch for p2 bugs

* fix(agent): correct MiniMax base URL handling and enforce API key validation

* fix(agent): add minimax entry to PROVIDER_DISPLAY_NAMES

The Record<ProviderType, string> map in ChatError.tsx was missing
the new minimax key added in this PR, causing a typecheck failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: krish-mm <112251957+krish-mm@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Felarof
2026-04-22 13:40:50 -07:00
committed by GitHub
parent 392cd58932
commit 1a2fe3a5bf
8 changed files with 245 additions and 1 deletions

View File

@@ -64,6 +64,7 @@ import {
import {
getDefaultBaseUrlForProviders,
getProviderTemplate,
MINIMAX_REGIONS,
providerTypeOptions,
} from '@/lib/llm-providers/providerTemplates'
import { type TestResult, testProvider } from '@/lib/llm-providers/testProvider'
@@ -87,6 +88,7 @@ const providerTypeEnum = z.enum([
'chatgpt-pro',
'github-copilot',
'qwen-code',
'minimax',
])
/**
@@ -105,7 +107,7 @@ export const providerFormSchema = z
temperature: z.number().min(0).max(2),
// Azure-specific
resourceName: z.string().optional(),
// Bedrock-specific
// Bedrock-specific / MiniMax region
accessKeyId: z.string().optional(),
secretAccessKey: z.string().optional(),
region: z.string().optional(),
@@ -164,6 +166,30 @@ export const providerFormSchema = z
) {
// No validation needed — OAuth tokens are on the server
}
// MiniMax: require baseUrl + apiKey
else if (data.type === 'minimax') {
if (!data.baseUrl) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Base URL is required',
path: ['baseUrl'],
})
} else if (!/^https?:\/\/.+/.test(data.baseUrl)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Must be a valid URL',
path: ['baseUrl'],
})
}
if (!data.apiKey?.trim()) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'API Key is required',
path: ['apiKey'],
})
}
}
// Other providers: require baseUrl
else if (!data.baseUrl) {
ctx.addIssue({
@@ -316,6 +342,9 @@ export const NewProviderDialog: FC<NewProviderDialogProps> = ({
if (defaultUrl) {
form.setValue('baseUrl', defaultUrl)
}
if (newType === 'minimax') {
form.setValue('region', 'chinese')
}
form.setValue('modelId', '')
}
@@ -722,6 +751,94 @@ export const NewProviderDialog: FC<NewProviderDialogProps> = ({
)
}
// Minimax: region selector
if (watchedType === 'minimax') {
return (
<>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<FormItem>
<FormLabel>Region *</FormLabel>
<Select
onValueChange={(v) => {
field.onChange(v)
form.setValue(
'baseUrl',
MINIMAX_REGIONS[v as keyof typeof MINIMAX_REGIONS].api,
)
}}
value={field.value || 'chinese'}
>
<FormControl>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="chinese">
Chinese (api.minimaxi.com)
</SelectItem>
<SelectItem value="international">
International (api.minimax.io)
</SelectItem>
</SelectContent>
</Select>
<FormDescription>
Choose the endpoint closest to your location
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="baseUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Base URL *</FormLabel>
<FormControl>
<Input placeholder="https://api.minimaxi.com/v1" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="apiKey"
render={({ field }) => (
<FormItem>
<FormLabel>API Key *</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Enter your MiniMax API key"
{...field}
/>
</FormControl>
<FormDescription>
Your API key is encrypted and stored locally.{' '}
{setupGuideUrl && (
<a
href={setupGuideUrl}
onClick={handleSetupGuideClick}
className="inline-flex cursor-pointer items-center gap-1 text-primary hover:underline"
>
<ExternalLink className="h-3 w-3" />
{setupGuideText}
</a>
)}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</>
)
}
// Standard providers (OpenAI, Anthropic, Google, etc.)
return (
<>

View File

@@ -31,6 +31,7 @@ const PROVIDER_DISPLAY_NAMES: Record<ProviderType, string> = {
'chatgpt-pro': 'ChatGPT Pro',
'github-copilot': 'GitHub Copilot',
'qwen-code': 'Qwen Code',
minimax: 'MiniMax',
}
const UPSTREAM_RATE_LIMIT_PATTERNS: Array<string | RegExp> = [

View File

@@ -5402,5 +5402,89 @@
"outputCost": 0
}
]
},
"minimax": {
"name": "MiniMax",
"api": "https://api.minimaxi.com/v1",
"doc": "https://platform.minimax.io",
"models": [
{
"id": "MiniMax-M2.7",
"name": "MiniMax M2.7",
"contextWindow": 204800,
"maxOutput": 8192,
"supportsImages": false,
"supportsReasoning": true,
"supportsToolCall": true,
"inputCost": 0.3,
"outputCost": 1.2
},
{
"id": "MiniMax-M2.7-highspeed",
"name": "MiniMax M2.7 Highspeed",
"contextWindow": 204800,
"maxOutput": 8192,
"supportsImages": false,
"supportsReasoning": true,
"supportsToolCall": true,
"inputCost": 0.6,
"outputCost": 2.4
},
{
"id": "MiniMax-M2.5",
"name": "MiniMax M2.5",
"contextWindow": 204800,
"maxOutput": 8192,
"supportsImages": false,
"supportsReasoning": true,
"supportsToolCall": true,
"inputCost": 0.3,
"outputCost": 1.2
},
{
"id": "MiniMax-M2.5-highspeed",
"name": "MiniMax M2.5 Highspeed",
"contextWindow": 204800,
"maxOutput": 8192,
"supportsImages": false,
"supportsReasoning": true,
"supportsToolCall": true,
"inputCost": 0.6,
"outputCost": 2.4
},
{
"id": "MiniMax-M2.1",
"name": "MiniMax M2.1",
"contextWindow": 204800,
"maxOutput": 8192,
"supportsImages": false,
"supportsReasoning": true,
"supportsToolCall": true,
"inputCost": 0.3,
"outputCost": 1.2
},
{
"id": "MiniMax-M2.1-highspeed",
"name": "MiniMax M2.1 Highspeed",
"contextWindow": 204800,
"maxOutput": 8192,
"supportsImages": false,
"supportsReasoning": true,
"supportsToolCall": true,
"inputCost": 0.6,
"outputCost": 2.4
},
{
"id": "M2-her",
"name": "M2-her",
"contextWindow": 204800,
"maxOutput": 8192,
"supportsImages": false,
"supportsReasoning": false,
"supportsToolCall": true,
"inputCost": 0.3,
"outputCost": 1.2
}
]
}
}

View File

@@ -5,6 +5,7 @@ import {
Gemini,
Kimi,
LmStudio,
Minimax,
Ollama,
OpenAI,
OpenRouter,
@@ -36,6 +37,7 @@ const providerIconMap: Record<ProviderType, IconComponent | null> = {
'chatgpt-pro': OpenAI,
'github-copilot': Github,
'qwen-code': Qwen,
minimax: Minimax,
}
interface ProviderIconProps {

View File

@@ -140,8 +140,31 @@ export const providerTemplates: ProviderTemplate[] = [
setupGuideUrl:
'https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html',
}),
enrichTemplate('minimax', {
defaultModelId: 'MiniMax-M2.7',
apiKeyUrl:
'https://platform.minimax.io/user-center/basic-information/interface-key',
setupGuideUrl: 'https://platform.minimax.io/docs/guides/models-intro',
}),
]
export const MINIMAX_REGIONS = {
chinese: {
api: 'https://api.minimaxi.com/v1',
apiKeyUrl:
'https://platform.minimaxi.com/user-center/basic-information/interface-key',
setupGuideUrl: 'https://platform.minimaxi.com/document',
},
international: {
api: 'https://api.minimax.io/v1',
apiKeyUrl:
'https://platform.minimax.io/user-center/basic-information/interface-key',
setupGuideUrl: 'https://platform.minimax.io/docs/guides/models-intro',
},
} as const
export type MinimaxRegion = keyof typeof MINIMAX_REGIONS
/**
* Provider type options for select dropdowns
* @public
@@ -161,6 +184,7 @@ export const providerTypeOptions: { value: ProviderType; label: string }[] = [
{ value: 'lmstudio', label: 'LM Studio' },
{ value: 'bedrock', label: 'AWS Bedrock' },
{ value: 'browseros', label: 'BrowserOS' },
{ value: 'minimax', label: 'MiniMax' },
]
/**
@@ -192,6 +216,7 @@ export const DEFAULT_BASE_URLS: Record<ProviderType, string> = {
lmstudio: 'http://localhost:1234/v1',
bedrock: '',
browseros: '',
minimax: MINIMAX_REGIONS.chinese.api,
}
/**

View File

@@ -17,6 +17,7 @@ export type ProviderType =
| 'chatgpt-pro'
| 'github-copilot'
| 'qwen-code'
| 'minimax'
/**
* LLM Provider configuration

View File

@@ -148,6 +148,16 @@ function createMoonshotModel(config: ResolvedLLMConfig): LanguageModel {
})(config.model)
}
function createMinimaxModel(config: ResolvedLLMConfig): LanguageModel {
if (!config.baseUrl) throw new Error('Minimax provider requires baseUrl')
if (!config.apiKey) throw new Error('Minimax provider requires apiKey')
return createOpenAICompatible({
name: 'minimax',
baseURL: config.baseUrl,
apiKey: config.apiKey,
})(config.model)
}
function createQwenCodeModel(config: ResolvedLLMConfig): LanguageModel {
if (!config.apiKey) throw new Error('Qwen Code requires OAuth authentication')
return createOpenAICompatible({
@@ -192,6 +202,7 @@ const PROVIDER_FACTORIES: Record<string, ProviderFactory> = {
[LLM_PROVIDERS.CHATGPT_PRO]: createChatGPTProModel,
[LLM_PROVIDERS.GITHUB_COPILOT]: createGitHubCopilotModel,
[LLM_PROVIDERS.QWEN_CODE]: createQwenCodeModel,
[LLM_PROVIDERS.MINIMAX]: createMinimaxModel,
}
export function createLLMProvider(config: ResolvedLLMConfig): LanguageModel {

View File

@@ -27,6 +27,7 @@ export const LLM_PROVIDERS = {
CHATGPT_PRO: 'chatgpt-pro',
GITHUB_COPILOT: 'github-copilot',
QWEN_CODE: 'qwen-code',
MINIMAX: 'minimax',
} as const
/**
@@ -48,6 +49,7 @@ export const LLMProviderSchema: z.ZodEnum<
'chatgpt-pro',
'github-copilot',
'qwen-code',
'minimax',
]
> = z.enum([
LLM_PROVIDERS.ANTHROPIC,
@@ -64,6 +66,7 @@ export const LLMProviderSchema: z.ZodEnum<
LLM_PROVIDERS.CHATGPT_PRO,
LLM_PROVIDERS.GITHUB_COPILOT,
LLM_PROVIDERS.QWEN_CODE,
LLM_PROVIDERS.MINIMAX,
])
export type LLMProvider = z.infer<typeof LLMProviderSchema>