diff --git a/README.md b/README.md index ed8737c24..de1dae222 100644 --- a/README.md +++ b/README.md @@ -203,8 +203,8 @@ Advanced and source-build guides: | Codex OAuth | `/provider` | Opens ChatGPT sign-in in your browser and stores Codex credentials securely | | Codex | `/provider` | Uses existing Codex CLI auth, OpenClaude secure storage, or env credentials | | Gitlawb Opengateway | Startup default, `/provider`, or env vars | Smart gateway at `https://opengateway.gitlawb.com/v1`; requires an API key from https://gitlawb.com/opengateway/keys and routes Xiaomi MiMo and GMI Cloud partner models by `OPENAI_MODEL` | -| OpenCode Zen | `/provider` or env vars | Pay-as-you-go AI gateway (43 models); uses `OPENCODE_API_KEY` via `https://opencode.ai/zen/v1`; shared key with OpenCode Go | -| OpenCode Go | `/provider` or env vars | $10/mo subscription for open models (13 models); uses `OPENCODE_API_KEY` via `https://opencode.ai/zen/go/v1`; shared key with OpenCode Zen | +| OpenCode Zen | `/provider` or env vars | Pay-as-you-go AI gateway (48 models); uses `OPENCODE_API_KEY` via `https://opencode.ai/zen/v1`; shared key with OpenCode Go | +| OpenCode Go | `/provider` or env vars | $10/mo subscription for open models (20 models); uses `OPENCODE_API_KEY` via `https://opencode.ai/zen/go/v1`; shared key with OpenCode Zen | | Xiaomi MiMo | `/provider` or env vars | OpenAI-compatible API at `https://mimo.mi.com`; uses `MIMO_API_KEY` and defaults to `mimo-v2.5-pro` | | NEAR AI | `/provider` or env vars | Unified gateway (Claude, GPT, Gemini + TEE open models); uses `NEARAI_API_KEY` at `https://cloud-api.near.ai/v1` | | Ollama | `/provider` or env vars | Local inference with no API key | diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index be402d4ea..664e55f32 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -197,7 +197,7 @@ export OPENAI_MODEL=gpt-5.4 openclaude ``` -OpenCode Zen is a pay-as-you-go AI gateway with 43 models (GPT, Claude, Gemini, +OpenCode Zen is a pay-as-you-go AI gateway with 48 models (GPT, Claude, Gemini, Qwen, MiniMax, GLM, Kimi, Grok, Big Pickle, DeepSeek, Nemotron). Uses the same `OPENCODE_API_KEY` as OpenCode Go. Get your key from https://opencode.ai. @@ -212,7 +212,7 @@ export OPENAI_MODEL=glm-5.1 openclaude ``` -OpenCode Go is a $10/mo subscription for 13 open models (GLM, Kimi, DeepSeek, +OpenCode Go is a $10/mo subscription for 20 open models (GLM, Kimi, DeepSeek, MiMo, MiniMax, Qwen). Uses the same `OPENCODE_API_KEY` as OpenCode Zen. ### Gitlawb Opengateway diff --git a/src/integrations/gateways/nvidia-nim.ts b/src/integrations/gateways/nvidia-nim.ts index 14d170c28..45b731fb5 100644 --- a/src/integrations/gateways/nvidia-nim.ts +++ b/src/integrations/gateways/nvidia-nim.ts @@ -92,6 +92,12 @@ export default defineGateway({ label: 'Llama 3.1 Nemotron 70B', modelDescriptorId: 'nvidia/llama-3.1-nemotron-70b-instruct', }, + { + id: 'nvidia-deepseek-v4-pro', + apiName: 'deepseek-ai/deepseek-v4-pro', + label: 'DeepSeek V4 Pro', + modelDescriptorId: 'deepseek-v4-pro', + }, ], }, usage: { supported: false }, diff --git a/src/integrations/gateways/opencode-go.ts b/src/integrations/gateways/opencode-go.ts index e4eeb3138..ffb383711 100644 --- a/src/integrations/gateways/opencode-go.ts +++ b/src/integrations/gateways/opencode-go.ts @@ -1,5 +1,54 @@ import { defineGateway } from '../define.js' +type OpenCodeGoCatalogSpec = { + id: string + apiName: string + label: string + endpointPath?: string +} + +function catalogEntry(spec: OpenCodeGoCatalogSpec) { + return { + id: `opencode-go-${spec.id}`, + apiName: spec.apiName, + label: spec.label, + modelDescriptorId: `opencode-go-${spec.id}`, + ...(spec.endpointPath + ? { + transportOverrides: { + openaiShim: { + endpointPath: spec.endpointPath, + defaultAuthHeader: { name: 'x-api-key', scheme: 'raw' as const }, + }, + }, + } + : {}), + } +} + +const goModels: OpenCodeGoCatalogSpec[] = [ + { id: 'minimax-m3', apiName: 'minimax-m3', label: 'MiniMax M3', endpointPath: '/messages' }, + { id: 'minimax-m2.7', apiName: 'minimax-m2.7', label: 'MiniMax M2.7', endpointPath: '/messages' }, + { id: 'minimax-m2.5', apiName: 'minimax-m2.5', label: 'MiniMax M2.5', endpointPath: '/messages' }, + { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', label: 'Kimi K2.7 Code' }, + { id: 'kimi-k2.6', apiName: 'kimi-k2.6', label: 'Kimi K2.6' }, + { id: 'kimi-k2.5', apiName: 'kimi-k2.5', label: 'Kimi K2.5' }, + { id: 'glm-5.2', apiName: 'glm-5.2', label: 'GLM 5.2' }, + { id: 'glm-5.1', apiName: 'glm-5.1', label: 'GLM 5.1' }, + { id: 'glm-5', apiName: 'glm-5', label: 'GLM 5' }, + { id: 'deepseek-v4-pro', apiName: 'deepseek-v4-pro', label: 'DeepSeek V4 Pro' }, + { id: 'deepseek-v4-flash', apiName: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash' }, + { id: 'qwen3.7-max', apiName: 'qwen3.7-max', label: 'Qwen3.7 Max', endpointPath: '/messages' }, + { id: 'qwen3.7-plus', apiName: 'qwen3.7-plus', label: 'Qwen3.7 Plus', endpointPath: '/messages' }, + { id: 'qwen3.6-plus', apiName: 'qwen3.6-plus', label: 'Qwen3.6 Plus', endpointPath: '/messages' }, + { id: 'qwen3.5-plus', apiName: 'qwen3.5-plus', label: 'Qwen3.5 Plus', endpointPath: '/messages' }, + { id: 'mimo-v2-pro', apiName: 'mimo-v2-pro', label: 'MiMo V2 Pro' }, + { id: 'mimo-v2-omni', apiName: 'mimo-v2-omni', label: 'MiMo V2 Omni' }, + { id: 'mimo-v2.5-pro', apiName: 'mimo-v2.5-pro', label: 'MiMo V2.5 Pro' }, + { id: 'mimo-v2.5', apiName: 'mimo-v2.5', label: 'MiMo V2.5' }, + { id: 'hy3-preview', apiName: 'hy3-preview', label: 'HY3 Preview' }, +] + export default defineGateway({ id: 'opencode-go', label: 'OpenCode Go', @@ -29,29 +78,13 @@ export default defineGateway({ preset: { id: 'opencode-go', vendorId: 'openai', - description: 'OpenCode Go — $10/mo subscription for open models (13 models)', + description: 'OpenCode Go - $10/mo subscription for open models (20 models)', apiKeyEnvVars: ['OPENCODE_API_KEY'], modelEnvVars: ['OPENAI_MODEL'], }, catalog: { source: 'static', - models: [ - // OpenAI-compatible — /zen/go/v1/chat/completions - { id: 'opencode-go-glm-5.1', apiName: 'glm-5.1', label: 'GLM 5.1', modelDescriptorId: 'opencode-go-glm-5.1' }, - { id: 'opencode-go-glm-5', apiName: 'glm-5', label: 'GLM 5', modelDescriptorId: 'opencode-go-glm-5' }, - { id: 'opencode-go-kimi-k2.5', apiName: 'kimi-k2.5', label: 'Kimi K2.5', modelDescriptorId: 'opencode-go-kimi-k2.5' }, - { id: 'opencode-go-kimi-k2.6', apiName: 'kimi-k2.6', label: 'Kimi K2.6', modelDescriptorId: 'opencode-go-kimi-k2.6' }, - { id: 'opencode-go-deepseek-v4-pro', apiName: 'deepseek-v4-pro', label: 'DeepSeek V4 Pro', modelDescriptorId: 'opencode-go-deepseek-v4-pro' }, - { id: 'opencode-go-deepseek-v4-flash', apiName: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash', modelDescriptorId: 'opencode-go-deepseek-v4-flash' }, - { id: 'opencode-go-mimo-v2.5', apiName: 'mimo-v2.5', label: 'MiMo V2.5', modelDescriptorId: 'opencode-go-mimo-v2.5' }, - { id: 'opencode-go-mimo-v2.5-pro', apiName: 'mimo-v2.5-pro', label: 'MiMo V2.5 Pro', modelDescriptorId: 'opencode-go-mimo-v2.5-pro' }, - // Anthropic Messages API — /zen/go/v1/messages with x-api-key auth - { id: 'opencode-go-minimax-m2.7', apiName: 'minimax-m2.7', label: 'MiniMax M2.7', modelDescriptorId: 'opencode-go-minimax-m2.7', transportOverrides: { openaiShim: { endpointPath: '/messages', defaultAuthHeader: { name: 'x-api-key', scheme: 'raw' } } } }, - { id: 'opencode-go-minimax-m2.5', apiName: 'minimax-m2.5', label: 'MiniMax M2.5', modelDescriptorId: 'opencode-go-minimax-m2.5', transportOverrides: { openaiShim: { endpointPath: '/messages', defaultAuthHeader: { name: 'x-api-key', scheme: 'raw' } } } }, - { id: 'opencode-go-qwen3.6-plus', apiName: 'qwen3.6-plus', label: 'Qwen3.6 Plus', modelDescriptorId: 'opencode-go-qwen3.6-plus', transportOverrides: { openaiShim: { endpointPath: '/messages', defaultAuthHeader: { name: 'x-api-key', scheme: 'raw' } } } }, - { id: 'opencode-go-qwen3.5-plus', apiName: 'qwen3.5-plus', label: 'Qwen3.5 Plus', modelDescriptorId: 'opencode-go-qwen3.5-plus', transportOverrides: { openaiShim: { endpointPath: '/messages', defaultAuthHeader: { name: 'x-api-key', scheme: 'raw' } } } }, - { id: 'opencode-go-minimax-m3', apiName: 'minimax-m3', label: 'MiniMax M3', modelDescriptorId: 'opencode-go-minimax-m3', transportOverrides: { openaiShim: { endpointPath: '/messages', defaultAuthHeader: { name: 'x-api-key', scheme: 'raw' } } } }, - ], + models: goModels.map(catalogEntry), }, usage: { supported: false }, }) diff --git a/src/integrations/gateways/opencode.test.ts b/src/integrations/gateways/opencode.test.ts index f67ee9c8a..36a805d7c 100644 --- a/src/integrations/gateways/opencode.test.ts +++ b/src/integrations/gateways/opencode.test.ts @@ -284,12 +284,12 @@ describe('OpenCode model catalog', () => { test('zen model count matches expected', () => { const models = getCatalogEntriesForRoute('opencode') - expect(models.length).toBe(43) + expect(models.length).toBe(48) }) test('go model count matches expected', () => { const models = getCatalogEntriesForRoute('opencode-go') - expect(models.length).toBe(13) + expect(models.length).toBe(20) }) test('all zen gpt models have modelDescriptorId', () => { @@ -324,6 +324,8 @@ describe('OpenCode model catalog', () => { 'opencode-go-minimax-m3', 'opencode-go-minimax-m2.7', 'opencode-go-minimax-m2.5', + 'opencode-go-qwen3.7-max', + 'opencode-go-qwen3.7-plus', 'opencode-go-qwen3.6-plus', 'opencode-go-qwen3.5-plus', ] @@ -466,6 +468,19 @@ describe('OpenCode edge cases', () => { } }) + test('model descriptors preserve OpenCode provider limit metadata', () => { + const models = new Map( + getAllModels() + .filter(m => m.id.startsWith('opencode-')) + .map(model => [model.id, model]), + ) + + expect(models.get('opencode-qwen3.6-plus')?.contextWindow).toBe(262_144) + expect(models.get('opencode-deepseek-v4-pro')?.maxOutputTokens).toBe(384_000) + expect(models.get('opencode-go-minimax-m2.5')?.maxOutputTokens).toBe(65_536) + expect(models.get('opencode-go-hy3-preview')?.contextWindow).toBe(256_000) + }) + test('zen gateway validation message mentions OPENCODE_API_KEY', () => { const gateway = getGateway('opencode') expect(gateway!.validation!.missingCredentialMessage).toContain('OPENCODE_API_KEY') diff --git a/src/integrations/gateways/opencode.ts b/src/integrations/gateways/opencode.ts index 384854923..30155ca59 100644 --- a/src/integrations/gateways/opencode.ts +++ b/src/integrations/gateways/opencode.ts @@ -1,5 +1,74 @@ import { defineGateway } from '../define.js' +type OpenCodeCatalogSpec = { + id: string + label: string + endpointPath?: string +} + +function catalogEntry(spec: OpenCodeCatalogSpec) { + return { + id: spec.id, + apiName: spec.id, + label: spec.label, + modelDescriptorId: `opencode-${spec.id}`, + ...(spec.endpointPath + ? { transportOverrides: { openaiShim: { endpointPath: spec.endpointPath } } } + : {}), + } +} + +const zenModels: OpenCodeCatalogSpec[] = [ + { id: 'claude-fable-5', label: 'Claude Fable 5', endpointPath: '/messages' }, + { id: 'claude-opus-4-8', label: 'Claude Opus 4.8', endpointPath: '/messages' }, + { id: 'claude-opus-4-7', label: 'Claude Opus 4.7', endpointPath: '/messages' }, + { id: 'claude-opus-4-6', label: 'Claude Opus 4.6', endpointPath: '/messages' }, + { id: 'claude-opus-4-5', label: 'Claude Opus 4.5', endpointPath: '/messages' }, + { id: 'claude-opus-4-1', label: 'Claude Opus 4.1', endpointPath: '/messages' }, + { id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6', endpointPath: '/messages' }, + { id: 'claude-sonnet-4-5', label: 'Claude Sonnet 4.5', endpointPath: '/messages' }, + { id: 'claude-sonnet-4', label: 'Claude Sonnet 4', endpointPath: '/messages' }, + { id: 'claude-haiku-4-5', label: 'Claude Haiku 4.5', endpointPath: '/messages' }, + { id: 'gemini-3.5-flash', label: 'Gemini 3.5 Flash', endpointPath: '/models/gemini-3.5-flash' }, + { id: 'gemini-3.1-pro', label: 'Gemini 3.1 Pro', endpointPath: '/models/gemini-3.1-pro' }, + { id: 'gemini-3-flash', label: 'Gemini 3 Flash', endpointPath: '/models/gemini-3-flash' }, + { id: 'gpt-5.5', label: 'GPT 5.5', endpointPath: '/responses' }, + { id: 'gpt-5.5-pro', label: 'GPT 5.5 Pro', endpointPath: '/responses' }, + { id: 'gpt-5.4', label: 'GPT 5.4', endpointPath: '/responses' }, + { id: 'gpt-5.4-pro', label: 'GPT 5.4 Pro', endpointPath: '/responses' }, + { id: 'gpt-5.4-mini', label: 'GPT 5.4 Mini', endpointPath: '/responses' }, + { id: 'gpt-5.4-nano', label: 'GPT 5.4 Nano', endpointPath: '/responses' }, + { id: 'gpt-5.3-codex-spark', label: 'GPT 5.3 Codex Spark', endpointPath: '/responses' }, + { id: 'gpt-5.3-codex', label: 'GPT 5.3 Codex', endpointPath: '/responses' }, + { id: 'gpt-5.2', label: 'GPT 5.2', endpointPath: '/responses' }, + { id: 'gpt-5.2-codex', label: 'GPT 5.2 Codex', endpointPath: '/responses' }, + { id: 'gpt-5.1', label: 'GPT 5.1', endpointPath: '/responses' }, + { id: 'gpt-5.1-codex-max', label: 'GPT 5.1 Codex Max', endpointPath: '/responses' }, + { id: 'gpt-5.1-codex', label: 'GPT 5.1 Codex', endpointPath: '/responses' }, + { id: 'gpt-5.1-codex-mini', label: 'GPT 5.1 Codex Mini', endpointPath: '/responses' }, + { id: 'gpt-5', label: 'GPT 5', endpointPath: '/responses' }, + { id: 'gpt-5-codex', label: 'GPT 5 Codex', endpointPath: '/responses' }, + { id: 'gpt-5-nano', label: 'GPT 5 Nano', endpointPath: '/responses' }, + { id: 'grok-build-0.1', label: 'Grok Build 0.1' }, + { id: 'deepseek-v4-pro', label: 'DeepSeek V4 Pro' }, + { id: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash' }, + { id: 'glm-5.1', label: 'GLM 5.1' }, + { id: 'glm-5', label: 'GLM 5' }, + { id: 'minimax-m2.7', label: 'MiniMax M2.7' }, + { id: 'minimax-m2.5', label: 'MiniMax M2.5' }, + { id: 'kimi-k2.6', label: 'Kimi K2.6' }, + { id: 'kimi-k2.5', label: 'Kimi K2.5' }, + { id: 'qwen3.6-plus', label: 'Qwen3.6 Plus', endpointPath: '/messages' }, + { id: 'qwen3.5-plus', label: 'Qwen3.5 Plus', endpointPath: '/messages' }, + { id: 'big-pickle', label: 'Big Pickle' }, + { id: 'deepseek-v4-flash-free', label: 'DeepSeek V4 Flash Free' }, + { id: 'mimo-v2.5-free', label: 'MiMo V2.5 Free' }, + { id: 'qwen3.6-plus-free', label: 'Qwen3.6 Plus Free', endpointPath: '/messages' }, + { id: 'minimax-m3-free', label: 'MiniMax M3 Free' }, + { id: 'nemotron-3-ultra-free', label: 'Nemotron 3 Ultra Free' }, + { id: 'north-mini-code-free', label: 'North Mini Code Free' }, +] + export default defineGateway({ id: 'opencode', label: 'OpenCode Zen', @@ -20,7 +89,7 @@ export default defineGateway({ preset: { id: 'opencode', vendorId: 'openai', - description: 'OpenCode Zen — pay-as-you-go AI gateway (43 models)', + description: 'OpenCode Zen - pay-as-you-go AI gateway (48 models)', apiKeyEnvVars: ['OPENCODE_API_KEY'], modelEnvVars: ['OPENAI_MODEL'], }, @@ -35,56 +104,7 @@ export default defineGateway({ }, catalog: { source: 'static', - models: [ - // GPT family — /zen/v1/responses - { id: 'gpt-5.5', apiName: 'gpt-5.5', label: 'GPT 5.5', modelDescriptorId: 'opencode-gpt-5.5', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.5-pro', apiName: 'gpt-5.5-pro', label: 'GPT 5.5 Pro', modelDescriptorId: 'opencode-gpt-5.5-pro', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.4', apiName: 'gpt-5.4', label: 'GPT 5.4', modelDescriptorId: 'opencode-gpt-5.4', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.4-pro', apiName: 'gpt-5.4-pro', label: 'GPT 5.4 Pro', modelDescriptorId: 'opencode-gpt-5.4-pro', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.4-mini', apiName: 'gpt-5.4-mini', label: 'GPT 5.4 Mini', modelDescriptorId: 'opencode-gpt-5.4-mini', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.4-nano', apiName: 'gpt-5.4-nano', label: 'GPT 5.4 Nano', modelDescriptorId: 'opencode-gpt-5.4-nano', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.3-codex', apiName: 'gpt-5.3-codex', label: 'GPT 5.3 Codex', modelDescriptorId: 'opencode-gpt-5.3-codex', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.3-codex-spark', apiName: 'gpt-5.3-codex-spark', label: 'GPT 5.3 Codex Spark', modelDescriptorId: 'opencode-gpt-5.3-codex-spark', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.2', apiName: 'gpt-5.2', label: 'GPT 5.2', modelDescriptorId: 'opencode-gpt-5.2', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.2-codex', apiName: 'gpt-5.2-codex', label: 'GPT 5.2 Codex', modelDescriptorId: 'opencode-gpt-5.2-codex', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.1', apiName: 'gpt-5.1', label: 'GPT 5.1', modelDescriptorId: 'opencode-gpt-5.1', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.1-codex', apiName: 'gpt-5.1-codex', label: 'GPT 5.1 Codex', modelDescriptorId: 'opencode-gpt-5.1-codex', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.1-codex-max', apiName: 'gpt-5.1-codex-max', label: 'GPT 5.1 Codex Max', modelDescriptorId: 'opencode-gpt-5.1-codex-max', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5.1-codex-mini', apiName: 'gpt-5.1-codex-mini', label: 'GPT 5.1 Codex Mini', modelDescriptorId: 'opencode-gpt-5.1-codex-mini', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5', apiName: 'gpt-5', label: 'GPT 5', modelDescriptorId: 'opencode-gpt-5', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5-codex', apiName: 'gpt-5-codex', label: 'GPT 5 Codex', modelDescriptorId: 'opencode-gpt-5-codex', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - { id: 'gpt-5-nano', apiName: 'gpt-5-nano', label: 'GPT 5 Nano', modelDescriptorId: 'opencode-gpt-5-nano', transportOverrides: { openaiShim: { endpointPath: '/responses' } } }, - // Claude family — /zen/v1/messages - { id: 'claude-opus-4-7', apiName: 'claude-opus-4-7', label: 'Claude Opus 4.7', modelDescriptorId: 'opencode-claude-opus-4-7', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - { id: 'claude-opus-4-8', apiName: 'claude-opus-4-8', label: 'Claude Opus 4.8', modelDescriptorId: 'opencode-claude-opus-4-8', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - { id: 'claude-opus-4-6', apiName: 'claude-opus-4-6', label: 'Claude Opus 4.6', modelDescriptorId: 'opencode-claude-opus-4-6', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - { id: 'claude-opus-4-5', apiName: 'claude-opus-4-5', label: 'Claude Opus 4.5', modelDescriptorId: 'opencode-claude-opus-4-5', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - { id: 'claude-opus-4-1', apiName: 'claude-opus-4-1', label: 'Claude Opus 4.1', modelDescriptorId: 'opencode-claude-opus-4-1', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - { id: 'claude-sonnet-4-6', apiName: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6', modelDescriptorId: 'opencode-claude-sonnet-4-6', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - { id: 'claude-sonnet-4-5', apiName: 'claude-sonnet-4-5', label: 'Claude Sonnet 4.5', modelDescriptorId: 'opencode-claude-sonnet-4-5', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - { id: 'claude-sonnet-4', apiName: 'claude-sonnet-4', label: 'Claude Sonnet 4', modelDescriptorId: 'opencode-claude-sonnet-4', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - { id: 'claude-haiku-4-5', apiName: 'claude-haiku-4-5', label: 'Claude Haiku 4.5', modelDescriptorId: 'opencode-claude-haiku-4-5', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - { id: 'claude-3-5-haiku', apiName: 'claude-3-5-haiku', label: 'Claude Haiku 3.5', modelDescriptorId: 'opencode-claude-3-5-haiku', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - // Gemini family — /zen/v1/models/ - { id: 'gemini-3.5-flash', apiName: 'gemini-3.5-flash', label: 'Gemini 3.5 Flash', modelDescriptorId: 'opencode-gemini-3.5-flash', transportOverrides: { openaiShim: { endpointPath: '/models/gemini-3.5-flash' } } }, - { id: 'gemini-3.1-pro', apiName: 'gemini-3.1-pro', label: 'Gemini 3.1 Pro', modelDescriptorId: 'opencode-gemini-3.1-pro', transportOverrides: { openaiShim: { endpointPath: '/models/gemini-3.1-pro' } } }, - { id: 'gemini-3-flash', apiName: 'gemini-3-flash', label: 'Gemini 3 Flash', modelDescriptorId: 'opencode-gemini-3-flash', transportOverrides: { openaiShim: { endpointPath: '/models/gemini-3-flash' } } }, - // Qwen — /zen/v1/messages - { id: 'qwen3.6-plus', apiName: 'qwen3.6-plus', label: 'Qwen3.6 Plus', modelDescriptorId: 'opencode-qwen3.6-plus', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - { id: 'qwen3.5-plus', apiName: 'qwen3.5-plus', label: 'Qwen3.5 Plus', modelDescriptorId: 'opencode-qwen3.5-plus', transportOverrides: { openaiShim: { endpointPath: '/messages' } } }, - // OpenAI-compatible — /zen/v1/chat/completions (default, no override needed) - { id: 'minimax-m2.7', apiName: 'minimax-m2.7', label: 'MiniMax M2.7', modelDescriptorId: 'opencode-minimax-m2.7' }, - { id: 'minimax-m2.5', apiName: 'minimax-m2.5', label: 'MiniMax M2.5', modelDescriptorId: 'opencode-minimax-m2.5' }, - { id: 'glm-5.1', apiName: 'glm-5.1', label: 'GLM 5.1', modelDescriptorId: 'opencode-glm-5.1' }, - { id: 'glm-5', apiName: 'glm-5', label: 'GLM 5', modelDescriptorId: 'opencode-glm-5' }, - { id: 'kimi-k2.5', apiName: 'kimi-k2.5', label: 'Kimi K2.5', modelDescriptorId: 'opencode-kimi-k2.5' }, - { id: 'kimi-k2.6', apiName: 'kimi-k2.6', label: 'Kimi K2.6', modelDescriptorId: 'opencode-kimi-k2.6' }, - { id: 'grok-build-0.1', apiName: 'grok-build-0.1', label: 'Grok Build 0.1', modelDescriptorId: 'opencode-grok-build-0.1' }, - { id: 'big-pickle', apiName: 'big-pickle', label: 'Big Pickle', modelDescriptorId: 'opencode-big-pickle' }, - { id: 'deepseek-v4-flash-free', apiName: 'deepseek-v4-flash-free', label: 'DeepSeek V4 Flash Free', modelDescriptorId: 'opencode-deepseek-v4-flash-free' }, - { id: 'nemotron-3-super-free', apiName: 'nemotron-3-super-free', label: 'Nemotron 3 Super Free', modelDescriptorId: 'opencode-nemotron-3-super-free' }, - { id: 'mimo-v2.5-free', apiName: 'mimo-v2.5-free', label: 'MiMo V2.5 Free', modelDescriptorId: 'opencode-mimo-v2.5-free' }, - ], + models: zenModels.map(catalogEntry), }, usage: { supported: false }, }) diff --git a/src/integrations/generated/integrationArtifacts.generated.ts b/src/integrations/generated/integrationArtifacts.generated.ts index 745a44b45..585ae5d3a 100644 --- a/src/integrations/generated/integrationArtifacts.generated.ts +++ b/src/integrations/generated/integrationArtifacts.generated.ts @@ -357,7 +357,7 @@ export const PROVIDER_PRESET_MANIFEST = [ "routeId": "opencode-go", "vendorId": "openai", "gatewayId": "opencode-go", - "description": "OpenCode Go — $10/mo subscription for open models (13 models)", + "description": "OpenCode Go - $10/mo subscription for open models (20 models)", "apiKeyEnvVars": [ "OPENCODE_API_KEY" ], @@ -371,7 +371,7 @@ export const PROVIDER_PRESET_MANIFEST = [ "routeId": "opencode", "vendorId": "openai", "gatewayId": "opencode", - "description": "OpenCode Zen — pay-as-you-go AI gateway (43 models)", + "description": "OpenCode Zen - pay-as-you-go AI gateway (48 models)", "apiKeyEnvVars": [ "OPENCODE_API_KEY" ], diff --git a/src/integrations/models/opencode.ts b/src/integrations/models/opencode.ts index ccf0d6303..98781b1e7 100644 --- a/src/integrations/models/opencode.ts +++ b/src/integrations/models/opencode.ts @@ -1,1033 +1,121 @@ import { defineModel } from '../define.js' -export default [ - // ============================================================ - // ZEN MODELS — https://opencode.ai/zen/v1 - // ============================================================ +const baseCapabilities = { + supportsStreaming: true, + supportsFunctionCalling: true, + supportsJsonMode: true, + supportsPreciseTokenCount: false, +} - // --- GPT family (responses endpoint) --- - defineModel({ - id: 'opencode-gpt-5.5', - label: 'GPT 5.5', +type OpenCodeModelSpec = { + id: string + label: string + defaultModel: string + contextWindow: number + maxOutputTokens: number + reasoning?: boolean + vision?: boolean + coding?: boolean +} +function openCodeModel(spec: OpenCodeModelSpec) { + return defineModel({ + id: spec.id, + label: spec.label, vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-gpt-5.5', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, + classification: [ + 'chat', + ...(spec.reasoning ? ['reasoning' as const] : []), + ...(spec.vision ? ['vision' as const] : []), + ...(spec.coding ? ['coding' as const] : []), + ], + defaultModel: spec.defaultModel, + providerModelMap: { + [spec.id.startsWith('opencode-go-') ? 'opencode-go' : 'opencode']: + spec.defaultModel, }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5.5-pro', - label: 'GPT 5.5 Pro', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-gpt-5.5-pro', capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, + ...baseCapabilities, + supportsVision: spec.vision === true, + supportsReasoning: spec.reasoning === true, }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5.4', - label: 'GPT 5.4', + contextWindow: spec.contextWindow, + maxOutputTokens: spec.maxOutputTokens, + }) +} - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-gpt-5.4', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5.4-pro', - label: 'GPT 5.4 Pro', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-gpt-5.4-pro', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5.4-mini', - label: 'GPT 5.4 Mini', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-gpt-5.4-mini', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5.4-nano', - label: 'GPT 5.4 Nano', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-gpt-5.4-nano', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 128_000, - maxOutputTokens: 16_384, - }), - defineModel({ - id: 'opencode-gpt-5.3-codex', - label: 'GPT 5.3 Codex', - - vendorId: 'openai', - classification: ['chat', 'coding'], - defaultModel: 'opencode-gpt-5.3-codex', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5.3-codex-spark', - label: 'GPT 5.3 Codex Spark', - - vendorId: 'openai', - classification: ['chat', 'coding'], - defaultModel: 'opencode-gpt-5.3-codex-spark', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 256_000, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-gpt-5.2', - label: 'GPT 5.2', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-gpt-5.2', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5.2-codex', - label: 'GPT 5.2 Codex', - - vendorId: 'openai', - classification: ['chat', 'coding'], - defaultModel: 'opencode-gpt-5.2-codex', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5.1', - label: 'GPT 5.1', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-gpt-5.1', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5.1-codex', - label: 'GPT 5.1 Codex', - - vendorId: 'openai', - classification: ['chat', 'coding'], - defaultModel: 'opencode-gpt-5.1-codex', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5.1-codex-max', - label: 'GPT 5.1 Codex Max', - - vendorId: 'openai', - classification: ['chat', 'coding'], - defaultModel: 'opencode-gpt-5.1-codex-max', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5.1-codex-mini', - label: 'GPT 5.1 Codex Mini', - - vendorId: 'openai', - classification: ['chat', 'coding'], - defaultModel: 'opencode-gpt-5.1-codex-mini', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 256_000, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-gpt-5', - label: 'GPT 5', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-gpt-5', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5-codex', - label: 'GPT 5 Codex', - - vendorId: 'openai', - classification: ['chat', 'coding'], - defaultModel: 'opencode-gpt-5-codex', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gpt-5-nano', - label: 'GPT 5 Nano', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-gpt-5-nano', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 128_000, - maxOutputTokens: 16_384, - }), - - // --- Claude family (messages endpoint) --- - defineModel({ - id: 'opencode-claude-opus-4-7', - label: 'Claude Opus 4.7', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-claude-opus-4-7', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 200_000, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-claude-opus-4-8', - label: 'Claude Opus 4.8', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-claude-opus-4-8', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 200_000, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-claude-opus-4-6', - label: 'Claude Opus 4.6', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-claude-opus-4-6', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 200_000, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-claude-opus-4-5', - label: 'Claude Opus 4.5', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-claude-opus-4-5', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 200_000, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-claude-opus-4-1', - label: 'Claude Opus 4.1', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-claude-opus-4-1', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 200_000, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-claude-sonnet-4-6', - label: 'Claude Sonnet 4.6', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-claude-sonnet-4-6', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 200_000, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-claude-sonnet-4-5', - label: 'Claude Sonnet 4.5', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-claude-sonnet-4-5', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 200_000, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-claude-sonnet-4', - label: 'Claude Sonnet 4', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-claude-sonnet-4', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 200_000, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-claude-haiku-4-5', - label: 'Claude Haiku 4.5', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-claude-haiku-4-5', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 200_000, - maxOutputTokens: 8_192, - }), - defineModel({ - id: 'opencode-claude-3-5-haiku', - label: 'Claude Haiku 3.5', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-claude-3-5-haiku', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 200_000, - maxOutputTokens: 8_192, - }), - - // --- Gemini family (Google AI native endpoint) --- - defineModel({ - id: 'opencode-gemini-3.5-flash', - label: 'Gemini 3.5 Flash', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-gemini-3.5-flash', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gemini-3.1-pro', - label: 'Gemini 3.1 Pro', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-gemini-3.1-pro', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-gemini-3-flash', - label: 'Gemini 3 Flash', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-gemini-3-flash', - capabilities: { - supportsVision: true, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - - // --- Qwen (messages endpoint) --- - defineModel({ - id: 'opencode-qwen3.6-plus', - label: 'Qwen3.6 Plus', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-qwen3.6-plus', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-qwen3.5-plus', - label: 'Qwen3.5 Plus', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-qwen3.5-plus', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - - // --- OpenAI-compatible (chat/completions endpoint) --- - defineModel({ - id: 'opencode-minimax-m2.7', - label: 'MiniMax M2.7', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-minimax-m2.7', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-minimax-m2.5', - label: 'MiniMax M2.5', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-minimax-m2.5', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-glm-5.1', - label: 'GLM 5.1', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-glm-5.1', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-glm-5', - label: 'GLM 5', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-glm-5', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-kimi-k2.5', - label: 'Kimi K2.5', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-kimi-k2.5', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-kimi-k2.6', - label: 'Kimi K2.6', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-kimi-k2.6', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-grok-build-0.1', - label: 'Grok Build 0.1', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-grok-build-0.1', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-big-pickle', - label: 'Big Pickle', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-big-pickle', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-deepseek-v4-flash-free', - label: 'DeepSeek V4 Flash Free', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-deepseek-v4-flash-free', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-nemotron-3-super-free', - label: 'Nemotron 3 Super Free', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-nemotron-3-super-free', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-mimo-v2.5-free', - label: 'MiMo V2.5 Free', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-mimo-v2.5-free', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - - // ============================================================ - // GO MODELS — https://opencode.ai/zen/go/v1 - // ============================================================ - - // --- OpenAI-compatible (chat/completions endpoint) --- - defineModel({ - id: 'opencode-go-glm-5.1', - label: 'GLM 5.1', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-glm-5.1', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-go-glm-5', - label: 'GLM 5', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-glm-5', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-go-kimi-k2.5', - label: 'Kimi K2.5', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-kimi-k2.5', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-go-kimi-k2.6', - label: 'Kimi K2.6', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-kimi-k2.6', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-go-deepseek-v4-pro', - label: 'DeepSeek V4 Pro', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-deepseek-v4-pro', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-go-deepseek-v4-flash', - label: 'DeepSeek V4 Flash', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-deepseek-v4-flash', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 1_048_576, - maxOutputTokens: 65_536, - }), - defineModel({ - id: 'opencode-go-mimo-v2.5', - label: 'MiMo V2.5', - - vendorId: 'openai', - classification: ['chat', 'coding'], - defaultModel: 'opencode-mimo-v2.5', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-go-mimo-v2.5-pro', - label: 'MiMo V2.5 Pro', - - vendorId: 'openai', - classification: ['chat', 'coding'], - defaultModel: 'opencode-mimo-v2.5-pro', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - - // --- Anthropic Messages API (x-api-key auth) --- - defineModel({ - id: 'opencode-go-minimax-m2.7', - label: 'MiniMax M2.7', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-minimax-m2.7', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-go-minimax-m2.5', - label: 'MiniMax M2.5', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'opencode-minimax-m2.5', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-go-qwen3.6-plus', - label: 'Qwen3.6 Plus', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-qwen3.6-plus', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-go-qwen3.5-plus', - label: 'Qwen3.5 Plus', - - vendorId: 'openai', - classification: ['chat', 'reasoning'], - defaultModel: 'opencode-qwen3.5-plus', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: true, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), - defineModel({ - id: 'opencode-go-minimax-m3', - label: 'MiniMax M3', - - vendorId: 'openai', - classification: ['chat'], - defaultModel: 'minimax-m3', - capabilities: { - supportsVision: false, - supportsStreaming: true, - supportsFunctionCalling: true, - supportsJsonMode: true, - supportsReasoning: false, - supportsPreciseTokenCount: false, - }, - contextWindow: 131_072, - maxOutputTokens: 32_768, - }), +const zenModels: OpenCodeModelSpec[] = [ + { id: 'opencode-claude-fable-5', label: 'Claude Fable 5', defaultModel: 'claude-fable-5', contextWindow: 1_000_000, maxOutputTokens: 128_000, reasoning: true, vision: true }, + { id: 'opencode-claude-opus-4-8', label: 'Claude Opus 4.8', defaultModel: 'claude-opus-4-8', contextWindow: 1_000_000, maxOutputTokens: 128_000, reasoning: true, vision: true }, + { id: 'opencode-claude-opus-4-7', label: 'Claude Opus 4.7', defaultModel: 'claude-opus-4-7', contextWindow: 1_000_000, maxOutputTokens: 128_000, reasoning: true, vision: true }, + { id: 'opencode-claude-opus-4-6', label: 'Claude Opus 4.6', defaultModel: 'claude-opus-4-6', contextWindow: 1_000_000, maxOutputTokens: 128_000, reasoning: true, vision: true }, + { id: 'opencode-claude-opus-4-5', label: 'Claude Opus 4.5', defaultModel: 'claude-opus-4-5', contextWindow: 200_000, maxOutputTokens: 64_000, reasoning: true, vision: true }, + { id: 'opencode-claude-opus-4-1', label: 'Claude Opus 4.1', defaultModel: 'claude-opus-4-1', contextWindow: 200_000, maxOutputTokens: 32_000, reasoning: true, vision: true }, + { id: 'opencode-claude-sonnet-4-6', label: 'Claude Sonnet 4.6', defaultModel: 'claude-sonnet-4-6', contextWindow: 1_000_000, maxOutputTokens: 64_000, reasoning: true, vision: true }, + { id: 'opencode-claude-sonnet-4-5', label: 'Claude Sonnet 4.5', defaultModel: 'claude-sonnet-4-5', contextWindow: 1_000_000, maxOutputTokens: 64_000, reasoning: true, vision: true }, + { id: 'opencode-claude-sonnet-4', label: 'Claude Sonnet 4', defaultModel: 'claude-sonnet-4', contextWindow: 1_000_000, maxOutputTokens: 64_000, reasoning: true, vision: true }, + { id: 'opencode-claude-haiku-4-5', label: 'Claude Haiku 4.5', defaultModel: 'claude-haiku-4-5', contextWindow: 200_000, maxOutputTokens: 64_000, vision: true }, + { id: 'opencode-gemini-3.5-flash', label: 'Gemini 3.5 Flash', defaultModel: 'gemini-3.5-flash', contextWindow: 1_048_576, maxOutputTokens: 65_536, vision: true }, + { id: 'opencode-gemini-3.1-pro', label: 'Gemini 3.1 Pro', defaultModel: 'gemini-3.1-pro', contextWindow: 1_048_576, maxOutputTokens: 65_536, reasoning: true, vision: true }, + { id: 'opencode-gemini-3-flash', label: 'Gemini 3 Flash', defaultModel: 'gemini-3-flash', contextWindow: 1_048_576, maxOutputTokens: 65_536, vision: true }, + { id: 'opencode-gpt-5.5', label: 'GPT 5.5', defaultModel: 'gpt-5.5', contextWindow: 1_050_000, maxOutputTokens: 128_000, reasoning: true, vision: true }, + { id: 'opencode-gpt-5.5-pro', label: 'GPT 5.5 Pro', defaultModel: 'gpt-5.5-pro', contextWindow: 1_050_000, maxOutputTokens: 128_000, reasoning: true, vision: true }, + { id: 'opencode-gpt-5.4', label: 'GPT 5.4', defaultModel: 'gpt-5.4', contextWindow: 1_050_000, maxOutputTokens: 128_000, reasoning: true, vision: true }, + { id: 'opencode-gpt-5.4-pro', label: 'GPT 5.4 Pro', defaultModel: 'gpt-5.4-pro', contextWindow: 1_050_000, maxOutputTokens: 128_000, reasoning: true, vision: true }, + { id: 'opencode-gpt-5.4-mini', label: 'GPT 5.4 Mini', defaultModel: 'gpt-5.4-mini', contextWindow: 400_000, maxOutputTokens: 128_000, vision: true }, + { id: 'opencode-gpt-5.4-nano', label: 'GPT 5.4 Nano', defaultModel: 'gpt-5.4-nano', contextWindow: 400_000, maxOutputTokens: 128_000 }, + { id: 'opencode-gpt-5.3-codex-spark', label: 'GPT 5.3 Codex Spark', defaultModel: 'gpt-5.3-codex-spark', contextWindow: 128_000, maxOutputTokens: 128_000, coding: true }, + { id: 'opencode-gpt-5.3-codex', label: 'GPT 5.3 Codex', defaultModel: 'gpt-5.3-codex', contextWindow: 400_000, maxOutputTokens: 128_000, vision: true, coding: true }, + { id: 'opencode-gpt-5.2', label: 'GPT 5.2', defaultModel: 'gpt-5.2', contextWindow: 400_000, maxOutputTokens: 128_000, vision: true }, + { id: 'opencode-gpt-5.2-codex', label: 'GPT 5.2 Codex', defaultModel: 'gpt-5.2-codex', contextWindow: 400_000, maxOutputTokens: 128_000, coding: true }, + { id: 'opencode-gpt-5.1', label: 'GPT 5.1', defaultModel: 'gpt-5.1', contextWindow: 400_000, maxOutputTokens: 128_000, vision: true }, + { id: 'opencode-gpt-5.1-codex-max', label: 'GPT 5.1 Codex Max', defaultModel: 'gpt-5.1-codex-max', contextWindow: 400_000, maxOutputTokens: 128_000, coding: true }, + { id: 'opencode-gpt-5.1-codex', label: 'GPT 5.1 Codex', defaultModel: 'gpt-5.1-codex', contextWindow: 400_000, maxOutputTokens: 128_000, coding: true }, + { id: 'opencode-gpt-5.1-codex-mini', label: 'GPT 5.1 Codex Mini', defaultModel: 'gpt-5.1-codex-mini', contextWindow: 400_000, maxOutputTokens: 128_000, coding: true }, + { id: 'opencode-gpt-5', label: 'GPT 5', defaultModel: 'gpt-5', contextWindow: 400_000, maxOutputTokens: 128_000, vision: true }, + { id: 'opencode-gpt-5-codex', label: 'GPT 5 Codex', defaultModel: 'gpt-5-codex', contextWindow: 400_000, maxOutputTokens: 128_000, coding: true }, + { id: 'opencode-gpt-5-nano', label: 'GPT 5 Nano', defaultModel: 'gpt-5-nano', contextWindow: 400_000, maxOutputTokens: 128_000 }, + { id: 'opencode-grok-build-0.1', label: 'Grok Build 0.1', defaultModel: 'grok-build-0.1', contextWindow: 256_000, maxOutputTokens: 256_000, coding: true }, + { id: 'opencode-deepseek-v4-pro', label: 'DeepSeek V4 Pro', defaultModel: 'deepseek-v4-pro', contextWindow: 1_000_000, maxOutputTokens: 384_000, reasoning: true, coding: true }, + { id: 'opencode-deepseek-v4-flash', label: 'DeepSeek V4 Flash', defaultModel: 'deepseek-v4-flash', contextWindow: 1_000_000, maxOutputTokens: 384_000, coding: true }, + { id: 'opencode-glm-5.1', label: 'GLM 5.1', defaultModel: 'glm-5.1', contextWindow: 204_800, maxOutputTokens: 131_072, coding: true }, + { id: 'opencode-glm-5', label: 'GLM 5', defaultModel: 'glm-5', contextWindow: 204_800, maxOutputTokens: 131_072, coding: true }, + { id: 'opencode-minimax-m2.7', label: 'MiniMax M2.7', defaultModel: 'minimax-m2.7', contextWindow: 204_800, maxOutputTokens: 131_072, reasoning: true, vision: true, coding: true }, + { id: 'opencode-minimax-m2.5', label: 'MiniMax M2.5', defaultModel: 'minimax-m2.5', contextWindow: 204_800, maxOutputTokens: 131_072, reasoning: true, vision: true, coding: true }, + { id: 'opencode-kimi-k2.6', label: 'Kimi K2.6', defaultModel: 'kimi-k2.6', contextWindow: 262_144, maxOutputTokens: 65_536, reasoning: true, coding: true }, + { id: 'opencode-kimi-k2.5', label: 'Kimi K2.5', defaultModel: 'kimi-k2.5', contextWindow: 262_144, maxOutputTokens: 65_536, reasoning: true, coding: true }, + { id: 'opencode-qwen3.6-plus', label: 'Qwen3.6 Plus', defaultModel: 'qwen3.6-plus', contextWindow: 262_144, maxOutputTokens: 65_536, reasoning: true, coding: true }, + { id: 'opencode-qwen3.5-plus', label: 'Qwen3.5 Plus', defaultModel: 'qwen3.5-plus', contextWindow: 262_144, maxOutputTokens: 65_536, reasoning: true, coding: true }, + { id: 'opencode-big-pickle', label: 'Big Pickle', defaultModel: 'big-pickle', contextWindow: 200_000, maxOutputTokens: 32_000, coding: true }, + { id: 'opencode-deepseek-v4-flash-free', label: 'DeepSeek V4 Flash Free', defaultModel: 'deepseek-v4-flash-free', contextWindow: 200_000, maxOutputTokens: 128_000, coding: true }, + { id: 'opencode-mimo-v2.5-free', label: 'MiMo V2.5 Free', defaultModel: 'mimo-v2.5-free', contextWindow: 200_000, maxOutputTokens: 32_000, coding: true }, + { id: 'opencode-qwen3.6-plus-free', label: 'Qwen3.6 Plus Free', defaultModel: 'qwen3.6-plus-free', contextWindow: 262_144, maxOutputTokens: 65_536, reasoning: true, coding: true }, + { id: 'opencode-minimax-m3-free', label: 'MiniMax M3 Free', defaultModel: 'minimax-m3-free', contextWindow: 200_000, maxOutputTokens: 32_000, reasoning: true, coding: true }, + { id: 'opencode-nemotron-3-ultra-free', label: 'Nemotron 3 Ultra Free', defaultModel: 'nemotron-3-ultra-free', contextWindow: 1_000_000, maxOutputTokens: 128_000, reasoning: true, coding: true }, + { id: 'opencode-north-mini-code-free', label: 'North Mini Code Free', defaultModel: 'north-mini-code-free', contextWindow: 256_000, maxOutputTokens: 64_000, coding: true }, ] + +const goModels: OpenCodeModelSpec[] = [ + { id: 'opencode-go-minimax-m3', label: 'MiniMax M3', defaultModel: 'minimax-m3', contextWindow: 512_000, maxOutputTokens: 131_072, reasoning: true, coding: true }, + { id: 'opencode-go-minimax-m2.7', label: 'MiniMax M2.7', defaultModel: 'minimax-m2.7', contextWindow: 204_800, maxOutputTokens: 131_072, reasoning: true, vision: true, coding: true }, + { id: 'opencode-go-minimax-m2.5', label: 'MiniMax M2.5', defaultModel: 'minimax-m2.5', contextWindow: 204_800, maxOutputTokens: 65_536, reasoning: true, vision: true, coding: true }, + { id: 'opencode-go-kimi-k2.7-code', label: 'Kimi K2.7 Code', defaultModel: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 262_144, reasoning: true, coding: true }, + { id: 'opencode-go-kimi-k2.6', label: 'Kimi K2.6', defaultModel: 'kimi-k2.6', contextWindow: 262_144, maxOutputTokens: 65_536, reasoning: true, coding: true }, + { id: 'opencode-go-kimi-k2.5', label: 'Kimi K2.5', defaultModel: 'kimi-k2.5', contextWindow: 262_144, maxOutputTokens: 65_536, reasoning: true, coding: true }, + { id: 'opencode-go-glm-5.2', label: 'GLM 5.2', defaultModel: 'glm-5.2', contextWindow: 1_000_000, maxOutputTokens: 131_072, coding: true }, + { id: 'opencode-go-glm-5.1', label: 'GLM 5.1', defaultModel: 'glm-5.1', contextWindow: 202_752, maxOutputTokens: 32_768, coding: true }, + { id: 'opencode-go-glm-5', label: 'GLM 5', defaultModel: 'glm-5', contextWindow: 202_752, maxOutputTokens: 32_768, coding: true }, + { id: 'opencode-go-deepseek-v4-pro', label: 'DeepSeek V4 Pro', defaultModel: 'deepseek-v4-pro', contextWindow: 1_000_000, maxOutputTokens: 384_000, reasoning: true, coding: true }, + { id: 'opencode-go-deepseek-v4-flash', label: 'DeepSeek V4 Flash', defaultModel: 'deepseek-v4-flash', contextWindow: 1_000_000, maxOutputTokens: 384_000, coding: true }, + { id: 'opencode-go-qwen3.7-max', label: 'Qwen3.7 Max', defaultModel: 'qwen3.7-max', contextWindow: 1_000_000, maxOutputTokens: 65_536, reasoning: true, coding: true }, + { id: 'opencode-go-qwen3.7-plus', label: 'Qwen3.7 Plus', defaultModel: 'qwen3.7-plus', contextWindow: 1_000_000, maxOutputTokens: 65_536, reasoning: true, coding: true }, + { id: 'opencode-go-qwen3.6-plus', label: 'Qwen3.6 Plus', defaultModel: 'qwen3.6-plus', contextWindow: 1_000_000, maxOutputTokens: 65_536, reasoning: true, coding: true }, + { id: 'opencode-go-qwen3.5-plus', label: 'Qwen3.5 Plus', defaultModel: 'qwen3.5-plus', contextWindow: 262_144, maxOutputTokens: 65_536, reasoning: true, coding: true }, + { id: 'opencode-go-mimo-v2-pro', label: 'MiMo V2 Pro', defaultModel: 'mimo-v2-pro', contextWindow: 1_048_576, maxOutputTokens: 128_000, reasoning: true, coding: true }, + { id: 'opencode-go-mimo-v2-omni', label: 'MiMo V2 Omni', defaultModel: 'mimo-v2-omni', contextWindow: 262_144, maxOutputTokens: 128_000, reasoning: true, vision: true, coding: true }, + { id: 'opencode-go-mimo-v2.5-pro', label: 'MiMo V2.5 Pro', defaultModel: 'mimo-v2.5-pro', contextWindow: 1_048_576, maxOutputTokens: 128_000, reasoning: true, coding: true }, + { id: 'opencode-go-mimo-v2.5', label: 'MiMo V2.5', defaultModel: 'mimo-v2.5', contextWindow: 1_000_000, maxOutputTokens: 128_000, reasoning: true, coding: true }, + { id: 'opencode-go-hy3-preview', label: 'HY3 Preview', defaultModel: 'hy3-preview', contextWindow: 256_000, maxOutputTokens: 64_000, coding: true }, +] + +export default [...zenModels, ...goModels].map(openCodeModel) diff --git a/src/integrations/runtimeMetadata.test.ts b/src/integrations/runtimeMetadata.test.ts index 670f27e7f..cdd595885 100644 --- a/src/integrations/runtimeMetadata.test.ts +++ b/src/integrations/runtimeMetadata.test.ts @@ -82,6 +82,52 @@ describe('resolveModelRuntimeLimits', () => { expect(limits.contextWindow).toBe(1_000_000) expect(limits.maxOutputTokens).toBe(131_072) }) + it('uses the applied provider profile route before generic custom base URL fallback', () => { + const limits = resolveModelRuntimeLimits({ + model: 'kimi-k2.6', + activeProfileProvider: 'opencode', + processEnv: { + CLAUDE_CODE_USE_OPENAI: '1', + CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED: '1', + OPENAI_BASE_URL: 'https://proxy.example.test/v1', + }, + }) + + expect(limits.contextWindow).toBe(262_144) + expect(limits.maxOutputTokens).toBe(65_536) + }) + + it('preserves composite provider paths before generic last-segment fallbacks', () => { + for (const model of [ + 'openrouter/accounts/fireworks/models/deepseek-v4-pro', + 'openrouter/fireworks/models/deepseek-v4-pro', + ]) { + expect( + resolveModelRuntimeLimits({ + model, + processEnv: { + CLAUDE_CODE_USE_OPENAI: '1', + OPENAI_BASE_URL: 'https://openrouter.ai/api/v1', + }, + }).maxOutputTokens, + ).toBe(32_768) + } + + for (const model of [ + 'openrouter/accounts/fireworks/models/llama-v3p1-70b-instruct', + 'openrouter/fireworks/models/llama-v3p1-70b-instruct', + ]) { + expect( + resolveModelRuntimeLimits({ + model, + processEnv: { + CLAUDE_CODE_USE_OPENAI: '1', + OPENAI_BASE_URL: 'https://openrouter.ai/api/v1', + }, + }).contextWindow, + ).toBe(131_072) + } + }) }) describe('resolveOpenAIShimRuntimeContext - Z.AI GLM-5.2', () => { @@ -201,4 +247,27 @@ describe('resolveOpenAIShimRuntimeContext - segment-boundary heuristic', () => { expect(result.openaiShimConfig.preserveReasoningContent).toBeUndefined() }) }) + it('matches provider-prefixed model ids to built-in runtime limits', () => { + expect( + resolveModelRuntimeLimits({ + model: 'google/gemini-3.1-pro', + activeProfileProvider: 'custom', + processEnv: { + CLAUDE_CODE_USE_OPENAI: '1', + OPENAI_BASE_URL: 'https://example-gateway.test/v1', + }, + }).contextWindow, + ).toBe(1_048_576) + + expect( + resolveModelRuntimeLimits({ + model: 'moonshotai/kimi-k2.6', + activeProfileProvider: 'nvidia-nim', + processEnv: { + CLAUDE_CODE_USE_OPENAI: '1', + OPENAI_BASE_URL: 'https://integrate.api.nvidia.com/v1', + }, + }).contextWindow, + ).toBe(262_144) + }) }) diff --git a/src/integrations/runtimeMetadata.ts b/src/integrations/runtimeMetadata.ts index 724a44081..123a64f50 100644 --- a/src/integrations/runtimeMetadata.ts +++ b/src/integrations/runtimeMetadata.ts @@ -277,6 +277,21 @@ function getModelDescriptorForCatalogEntry(entry: ModelCatalogEntry | null) { return getModel(entry.modelDescriptorId) ?? null } +function getProviderScopedModelSegments(modelApiName: string): string[] { + const segments = modelApiName + .split('/') + .map(segment => segment.trim()) + .filter(Boolean) + const suffixes = segments + .slice(1) + .map((_, index) => segments.slice(index + 1).join('/')) + const accountQualifiedSuffixes = suffixes + .filter(suffix => /^[^/]+\/models\//.test(suffix)) + .map(suffix => `accounts/${suffix}`) + + return [...suffixes, ...accountQualifiedSuffixes] +} + function findModelDescriptorForApiName( routeId: string | null, modelApiName: string | undefined, @@ -286,18 +301,25 @@ function findModelDescriptorForApiName( return null } const normalizedModel = trimmedModel.toLowerCase() + const providerScopedSegments = getProviderScopedModelSegments(trimmedModel) + const normalizedProviderScopedSegments = getProviderScopedModelSegments( + normalizedModel, + ) ensureIntegrationsLoaded() const models = getAllModels() .map(model => { + const providerModelMap = model.providerModelMap const routeMappedModel = routeId - ? model.providerModelMap?.[routeId] + ? providerModelMap?.[routeId] : undefined + const hasProviderModelMap = + providerModelMap && Object.keys(providerModelMap).length > 0 return { model, names: [ model.id, - model.defaultModel, + hasProviderModelMap ? routeMappedModel : model.defaultModel, routeMappedModel, ].filter((value): value is string => Boolean(value?.trim())), } @@ -320,13 +342,20 @@ function findModelDescriptorForApiName( } } + for (const candidate of models) { + if (candidate.names.some(name => providerScopedSegments.includes(name.trim()))) { + return candidate.model + } + } + for (const candidate of models) { if ( candidate.names.some(name => { const normalizedName = name.trim().toLowerCase() return ( normalizedModel === normalizedName || - normalizedModel.startsWith(normalizedName) + normalizedModel.startsWith(normalizedName) || + normalizedProviderScopedSegments.includes(normalizedName) ) }) ) { diff --git a/src/utils/context.test.ts b/src/utils/context.test.ts index 541471b36..30ba1419c 100644 --- a/src/utils/context.test.ts +++ b/src/utils/context.test.ts @@ -20,6 +20,10 @@ const originalEnv = { OPENAI_API_BASE: process.env.OPENAI_API_BASE, OPENAI_API_KEY: process.env.OPENAI_API_KEY, OPENAI_MODEL: process.env.OPENAI_MODEL, + CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED: + process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED, + CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID: + process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID, MINIMAX_API_KEY: process.env.MINIMAX_API_KEY, XAI_API_KEY: process.env.XAI_API_KEY, } @@ -34,6 +38,8 @@ beforeEach(async () => { delete process.env.OPENAI_API_BASE delete process.env.OPENAI_API_KEY delete process.env.OPENAI_MODEL + delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED + delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID delete process.env.MINIMAX_API_KEY delete process.env.XAI_API_KEY }) @@ -83,6 +89,18 @@ afterEach(() => { } else { process.env.OPENAI_API_KEY = originalEnv.OPENAI_API_KEY } + if (originalEnv.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED === undefined) { + delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED + } else { + process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED = + originalEnv.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED + } + if (originalEnv.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID === undefined) { + delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID + } else { + process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID = + originalEnv.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID + } if (originalEnv.MINIMAX_API_KEY === undefined) { delete process.env.MINIMAX_API_KEY } else { @@ -480,6 +498,36 @@ test('prefixed OpenGateway Gemini Flash Lite uses integration metadata', () => { }) expect(getMaxOutputTokensForModel('google/gemini-3.1-flash-lite')).toBe(65_536) }) +test('prefixed Gemini 3.1 Pro router model uses integration metadata', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + delete process.env.OPENAI_MODEL + delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED + delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID + + expect(getContextWindowForModel('google/gemini-3.1-pro')).toBe(1_048_576) + expect(getModelMaxOutputTokens('google/gemini-3.1-pro')).toEqual({ + default: 65_536, + upperLimit: 65_536, + }) + expect(getMaxOutputTokensForModel('google/gemini-3.1-pro')).toBe(65_536) +}) + +test('NVIDIA NIM DeepSeek V4 Pro uses NIM route catalog metadata', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + process.env.OPENAI_BASE_URL = 'https://integrate.api.nvidia.com/v1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + delete process.env.OPENAI_MODEL + delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED + delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID + + expect(getContextWindowForModel('deepseek-ai/deepseek-v4-pro')).toBe(1_048_576) + expect(getModelMaxOutputTokens('deepseek-ai/deepseek-v4-pro')).toEqual({ + default: 65_536, + upperLimit: 65_536, + }) + expect(getMaxOutputTokensForModel('deepseek-ai/deepseek-v4-pro')).toBe(65_536) +}) test('OpenAI-compatible custom model limits honor documented env overrides', () => { process.env.CLAUDE_CODE_USE_OPENAI = '1' diff --git a/src/utils/context.ts b/src/utils/context.ts index eded5e0d3..9f580db0f 100644 --- a/src/utils/context.ts +++ b/src/utils/context.ts @@ -11,6 +11,7 @@ import { import { getCanonicalName } from './model/model.js' import { getModelCapability } from './model/modelCapabilities.js' import { resolveAntModel } from './model/antModels.js' +import { getActiveProviderProfile } from './providerProfiles.js' // Model context window size (200k tokens for all models right now) export const MODEL_CONTEXT_WINDOW_DEFAULT = 200_000 @@ -42,6 +43,8 @@ export const CAPPED_DEFAULT_MAX_TOKENS = 8_000 export const ESCALATED_MAX_TOKENS = 64_000 const warnedUnknownIntegrationRuntimeLimitKeys = new Set() +const PROFILE_ENV_APPLIED_FLAG = 'CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED' +const PROFILE_ENV_APPLIED_ID = 'CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID' /** * Check if 1M context is disabled via environment variable. @@ -71,10 +74,32 @@ export function modelSupports1M(model: string): boolean { ) } +function getAppliedActiveProfileProvider( + processEnv: NodeJS.ProcessEnv = process.env, +): string | undefined { + if (processEnv[PROFILE_ENV_APPLIED_FLAG] !== '1') { + return undefined + } + + const activeProfile = getActiveProviderProfile() + if (!activeProfile) { + return undefined + } + + const appliedId = processEnv[PROFILE_ENV_APPLIED_ID]?.trim() + if (appliedId && appliedId !== activeProfile.id) { + return undefined + } + + return activeProfile.provider +} + export function shouldUseIntegrationRuntimeLimits( processEnv: NodeJS.ProcessEnv = process.env, ): boolean { - const routeId = resolveActiveRouteIdFromEnv(processEnv) + const routeId = resolveActiveRouteIdFromEnv(processEnv, { + activeProfileProvider: getAppliedActiveProfileProvider(processEnv), + }) const transportKind = routeId ? getTransportKindForRoute(routeId) : null return ( @@ -93,7 +118,10 @@ export function shouldUseIntegrationRuntimeLimits( * the Ink runtime treats console errors as application errors. */ function warnUnknownIntegrationRuntimeLimits(model: string): void { - const routeId = resolveActiveRouteIdFromEnv(process.env) ?? 'unknown-route' + const routeId = + resolveActiveRouteIdFromEnv(process.env, { + activeProfileProvider: getAppliedActiveProfileProvider(process.env), + }) ?? 'unknown-route' const warningKey = `${routeId}:${model}` if (warnedUnknownIntegrationRuntimeLimitKeys.has(warningKey)) return @@ -134,7 +162,10 @@ export function getContextWindowForModel( // but that caused auto-compact to fire on every turn because the effective // context (8k minus output reservation) became negative (issue #635). if (shouldUseIntegrationRuntimeLimits()) { - const runtimeLimits = resolveModelRuntimeLimits({ model }) + const runtimeLimits = resolveModelRuntimeLimits({ + model, + activeProfileProvider: getAppliedActiveProfileProvider(), + }) if (runtimeLimits.contextWindow !== undefined) { return runtimeLimits.contextWindow } @@ -235,7 +266,10 @@ export function getModelMaxOutputTokens(model: string): { // OpenAI-compatible provider — use known output limits to avoid 400 errors if (shouldUseIntegrationRuntimeLimits()) { - const runtimeLimits = resolveModelRuntimeLimits({ model }) + const runtimeLimits = resolveModelRuntimeLimits({ + model, + activeProfileProvider: getAppliedActiveProfileProvider(), + }) if (runtimeLimits.maxOutputTokens !== undefined) { return { default: runtimeLimits.maxOutputTokens, diff --git a/src/utils/hooks/hooksSettings.ts b/src/utils/hooks/hooksSettings.ts index 806b7c0f7..f4c76891f 100644 --- a/src/utils/hooks/hooksSettings.ts +++ b/src/utils/hooks/hooksSettings.ts @@ -176,7 +176,7 @@ export function hookSourceDescriptionDisplayString(source: HookSource): string { } const pluginHooksPath = getDisplayPath( join(getClaudeConfigHomeDir(), 'plugins', '*', 'hooks', 'hooks.json'), - ) + ).replace(/\\/g, '/') switch (source) { case 'userSettings': diff --git a/src/utils/statusRedaction.test.ts b/src/utils/statusRedaction.test.ts index 603ee556d..3064b4b05 100644 --- a/src/utils/statusRedaction.test.ts +++ b/src/utils/statusRedaction.test.ts @@ -96,6 +96,8 @@ describe('redactUrlForStatus', () => { describe('redactPathForStatus', () => { beforeEach(async () => { await acquireSharedMutationLock('utils/statusRedaction.test.ts') + process.env.HOME = REAL_HOMEDIR + restoreEnvValue('USERPROFILE', ORIGINAL_USERPROFILE) }) afterEach(() => {