mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
fix: preserve provider route context metadata (#1741)
* fix: preserve provider route context metadata Resolve issue #1732 by keeping active provider-profile routes attached during context limit resolution and making gateway-prefixed model IDs resolve against provider-scoped metadata instead of falling through to global aliases. Preserve composite provider-path suffixes such as accounts/fireworks/models/... and fireworks/models/... before generic last-segment matching, so wrapped gateway IDs resolve Fireworks-specific limits instead of generic descriptors. Refresh OpenCode Zen and OpenCode Go catalog metadata, including route-specific context/output limits, regenerate integration artifacts, add DeepSeek V4 Pro on NVIDIA NIM, add the Gemini 3.1 Pro router alias, and update user-facing OpenCode model counts. Scope OpenCode descriptor default model names to OpenCode routes via providerModelMap so unprefixed vendor lookups are not hijacked by gateway descriptors. Add wrapper-path assertions for the user-facing max output token helper. Add regression coverage for provider-prefixed gateway models, active-profile route preservation, account-qualified composite paths, and OpenRouter-wrapped fireworks/models/... paths. Harden Windows/full-suite validation by normalizing plugin hook display paths and resetting status-redaction HOME/USERPROFILE state. Validation: bun install; bun run build; bun run smoke; bun run typecheck; bun run typecheck:type-tests; bun run check (4634 pass, 0 fail); bun run test:provider (857 pass, 0 fail); bun run test:provider-recommendation (91 pass, 0 fail); bun run integrations:check; bun run security:pr-scan -- --base upstream/main; git diff --check. Follow-up validation: bun test src/integrations/runtimeMetadata.test.ts --max-concurrency=1; bun test src/utils/context.test.ts src/integrations/runtimeMetadata.test.ts src/integrations/gateways/opencode.test.ts --max-concurrency=1; bun run test:provider; bun run integrations:check; bun run typecheck; git diff --check. # Conflicts: # src/integrations/gateways/opencode-go.ts # src/integrations/models/opencode.ts * fix: align OpenCode context metadata Refresh OpenCode Zen and Go descriptor context/output limits against the live OpenCode model lists and Models.dev provider metadata. Add a regression assertion for provider-specific OpenCode limits so route-scoped metadata does not fall back to generic model budgets. * fix: remove duplicate Gemini model descriptor Keep the canonical Gemini 3.1 Pro descriptor and rely on provider-prefixed suffix matching for google/gemini-3.1-pro runtime lookups. Also separate the OpenCode limit regression test from the following assertion for readability. * fix: preserve OpenCode Go messages auth metadata * test: cover OpenCode Go review cases
This commit is contained in:
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 },
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
{ 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 },
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
],
|
||||
|
||||
+111
-1023
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
})
|
||||
) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
+38
-4
@@ -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<string>()
|
||||
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,
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user