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
2 changes: 1 addition & 1 deletion README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ brew install lima
| **OpenRouter** | [OpenRouter](https://openrouter.ai/) | `https://openrouter.ai/api` | `claude-4-5-sonnet` |
| **Anthropic** | [Anthropic Console](https://console.anthropic.com/) | 默认 | `claude-4-5-sonnet` |
| **智谱 AI** | [GLM Coding Plan](https://bigmodel.cn/glm-coding) (⚡️国产特惠) | `https://open.bigmodel.cn/api/anthropic` | `glm-4.7`, `glm-4.6` |
| **MiniMax** | [MiniMax Coding Plan](https://platform.minimaxi.com/subscribe/coding-plan) | `https://api.minimaxi.com/anthropic` | `minimax-m2` |
| **MiniMax** | [MiniMax Platform](https://platform.minimax.io/) | Global: `https://api.minimax.io/v1` (OpenAI) or `https://api.minimax.io/anthropic` (Anthropic)<br>China: `https://api.minimaxi.com/v1` (OpenAI) or `https://api.minimaxi.com/anthropic` (Anthropic) | `MiniMax-M3`, `MiniMax-M2.7` |
| **Kimi** | [Kimi Coding Plan](https://www.kimi.com/membership/pricing) | `https://api.kimi.com/coding/` | `kimi-k2` |

### 2. 配置应用
Expand Down
2 changes: 1 addition & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Download from: https://github.com/OpenCoworkAI/open-cowork/releases
| OpenRouter | https://openrouter.ai/api | claude-4-5-sonnet |
| Anthropic | (Default) | claude-4-5-sonnet |
| Zhipu AI (GLM) | https://open.bigmodel.cn/api/anthropic | glm-4.7, glm-4.6 |
| MiniMax | https://api.minimaxi.com/anthropic | minimax-m2 |
| MiniMax | Global: https://api.minimax.io/v1 (OpenAI) or https://api.minimax.io/anthropic (Anthropic); China: https://api.minimaxi.com/v1 (OpenAI) or https://api.minimaxi.com/anthropic (Anthropic) | MiniMax-M3, MiniMax-M2.7 |
| Kimi | https://api.kimi.com/coding/ | kimi-k2 |

## FAQ
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ You need an API key to power the agent. We support **OpenRouter**, **Anthropic**
| **OpenRouter** | [OpenRouter](https://openrouter.ai/) | `https://openrouter.ai/api` | `claude-4-5-sonnet` |
| **Anthropic** | [Anthropic Console](https://console.anthropic.com/) | (Default) | `claude-4-5-sonnet` |
| **Zhipu AI (GLM)** | [GLM Coding Plan](https://bigmodel.cn/glm-coding) (⚡️Chinese Deal) | `https://open.bigmodel.cn/api/anthropic` | `glm-4.7`, `glm-4.6` |
| **MiniMax** | [MiniMax Coding Plan](https://platform.minimaxi.com/subscribe/coding-plan) | `https://api.minimaxi.com/anthropic` | `minimax-m2` |
| **MiniMax** | [MiniMax Platform](https://platform.minimax.io/) | Global: `https://api.minimax.io/v1` (OpenAI) or `https://api.minimax.io/anthropic` (Anthropic)<br>China: `https://api.minimaxi.com/v1` (OpenAI) or `https://api.minimaxi.com/anthropic` (Anthropic) | `MiniMax-M3`, `MiniMax-M2.7` |
| **Kimi** | [Kimi Coding Plan](https://www.kimi.com/membership/pricing) | `https://api.kimi.com/coding/` | `kimi-k2` |

### 2. Configure
Expand Down
45 changes: 36 additions & 9 deletions src/main/agent/pi-model-resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,36 @@ export function inferPiApi(protocol: string): string {
}
}

interface KnownModelSpec {
contextWindow: number;
maxTokens: number;
reasoning?: boolean;
input?: Model<Api>['input'];
cost?: Model<Api>['cost'];
}

/**
* Known context window / max output specs for common Ollama model families.
* Used as a middle layer between user config overrides and the hardcoded default.
* Known metadata for synthetic models that are not yet present in the pi-ai registry.
* The current runtime schema supports text and image input but not video metadata.
*/
const KNOWN_MODEL_SPECS: Record<string, { contextWindow: number; maxTokens: number }> = {
const EXACT_MODEL_SPECS: Record<string, KnownModelSpec> = {
'minimax-m3': {
contextWindow: 1000000,
maxTokens: 524288,
reasoning: true,
input: ['text', 'image'],
cost: { input: 0.6, output: 2.4, cacheRead: 0.12, cacheWrite: 0 },
},
'minimax-m2.7': {
contextWindow: 204800,
maxTokens: 204800,
reasoning: true,
input: ['text'],
cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 },
},
};

const KNOWN_MODEL_SPECS: Record<string, KnownModelSpec> = {
'qwen3.5': { contextWindow: 258048, maxTokens: 32768 },
qwen3: { contextWindow: 40960, maxTokens: 8192 },
'qwen2.5': { contextWindow: 131072, maxTokens: 8192 },
Expand All @@ -120,10 +145,12 @@ const KNOWN_MODEL_SPECS: Record<string, { contextWindow: number; maxTokens: numb
'command-r': { contextWindow: 131072, maxTokens: 4096 },
};

function lookupModelSpecs(
modelId: string
): { contextWindow: number; maxTokens: number } | undefined {
function lookupModelSpecs(modelId: string): KnownModelSpec | undefined {
const lower = modelId.toLowerCase();
const exact = EXACT_MODEL_SPECS[lower];
if (exact) {
return exact;
}
// Match by prefix: "qwen3.5:0.8b" → "qwen3.5", "deepseek-r1-distill" → "deepseek-r1"
for (const [key, specs] of Object.entries(KNOWN_MODEL_SPECS)) {
if (lower === key || lower.startsWith(key + ':') || lower.startsWith(key + '-')) {
Expand All @@ -144,17 +171,17 @@ export function buildSyntheticPiModel(
maxTokens?: number
): Model<Api> {
const api = apiOverride || inferPiApi(protocol);
const autoReasoning = reasoning ?? REASONING_MODEL_PATTERN.test(modelId);
const knownSpecs = lookupModelSpecs(modelId);
const autoReasoning = reasoning ?? knownSpecs?.reasoning ?? REASONING_MODEL_PATTERN.test(modelId);
return {
id: modelId,
name: modelId,
api,
provider,
baseUrl: baseUrl || '',
reasoning: autoReasoning,
input: ['text', 'image'],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
input: knownSpecs?.input ?? ['text', 'image'],
cost: knownSpecs?.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: contextWindow ?? knownSpecs?.contextWindow ?? 128000,
maxTokens: maxTokens ?? knownSpecs?.maxTokens ?? 16384,
} as Model<Api>;
Expand Down
2 changes: 2 additions & 0 deletions src/shared/api-model-presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export const API_PROVIDER_PRESETS: SharedProviderPresets = {
{ id: 'deepseek-v4-pro', name: 'deepseek-v4-pro' },
{ id: 'kimi-k2-thinking', name: 'kimi-k2-thinking' },
{ id: 'glm-5', name: 'glm-5' },
{ id: 'MiniMax-M3', name: 'MiniMax-M3' },
{ id: 'MiniMax-M2.7', name: 'MiniMax-M2.7' },
{ id: 'MiniMax-M2.5', name: 'MiniMax-M2.5' },
{ id: 'qwen-max', name: 'qwen-max' },
{ id: 'grok-code-fast-1', name: 'grok-code-fast-1' },
Expand Down
74 changes: 62 additions & 12 deletions src/shared/api-provider-guidance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export type CommonProviderSetupId =
| 'glm-anthropic'
| 'ollama'
| 'gemini-custom'
| 'minimax'
| 'minimax-global-openai'
| 'minimax-global-anthropic'
| 'minimax-cn-openai'
| 'minimax-cn-anthropic'
| 'generic-openai';

export interface CommonProviderSetup {
Expand Down Expand Up @@ -126,19 +129,61 @@ export const COMMON_PROVIDER_SETUPS: CommonProviderSetup[] = [
},
},
{
id: 'minimax',
id: 'minimax-global-openai',
nameKey: 'api.guidance.setups.minimax.name',
noteKey: 'api.guidance.setups.minimax.note',
applyProvider: 'custom',
recommendedProtocol: 'openai',
recommendedBaseUrl: 'https://api.minimax.chat/v1',
exampleModel: 'MiniMax-M2.5',
recommendedBaseUrl: 'https://api.minimax.io/v1',
exampleModel: 'MiniMax-M3',
protocolLabel: 'OpenAI (Global)',
matcher: {
hosts: ['api.minimax.chat'],
hostContains: ['minimax'],
hosts: ['api.minimax.io'],
pathPrefixes: ['/v1'],
},
},
{
id: 'minimax-global-anthropic',
nameKey: 'api.guidance.setups.minimax.name',
noteKey: 'api.guidance.setups.minimax.note',
applyProvider: 'custom',
recommendedProtocol: 'anthropic',
recommendedBaseUrl: 'https://api.minimax.io/anthropic',
exampleModel: 'MiniMax-M3',
protocolLabel: 'Anthropic (Global)',
matcher: {
hosts: ['api.minimax.io'],
pathPrefixes: ['/anthropic'],
},
},
{
id: 'minimax-cn-openai',
nameKey: 'api.guidance.setups.minimax.name',
noteKey: 'api.guidance.setups.minimax.note',
applyProvider: 'custom',
recommendedProtocol: 'openai',
recommendedBaseUrl: 'https://api.minimaxi.com/v1',
exampleModel: 'MiniMax-M3',
protocolLabel: 'OpenAI (China)',
matcher: {
hosts: ['api.minimaxi.com'],
pathPrefixes: ['/v1'],
},
},
{
id: 'minimax-cn-anthropic',
nameKey: 'api.guidance.setups.minimax.name',
noteKey: 'api.guidance.setups.minimax.note',
applyProvider: 'custom',
recommendedProtocol: 'anthropic',
recommendedBaseUrl: 'https://api.minimaxi.com/anthropic',
exampleModel: 'MiniMax-M3',
protocolLabel: 'Anthropic (China)',
matcher: {
hosts: ['api.minimaxi.com'],
pathPrefixes: ['/anthropic'],
},
},
{
id: 'generic-openai',
nameKey: 'api.guidance.setups.genericOpenAI.name',
Expand Down Expand Up @@ -186,17 +231,18 @@ function matchesPath(pathname: string, setup: CommonProviderSetup): boolean {
const normalizedPath = pathname.replace(/\/+$/, '') || '/';
const { pathPrefixes, pathIncludes } = setup.matcher || {};

const prefixOk = !pathPrefixes?.length
|| pathPrefixes.some((prefix) => {
const prefixOk =
!pathPrefixes?.length ||
pathPrefixes.some((prefix) => {
const value = prefix || '/';
return normalizedPath === value || normalizedPath.startsWith(`${value}/`);
});
if (!prefixOk) {
return false;
}

const includesOk = !pathIncludes?.length
|| pathIncludes.some((fragment) => normalizedPath.includes(fragment));
const includesOk =
!pathIncludes?.length || pathIncludes.some((fragment) => normalizedPath.includes(fragment));
return includesOk;
}

Expand Down Expand Up @@ -239,7 +285,9 @@ export function detectCommonProviderSetup(baseUrl: string | undefined): CommonPr
return null;
}

export function orderCommonProviderSetups(activeId?: CommonProviderSetupId | null): CommonProviderSetup[] {
export function orderCommonProviderSetups(
activeId?: CommonProviderSetupId | null
): CommonProviderSetup[] {
if (!activeId) {
return COMMON_PROVIDER_SETUPS;
}
Expand All @@ -255,7 +303,9 @@ export function getFallbackOpenAISetup(): CommonProviderSetup {
return COMMON_PROVIDER_SETUPS.find((setup) => setup.id === 'generic-openai')!;
}

function detectProviderGuidanceHintCode(details: string | undefined): ProviderGuidanceHintCode | null {
function detectProviderGuidanceHintCode(
details: string | undefined
): ProviderGuidanceHintCode | null {
const value = details?.trim().toLowerCase();
if (!value) {
return null;
Expand Down
4 changes: 4 additions & 0 deletions tests/api-config-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ describe('api config state helpers', () => {
'kimi-k2-thinking'
);
expect(FALLBACK_PROVIDER_PRESETS.custom.models.map((item) => item.id)).toContain('glm-5');
expect(FALLBACK_PROVIDER_PRESETS.custom.models.map((item) => item.id)).toContain('MiniMax-M3');
expect(FALLBACK_PROVIDER_PRESETS.custom.models.map((item) => item.id)).toContain(
'MiniMax-M2.7'
);
expect(FALLBACK_PROVIDER_PRESETS.custom.models.map((item) => item.id)).toContain(
'MiniMax-M2.5'
);
Expand Down
39 changes: 39 additions & 0 deletions tests/pi-model-resolution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,45 @@ describe('pi model resolution helpers', () => {
expect(model.baseUrl).toBe('https://api.x.ai/v1');
});

it('uses current MiniMax metadata for synthetic models', () => {
const m3 = buildSyntheticPiModel(
'MiniMax-M3',
'anthropic',
'anthropic',
'https://api.minimax.io/anthropic'
);
expect(m3.contextWindow).toBe(1000000);
expect(m3.maxTokens).toBe(524288);
expect(m3.reasoning).toBe(true);
expect(m3.input).toEqual(['text', 'image']);
expect(m3.cost).toEqual({ input: 0.6, output: 2.4, cacheRead: 0.12, cacheWrite: 0 });

const m27 = buildSyntheticPiModel(
'MiniMax-M2.7',
'openai',
'openai',
'https://api.minimaxi.com/v1'
);
expect(m27.contextWindow).toBe(204800);
expect(m27.maxTokens).toBe(204800);
expect(m27.reasoning).toBe(true);
expect(m27.input).toEqual(['text']);
expect(m27.cost).toEqual({
input: 0.3,
output: 1.2,
cacheRead: 0.06,
cacheWrite: 0.375,
});

const unknownVariant = buildSyntheticPiModel('MiniMax-M2.7-custom', 'openai', 'openai');
expect(unknownVariant.cost).toEqual({
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
});
});

it('preserves explicit provider-prefixed ids for openrouter synthetic fallbacks', () => {
const fallback = resolveSyntheticPiModelFallback({
rawModel: 'z-ai/glm-5-turbo',
Expand Down
19 changes: 19 additions & 0 deletions tests/provider-guidance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ describe('provider guidance helpers', () => {
expect(detectCommonProviderSetup('http://localhost:3000/v1')).toBeNull();
});

it('detects each MiniMax region and protocol endpoint with the target model', () => {
const endpoints = [
['https://api.minimax.io/v1', 'minimax-global-openai', 'openai'],
['https://api.minimax.io/anthropic', 'minimax-global-anthropic', 'anthropic'],
['https://api.minimaxi.com/v1', 'minimax-cn-openai', 'openai'],
['https://api.minimaxi.com/anthropic', 'minimax-cn-anthropic', 'anthropic'],
] as const;

for (const [baseUrl, id, protocol] of endpoints) {
const setup = detectCommonProviderSetup(baseUrl);
expect(setup?.id).toBe(id);
expect(setup?.recommendedProtocol).toBe(protocol);
expect(setup?.recommendedBaseUrl).toBe(baseUrl);
expect(setup?.exampleModel).toBe('MiniMax-M3');
}

expect(detectCommonProviderSetup('https://minimax-proxy.example.com/v1')).toBeNull();
});

it('keeps unknown hosts unmatched and exposes the generic OpenAI fallback separately', () => {
expect(detectCommonProviderSetup('https://relay.example.internal/v1')).toBeNull();
expect(getFallbackOpenAISetup().id).toBe('generic-openai');
Expand Down
6 changes: 5 additions & 1 deletion website/public/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ The project documentation lists these provider examples:
- OpenRouter: base URL `https://openrouter.ai/api`, recommended model `claude-4-5-sonnet`
- Anthropic: default base URL, recommended model `claude-4-5-sonnet`
- Zhipu AI / GLM: base URL `https://open.bigmodel.cn/api/anthropic`, recommended models `glm-4.7` and `glm-4.6`
- MiniMax: base URL `https://api.minimaxi.com/anthropic`, recommended model `minimax-m2`
- MiniMax global OpenAI: base URL `https://api.minimax.io/v1`
- MiniMax global Anthropic: base URL `https://api.minimax.io/anthropic`
- MiniMax China OpenAI: base URL `https://api.minimaxi.com/v1`
- MiniMax China Anthropic: base URL `https://api.minimaxi.com/anthropic`
- MiniMax recommended models: `MiniMax-M3` and `MiniMax-M2.7`
- Kimi: base URL `https://api.kimi.com/coding/`, recommended model `kimi-k2`

Any provider with an OpenAI-compatible API can be configured when compatible with the app's model settings.
Expand Down
4 changes: 2 additions & 2 deletions website/public/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@
},
{
"name": "MiniMax",
"baseUrl": "https://api.minimaxi.com/anthropic",
"recommendedModels": ["minimax-m2"]
"baseUrl": "https://api.minimax.io/v1",
"recommendedModels": ["MiniMax-M3", "MiniMax-M2.7"]
},
{
"name": "Kimi",
Expand Down
Loading