Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Gemini,
Kimi,
LmStudio,
Minimax,
Ollama,
OpenAI,
OpenRouter,
Expand Down Expand Up @@ -33,6 +34,7 @@ const providerIconMap: Record<ProviderType, IconComponent | null> = {
bedrock: Bedrock,
browseros: null,
moonshot: Kimi,
minimax: Minimax,
'chatgpt-pro': OpenAI,
'github-copilot': Github,
'qwen-code': Qwen,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,19 @@ describe('providerTemplates', () => {
contextWindow: 1050000,
})
})

it('defaults MiniMax to M3 on the international endpoint with a 1M context', () => {
const template = providerTemplates.find(
(provider) => provider.id === 'minimax',
)

expect(template).toMatchObject({
name: 'MiniMax',
defaultBaseUrl: 'https://api.minimax.io/v1',
defaultModelId: 'MiniMax-M3',
contextWindow: 1000000,
setupGuideUrl:
'https://platform.minimax.io/docs/api-reference/api-overview',
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ export const providerTemplates: ProviderTemplate[] = [
apiKeyUrl: 'https://platform.moonshot.ai/console/api-keys',
setupGuideUrl: 'https://platform.moonshot.ai/console/api-keys',
},
{
id: 'minimax',
name: 'MiniMax',
defaultBaseUrl: 'https://api.minimax.io/v1',
defaultModelId: 'MiniMax-M3',
supportsImages: true,
contextWindow: 1000000,
setupGuideUrl:
'https://platform.minimax.io/docs/api-reference/api-overview',
},
enrichTemplate('openai', {
defaultModelId: 'gpt-5',
apiKeyUrl: 'https://platform.openai.com/api-keys',
Expand Down Expand Up @@ -163,6 +173,7 @@ export const providerTypeOptions: { value: ProviderType; label: string }[] = [
{ value: 'codex', label: 'Codex' },
{ value: 'claude-code', label: 'Claude Code' },
{ value: 'moonshot', label: 'Moonshot AI' },
{ value: 'minimax', label: 'MiniMax' },
{ value: 'anthropic', label: 'Anthropic' },
{ value: 'openai', label: 'OpenAI' },
{ value: 'openai-compatible', label: 'OpenAI Compatible' },
Expand Down Expand Up @@ -198,6 +209,7 @@ const DEFAULT_BASE_URLS: Record<ProviderType, string> = {
'claude-code': '',
'acp-custom': '',
moonshot: 'https://api.moonshot.ai/v1',
minimax: 'https://api.minimax.io/v1',
anthropic: 'https://api.anthropic.com/v1',
openai: 'https://api.openai.com/v1',
'openai-compatible': '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type ProviderType =
| 'bedrock'
| 'browseros'
| 'moonshot'
| 'minimax'
| 'chatgpt-pro'
| 'github-copilot'
| 'qwen-code'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,27 @@ describe('ChatGPT models', () => {
expect(getModelContextLength('chatgpt-pro', 'gpt-5.1')).toBe(400000)
})
})

describe('MiniMax models', () => {
it('offers current models with their supported capabilities', () => {
const models = getModelsForProvider('minimax')

expect(models).toEqual([
{
modelId: 'MiniMax-M3',
contextLength: 1000000,
supportsImages: true,
supportsReasoning: true,
supportsToolCall: true,
},
{
modelId: 'MiniMax-M2.7',
contextLength: 204800,
supportsReasoning: true,
supportsToolCall: true,
},
])
expect(getModelContextLength('minimax', 'MiniMax-M3')).toBe(1000000)
expect(getModelContextLength('minimax', 'MiniMax-M2.7')).toBe(204800)
})
})
15 changes: 15 additions & 0 deletions packages/browseros-agent/apps/app/screens/ai-settings/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ const CUSTOM_PROVIDER_MODELS: Partial<Record<ProviderType, ModelInfo[]>> = {
{ modelId: 'qwen3-coder-flash', contextLength: 1000000 },
{ modelId: 'qwen3.5-plus', contextLength: 1000000 },
],
minimax: [
{
modelId: 'MiniMax-M3',
contextLength: 1000000,
supportsImages: true,
supportsReasoning: true,
supportsToolCall: true,
},
{
modelId: 'MiniMax-M2.7',
contextLength: 204800,
supportsReasoning: true,
supportsToolCall: true,
},
],
}

function fromModelsDevModel(m: ModelsDevModel): ModelInfo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { isLocalRuntimeProviderType } from '../../lib/llm-providers/provider-run

const providerTypeEnum = z.enum([
'moonshot',
'minimax',
'anthropic',
'openai',
'openai-compatible',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { resolveAcpSpawnCommand } from '../lib/agents/host-acp/launcher'
import { getBrowserosDir } from '../lib/browseros-dir'
import { createBrowserOSFetch } from '../lib/browseros-fetch'
import { createMinimaxProvider } from '../lib/clients/llm/minimax-provider'
import {
createMockBrowserOSLanguageModel,
shouldUseMockBrowserOSLLM,
Expand Down Expand Up @@ -413,6 +414,12 @@ function createMoonshotFactory(
})
}

function createMinimaxFactory(
config: ResolvedAgentConfig,
): (modelId: string) => unknown {
return createMinimaxProvider(config)
}

function createQwenCodeFactory(
config: ResolvedAgentConfig,
): (modelId: string) => unknown {
Expand Down Expand Up @@ -459,6 +466,7 @@ const PROVIDER_FACTORIES: Record<string, ProviderFactory> = {
[LLM_PROVIDERS.BROWSEROS]: createBrowserOSFactory,
[LLM_PROVIDERS.OPENAI_COMPATIBLE]: createOpenAICompatibleFactory,
[LLM_PROVIDERS.MOONSHOT]: createMoonshotFactory,
[LLM_PROVIDERS.MINIMAX]: createMinimaxFactory,
[LLM_PROVIDERS.CHATGPT_PRO]: createChatGPTProFactory,
[LLM_PROVIDERS.GITHUB_COPILOT]: createGitHubCopilotFactory,
[LLM_PROVIDERS.QWEN_CODE]: createQwenCodeFactory,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @license
* Copyright 2025 BrowserOS
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { createAnthropic } from '@ai-sdk/anthropic'
import { createOpenAICompatible } from '@ai-sdk/openai-compatible'
import type { LanguageModel } from 'ai'

interface MinimaxProviderConfig {
apiKey?: string
baseUrl?: string
}

export function createMinimaxProvider(
config: MinimaxProviderConfig,
): (modelId: string) => LanguageModel {
if (!config.baseUrl) throw new Error('MiniMax provider requires baseUrl')
if (!config.apiKey) throw new Error('MiniMax provider requires apiKey')

const baseUrl = config.baseUrl.replace(/\/+$/, '')
if (baseUrl.endsWith('/anthropic')) {
return createAnthropic({
name: 'minimax',
baseURL: `${baseUrl}/v1`,
apiKey: config.apiKey,
})
}

return createOpenAICompatible({
name: 'minimax',
baseURL: baseUrl,
apiKey: config.apiKey,
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { logger } from '../../logger'
import { createOpenRouterCompatibleFetch } from '../../openrouter-fetch'
import { createCodexFetch } from '../oauth/codex-fetch'
import { createCopilotFetch } from '../oauth/copilot-fetch'
import { createMinimaxProvider } from './minimax-provider'
import {
createMockBrowserOSLanguageModel,
shouldUseMockBrowserOSLLM,
Expand Down Expand Up @@ -152,6 +153,10 @@ function createMoonshotModel(config: ResolvedLLMConfig): LanguageModel {
})(config.model)
}

function createMinimaxModel(config: ResolvedLLMConfig): LanguageModel {
return createMinimaxProvider(config)(config.model)
}

function createQwenCodeModel(config: ResolvedLLMConfig): LanguageModel {
if (!config.apiKey) throw new Error('Qwen Code requires OAuth authentication')
return createOpenAICompatible({
Expand Down Expand Up @@ -192,6 +197,7 @@ const PROVIDER_FACTORIES: Record<string, ProviderFactory> = {
[LLM_PROVIDERS.BROWSEROS]: createBrowserOSModel,
[LLM_PROVIDERS.OPENAI_COMPATIBLE]: createOpenAICompatibleModel,
[LLM_PROVIDERS.MOONSHOT]: createMoonshotModel,
[LLM_PROVIDERS.MINIMAX]: createMinimaxModel,
[LLM_PROVIDERS.CHATGPT_PRO]: createChatGPTProModel,
[LLM_PROVIDERS.GITHUB_COPILOT]: createGitHubCopilotModel,
[LLM_PROVIDERS.QWEN_CODE]: createQwenCodeModel,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* @license
* Copyright 2025 BrowserOS
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { afterEach, describe, expect, it } from 'bun:test'
import { LLM_PROVIDERS } from '@browseros/shared/schemas/llm'
import { generateText } from 'ai'
import { createLLMProvider } from '../../../../src/lib/clients/llm/provider'

const originalFetch = globalThis.fetch

afterEach(() => {
globalThis.fetch = originalFetch
})

function requestUrl(input: RequestInfo | URL): string {
if (typeof input === 'string') return input
if (input instanceof URL) return input.href
return input.url
}

function responseBody(protocol: 'anthropic' | 'openai', model: string) {
if (protocol === 'anthropic') {
return {
id: 'msg_test',
type: 'message',
role: 'assistant',
content: [{ type: 'text', text: 'Hello' }],
model,
stop_reason: 'end_turn',
stop_sequence: null,
usage: { input_tokens: 1, output_tokens: 1 },
}
}

return {
id: 'chatcmpl_test',
object: 'chat.completion',
created: 0,
model,
choices: [
{
index: 0,
message: { role: 'assistant', content: 'Hello' },
finish_reason: 'stop',
},
],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}
}

const endpointCases = [
{
name: 'global OpenAI',
baseUrl: 'https://api.minimax.io/v1',
requestUrl: 'https://api.minimax.io/v1/chat/completions',
protocol: 'openai',
model: 'MiniMax-M3',
},
{
name: 'China OpenAI',
baseUrl: 'https://api.minimaxi.com/v1',
requestUrl: 'https://api.minimaxi.com/v1/chat/completions',
protocol: 'openai',
model: 'MiniMax-M2.7',
},
{
name: 'global Anthropic',
baseUrl: 'https://api.minimax.io/anthropic',
requestUrl: 'https://api.minimax.io/anthropic/v1/messages',
protocol: 'anthropic',
model: 'MiniMax-M3',
},
{
name: 'China Anthropic',
baseUrl: 'https://api.minimaxi.com/anthropic',
requestUrl: 'https://api.minimaxi.com/anthropic/v1/messages',
protocol: 'anthropic',
model: 'MiniMax-M2.7',
},
] as const

describe('createLLMProvider', () => {
it('creates an OpenAI-compatible model for MiniMax', () => {
const model = createLLMProvider({
provider: LLM_PROVIDERS.MINIMAX,
model: 'MiniMax-M3',
apiKey: 'test-key',
baseUrl: 'https://api.minimax.io/v1',
})

expect(model.provider).toBe('minimax.chat')
expect(model.modelId).toBe('MiniMax-M3')
})

it('throws when the MiniMax provider is missing an apiKey', () => {
expect(() =>
createLLMProvider({
provider: LLM_PROVIDERS.MINIMAX,
model: 'MiniMax-M3',
baseUrl: 'https://api.minimax.io/v1',
}),
).toThrow('MiniMax provider requires apiKey')
})

for (const endpoint of endpointCases) {
it(`sends requests to the ${endpoint.name} endpoint`, async () => {
let capturedUrl = ''
globalThis.fetch = (async (input: RequestInfo | URL) => {
capturedUrl = requestUrl(input)
return Response.json(responseBody(endpoint.protocol, endpoint.model))
}) as typeof globalThis.fetch

const model = createLLMProvider({
provider: LLM_PROVIDERS.MINIMAX,
model: endpoint.model,
apiKey: 'test-key',
baseUrl: endpoint.baseUrl,
})
const result = await generateText({ model, prompt: 'Hello' })

expect(result.text).toBe('Hello')
expect(capturedUrl).toBe(endpoint.requestUrl)
})
}
})
3 changes: 3 additions & 0 deletions packages/browseros-agent/packages/shared/src/schemas/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const LLM_PROVIDERS = {
BROWSEROS: 'browseros',
OPENAI_COMPATIBLE: 'openai-compatible',
MOONSHOT: 'moonshot',
MINIMAX: 'minimax',
CHATGPT_PRO: 'chatgpt-pro',
GITHUB_COPILOT: 'github-copilot',
QWEN_CODE: 'qwen-code',
Expand All @@ -49,6 +50,7 @@ export const LLMProviderSchema: z.ZodEnum<
'browseros',
'openai-compatible',
'moonshot',
'minimax',
'chatgpt-pro',
'github-copilot',
'qwen-code',
Expand All @@ -69,6 +71,7 @@ export const LLMProviderSchema: z.ZodEnum<
LLM_PROVIDERS.BROWSEROS,
LLM_PROVIDERS.OPENAI_COMPATIBLE,
LLM_PROVIDERS.MOONSHOT,
LLM_PROVIDERS.MINIMAX,
LLM_PROVIDERS.CHATGPT_PRO,
LLM_PROVIDERS.GITHUB_COPILOT,
LLM_PROVIDERS.QWEN_CODE,
Expand Down
Loading