@bitkyc08/opencodex 2.7.1 → 2.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/gui/dist/assets/index-D7o1qwy-.css +1 -0
- package/gui/dist/assets/index-DGFbiUtS.js +40 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +14 -3
- package/src/adapters/azure.ts +16 -11
- package/src/adapters/cursor/live-models.ts +36 -24
- package/src/adapters/cursor/live-transport.ts +5 -17
- package/src/adapters/cursor/native-exec.ts +11 -7
- package/src/adapters/cursor/transport-retry.ts +1 -4
- package/src/adapters/google.ts +32 -9
- package/src/adapters/kiro-retry.ts +6 -2
- package/src/adapters/kiro.ts +14 -9
- package/src/adapters/openai-chat.ts +38 -28
- package/src/codex/catalog.ts +67 -32
- package/src/config.ts +25 -2
- package/src/oauth/index.ts +4 -3
- package/src/oauth/key-providers.ts +1 -1
- package/src/oauth/kiro-credentials.ts +13 -3
- package/src/providers/derive.ts +4 -0
- package/src/providers/kiro-models.ts +1 -1
- package/src/providers/registry.ts +129 -40
- package/src/responses/parser.ts +12 -0
- package/src/router.ts +13 -1
- package/src/server/management-api.ts +10 -2
- package/src/server/request-log.ts +25 -1
- package/src/server/responses.ts +148 -10
- package/src/types.ts +12 -2
- package/src/update/index.ts +1 -1
- package/gui/dist/assets/index-BUAMcKFd.css +0 -1
- package/gui/dist/assets/index-CtHGtaW8.js +0 -34
|
@@ -18,6 +18,9 @@ export interface ProviderRegistryEntry {
|
|
|
18
18
|
adapter: string;
|
|
19
19
|
baseUrl: string;
|
|
20
20
|
authKind: ProviderAuthKind;
|
|
21
|
+
keyOptional?: boolean;
|
|
22
|
+
allowBaseUrlOverride?: boolean;
|
|
23
|
+
modelSuffixBracketStrip?: boolean;
|
|
21
24
|
featured?: boolean;
|
|
22
25
|
dashboardPreset?: boolean;
|
|
23
26
|
note?: string;
|
|
@@ -55,7 +58,7 @@ export interface ProviderRegistryEntry {
|
|
|
55
58
|
|
|
56
59
|
export type ProviderConfigSeed = Pick<
|
|
57
60
|
OcxProviderConfig,
|
|
58
|
-
"adapter" | "baseUrl" | "authMode" | "defaultModel" | "models"
|
|
61
|
+
"adapter" | "baseUrl" | "authMode" | "keyOptional" | "modelSuffixBracketStrip" | "defaultModel" | "models"
|
|
59
62
|
| "liveModels" | "contextWindow" | "modelContextWindows" | "modelInputModalities"
|
|
60
63
|
| "reasoningEfforts" | "modelReasoningEfforts" | "reasoningEffortMap" | "modelReasoningEffortMap"
|
|
61
64
|
| "noVisionModels" | "noReasoningModels" | "noTemperatureModels" | "noTopPModels" | "noPenaltyModels"
|
|
@@ -65,13 +68,25 @@ export type ProviderConfigSeed = Pick<
|
|
|
65
68
|
|
|
66
69
|
// Shared between the OAuth (Claude account) and API-key Anthropic entries so both expose the
|
|
67
70
|
// same static model seed.
|
|
68
|
-
//
|
|
69
|
-
// devlog/
|
|
71
|
+
// 260710 context refresh: Tier-2 evidence in
|
|
72
|
+
// devlog/_plan/260710_provider_hardening/001_research_frontier.md.
|
|
70
73
|
const ANTHROPIC_MODELS = ["claude-fable-5", "claude-sonnet-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"];
|
|
71
|
-
const ANTHROPIC_MODEL_CONTEXT_WINDOWS: Record<string, number> = { "claude-sonnet-5": 1_000_000, "claude-fable-5": 1_000_000 };
|
|
74
|
+
const ANTHROPIC_MODEL_CONTEXT_WINDOWS: Record<string, number> = { "claude-sonnet-5": 1_000_000, "claude-fable-5": 1_000_000, "claude-opus-4-8": 1_000_000, "claude-haiku-4-5": 200_000 };
|
|
72
75
|
|
|
73
76
|
const ZAI_GLM_52_MODELS = ["glm-5.2", "glm-5.2[1m]"];
|
|
74
77
|
const ZAI_GLM_52_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
78
|
+
// 260710 MiniMax models and context windows: Tier-2 evidence in
|
|
79
|
+
// devlog/_plan/260710_provider_hardening/002_research_cn.md.
|
|
80
|
+
const MINIMAX_MODELS = [
|
|
81
|
+
"MiniMax-M3",
|
|
82
|
+
"MiniMax-M2.7", "MiniMax-M2.7-highspeed",
|
|
83
|
+
"MiniMax-M2.5", "MiniMax-M2.5-highspeed",
|
|
84
|
+
"MiniMax-M2.1", "MiniMax-M2.1-highspeed",
|
|
85
|
+
"MiniMax-M2",
|
|
86
|
+
];
|
|
87
|
+
const MINIMAX_MODEL_CONTEXT_WINDOWS: Record<string, number> = Object.fromEntries(
|
|
88
|
+
MINIMAX_MODELS.map(id => [id, id === "MiniMax-M3" ? 1_000_000 : 204_800]),
|
|
89
|
+
);
|
|
75
90
|
const OPENAI_GPT56_MODELS = ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"];
|
|
76
91
|
const OPENAI_GPT56_CONTEXT_WINDOW = 372_000;
|
|
77
92
|
const OPENAI_GPT56_CONTEXT_WINDOWS = {
|
|
@@ -80,10 +95,13 @@ const OPENAI_GPT56_CONTEXT_WINDOWS = {
|
|
|
80
95
|
"gpt-5.6-luna": OPENAI_GPT56_CONTEXT_WINDOW,
|
|
81
96
|
};
|
|
82
97
|
const OPENROUTER_GPT56_MODELS = OPENAI_GPT56_MODELS.map(id => `openai/${id}`);
|
|
98
|
+
// OpenRouter's live /endpoints routes report 1,050,000; keep this separate from the
|
|
99
|
+
// unverified OpenAI API-key seed. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
100
|
+
const OPENROUTER_GPT56_CONTEXT_WINDOW = 1_050_000;
|
|
83
101
|
const OPENROUTER_GPT56_CONTEXT_WINDOWS = {
|
|
84
|
-
"openai/gpt-5.6-sol":
|
|
85
|
-
"openai/gpt-5.6-terra":
|
|
86
|
-
"openai/gpt-5.6-luna":
|
|
102
|
+
"openai/gpt-5.6-sol": OPENROUTER_GPT56_CONTEXT_WINDOW,
|
|
103
|
+
"openai/gpt-5.6-terra": OPENROUTER_GPT56_CONTEXT_WINDOW,
|
|
104
|
+
"openai/gpt-5.6-luna": OPENROUTER_GPT56_CONTEXT_WINDOW,
|
|
87
105
|
};
|
|
88
106
|
|
|
89
107
|
/**
|
|
@@ -122,11 +140,22 @@ const DEEPSEEK_THINKING_REASONING_MAP: Record<string, string> = {
|
|
|
122
140
|
xhigh: "max",
|
|
123
141
|
max: "max",
|
|
124
142
|
};
|
|
125
|
-
|
|
126
|
-
|
|
143
|
+
// 260710 Kimi model aliases and context windows: Tier-2 evidence in
|
|
144
|
+
// devlog/_plan/260710_provider_hardening/002_research_cn.md.
|
|
145
|
+
const KIMI_API_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"];
|
|
146
|
+
const KIMI_CODING_MODELS = [...KIMI_API_MODELS, "kimi-for-coding"];
|
|
147
|
+
const KIMI_THINKING_MODELS = KIMI_CODING_MODELS;
|
|
148
|
+
const KIMI_LOCKED_PARAMETER_MODELS = KIMI_CODING_MODELS;
|
|
149
|
+
const KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-for-coding"];
|
|
150
|
+
const KIMI_API_MODEL_CONTEXT_WINDOWS: Record<string, number> = Object.fromEntries(
|
|
151
|
+
KIMI_API_MODELS.map(id => [id, 262_144]),
|
|
152
|
+
);
|
|
153
|
+
const KIMI_CODING_MODEL_CONTEXT_WINDOWS: Record<string, number> = Object.fromEntries(
|
|
154
|
+
KIMI_CODING_MODELS.map(id => [id, 262_144]),
|
|
155
|
+
);
|
|
127
156
|
const NEURALWATT_REASONING_HISTORY_MODELS = [
|
|
128
|
-
"glm-5.2",
|
|
129
|
-
"
|
|
157
|
+
"glm-5.2", "glm-5.2-short",
|
|
158
|
+
"kimi-k2.6", "kimi-k2.7-code",
|
|
130
159
|
"qwen3.5-397b", "qwen3.6-35b",
|
|
131
160
|
];
|
|
132
161
|
const UMANS_MODELS = [
|
|
@@ -258,15 +287,16 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
258
287
|
oauthId: "kimi",
|
|
259
288
|
jawcodeBundle: "moonshot",
|
|
260
289
|
note: "Log in with your Kimi account",
|
|
261
|
-
models:
|
|
290
|
+
models: KIMI_CODING_MODELS,
|
|
262
291
|
defaultModel: "kimi-k2.7-code",
|
|
292
|
+
modelContextWindows: KIMI_CODING_MODEL_CONTEXT_WINDOWS,
|
|
263
293
|
// Kimi thinking is controlled by Kimi's `thinking` extension, not OpenAI `reasoning_effort`.
|
|
264
294
|
noReasoningModels: KIMI_THINKING_MODELS,
|
|
265
295
|
modelReasoningEfforts: Object.fromEntries(KIMI_THINKING_MODELS.map(id => [id, []])),
|
|
266
296
|
noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
267
297
|
noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
268
298
|
noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
269
|
-
autoToolChoiceOnlyModels:
|
|
299
|
+
autoToolChoiceOnlyModels: KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS,
|
|
270
300
|
preserveReasoningContentModels: KIMI_THINKING_MODELS,
|
|
271
301
|
},
|
|
272
302
|
{
|
|
@@ -351,9 +381,11 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
351
381
|
authKind: "key",
|
|
352
382
|
dashboardUrl: "https://portal.neuralwatt.com",
|
|
353
383
|
defaultModel: "glm-5.2",
|
|
384
|
+
// 2026-07-10 live /v1/models: K2.5 rows were removed and GLM-5.2 short variants added.
|
|
385
|
+
// Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md and https://api.neuralwatt.com/v1/models.
|
|
354
386
|
models: [
|
|
355
|
-
"glm-5.2", "glm-5.2-fast",
|
|
356
|
-
"
|
|
387
|
+
"glm-5.2", "glm-5.2-fast", "glm-5.2-short", "glm-5.2-short-fast",
|
|
388
|
+
"kimi-k2.6", "kimi-k2.6-fast",
|
|
357
389
|
"kimi-k2.7-code",
|
|
358
390
|
"qwen3.5-397b", "qwen3.5-397b-fast", "qwen3.6-35b", "qwen3.6-35b-fast",
|
|
359
391
|
],
|
|
@@ -361,8 +393,8 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
361
393
|
modelReasoningEfforts: {
|
|
362
394
|
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
363
395
|
"glm-5.2-fast": [],
|
|
364
|
-
"
|
|
365
|
-
"
|
|
396
|
+
"glm-5.2-short": ZAI_GLM_52_REASONING_EFFORTS,
|
|
397
|
+
"glm-5.2-short-fast": [],
|
|
366
398
|
"kimi-k2.6": [],
|
|
367
399
|
"kimi-k2.6-fast": [],
|
|
368
400
|
"kimi-k2.7-code": [],
|
|
@@ -374,8 +406,8 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
374
406
|
"qwen3.6-35b-fast": [],
|
|
375
407
|
},
|
|
376
408
|
thinkingBudgetModels: THINKING_BUDGET_MODELS,
|
|
377
|
-
noReasoningModels: ["glm-5.2-fast", "
|
|
378
|
-
noVisionModels: ["glm-5.2", "glm-5.2-fast", "qwen3.5-397b", "qwen3.5-397b-fast"],
|
|
409
|
+
noReasoningModels: ["glm-5.2-fast", "glm-5.2-short-fast", "kimi-k2.6-fast", "qwen3.5-397b-fast", "qwen3.6-35b-fast"],
|
|
410
|
+
noVisionModels: ["glm-5.2", "glm-5.2-fast", "glm-5.2-short", "glm-5.2-short-fast", "qwen3.5-397b", "qwen3.5-397b-fast"],
|
|
379
411
|
noTemperatureModels: ["kimi-k2.7-code"],
|
|
380
412
|
noTopPModels: ["kimi-k2.7-code"],
|
|
381
413
|
noPenaltyModels: ["kimi-k2.7-code"],
|
|
@@ -384,13 +416,26 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
384
416
|
},
|
|
385
417
|
{ id: "openrouter", label: "OpenRouter", adapter: "openai-chat", baseUrl: "https://openrouter.ai/api/v1", authKind: "key", featured: true, dashboardUrl: "https://openrouter.ai/keys", jawcodeBundle: "openrouter", models: ["anthropic/claude-sonnet-5", ...OPENROUTER_GPT56_MODELS], modelContextWindows: { "anthropic/claude-sonnet-5": 1_000_000, ...OPENROUTER_GPT56_CONTEXT_WINDOWS } },
|
|
386
418
|
{ id: "groq", label: "Groq", adapter: "openai-chat", baseUrl: "https://api.groq.com/openai/v1", authKind: "key", featured: true, dashboardUrl: "https://console.groq.com/keys" },
|
|
387
|
-
|
|
419
|
+
// 2026-07-10 Gemini API refresh: Tier-2 ai.google.dev evidence recorded in
|
|
420
|
+
// devlog/_plan/260710_provider_hardening/001_research_frontier.md.
|
|
421
|
+
{
|
|
422
|
+
id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true,
|
|
423
|
+
dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-3.5-flash", "gemini-3.1-pro-preview"],
|
|
424
|
+
modelContextWindows: { "gemini-3.5-flash": 1_000_000 },
|
|
425
|
+
modelReasoningEfforts: {
|
|
426
|
+
"gemini-3.5-flash": ["minimal", "low", "medium", "high"],
|
|
427
|
+
"gemini-3.1-pro-preview": ["low", "medium", "high"],
|
|
428
|
+
},
|
|
429
|
+
jawcodeBundle: "google", extraMetadataAliases: ["gemini"],
|
|
430
|
+
},
|
|
431
|
+
// 2026-07-10: defaultModel is frozen pending Vertex-specific Tier-2 evidence; Gemini API
|
|
432
|
+
// evidence from ai.google.dev does not establish Vertex publisher availability.
|
|
388
433
|
{ id: "google-vertex", label: "Google Vertex AI", adapter: "google", baseUrl: "https://aiplatform.googleapis.com", authKind: "key", dashboardUrl: "https://console.cloud.google.com/vertex-ai", defaultModel: "gemini-3-pro", googleMode: "vertex", jawcodeBundle: "google", extraMetadataAliases: ["gemini-vertex"] },
|
|
389
434
|
{ id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, defaultModel: "gemini-3.5-flash-low", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
|
|
390
435
|
{ id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
|
|
391
|
-
{ id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", featured: true, note: "Local — key usually blank" },
|
|
392
|
-
{ id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", featured: true, note: "Local — key usually blank" },
|
|
393
|
-
{ id: "lm-studio", label: "LM Studio (local)", adapter: "openai-chat", baseUrl: "http://localhost:1234/v1", authKind: "local", featured: true, note: "Local — no key needed" },
|
|
436
|
+
{ id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
|
|
437
|
+
{ id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
|
|
438
|
+
{ id: "lm-studio", label: "LM Studio (local)", adapter: "openai-chat", baseUrl: "http://localhost:1234/v1", authKind: "local", allowBaseUrlOverride: true, featured: true, note: "Local — no key needed" },
|
|
394
439
|
{
|
|
395
440
|
id: "deepseek",
|
|
396
441
|
label: "DeepSeek",
|
|
@@ -398,8 +443,11 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
398
443
|
adapter: "openai-chat",
|
|
399
444
|
authKind: "key",
|
|
400
445
|
dashboardUrl: "https://platform.deepseek.com/api_keys",
|
|
446
|
+
// deepseek-chat/deepseek-reasoner are upstream-deprecated at 2026-07-24 15:59 UTC;
|
|
447
|
+
// kept until then. Evidence: devlog/_plan/260710_provider_hardening/002_research_cn.md.
|
|
401
448
|
models: ["deepseek-chat", "deepseek-reasoner", ...DEEPSEEK_THINKING_MODELS],
|
|
402
|
-
defaultModel: "deepseek-
|
|
449
|
+
defaultModel: "deepseek-v4-flash",
|
|
450
|
+
modelContextWindows: { "deepseek-v4-flash": 1_000_000, "deepseek-v4-pro": 1_000_000 },
|
|
403
451
|
/* [Decision Log]
|
|
404
452
|
- 목적: DeepSeek V4 thinking mode multi-turn/tool-call requests must replay prior assistant reasoning_content.
|
|
405
453
|
- 대안 분석: Globally preserve reasoning_content for all OpenAI-compatible models; preserve it for legacy deepseek-reasoner too; mark only V4 thinking models in registry metadata.
|
|
@@ -409,42 +457,67 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
409
457
|
modelReasoningEffortMap: Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, DEEPSEEK_THINKING_REASONING_MAP])),
|
|
410
458
|
preserveReasoningContentModels: DEEPSEEK_THINKING_MODELS,
|
|
411
459
|
},
|
|
412
|
-
|
|
460
|
+
// llama-3.3-70b was deprecated by Cerebras on 2026-02-16. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
461
|
+
{ id: "cerebras", label: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://cloud.cerebras.ai/platform/apikeys", defaultModel: "gpt-oss-120b" },
|
|
462
|
+
// FREEZE 2026-07-10: exact serverless ids remain auth-gated/unverified. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
413
463
|
{ id: "together", label: "Together", baseUrl: "https://api.together.xyz/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://api.together.xyz/settings/api-keys" },
|
|
414
464
|
{ id: "fireworks", label: "Fireworks", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://fireworks.ai/account/api-keys" },
|
|
415
|
-
{
|
|
465
|
+
{
|
|
466
|
+
id: "firepass", label: "Fire Pass (Fireworks Kimi)", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key",
|
|
467
|
+
dashboardUrl: "https://fireworks.ai/account/api-keys",
|
|
468
|
+
note: "Model data frozen pending Tier-2 entitlement proof",
|
|
469
|
+
},
|
|
416
470
|
{
|
|
417
471
|
id: "moonshot", label: "Moonshot (Kimi API)", baseUrl: "https://api.moonshot.ai/v1", adapter: "openai-chat", authKind: "key",
|
|
418
472
|
dashboardUrl: "https://platform.moonshot.ai/console/api-keys", defaultModel: "kimi-k2.7-code", jawcodeBundle: "moonshot",
|
|
419
|
-
models:
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
473
|
+
models: KIMI_API_MODELS,
|
|
474
|
+
modelContextWindows: KIMI_API_MODEL_CONTEXT_WINDOWS,
|
|
475
|
+
noReasoningModels: KIMI_API_MODELS,
|
|
476
|
+
modelReasoningEfforts: Object.fromEntries(KIMI_API_MODELS.map(id => [id, []])),
|
|
477
|
+
noTemperatureModels: KIMI_API_MODELS,
|
|
478
|
+
noTopPModels: KIMI_API_MODELS,
|
|
479
|
+
noPenaltyModels: KIMI_API_MODELS,
|
|
425
480
|
autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
426
|
-
preserveReasoningContentModels:
|
|
481
|
+
preserveReasoningContentModels: KIMI_API_MODELS,
|
|
427
482
|
},
|
|
428
483
|
{ id: "huggingface", label: "Hugging Face", baseUrl: "https://router.huggingface.co/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://huggingface.co/settings/tokens" },
|
|
429
484
|
{ id: "nvidia", label: "NVIDIA NIM", baseUrl: "https://integrate.api.nvidia.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://build.nvidia.com" },
|
|
430
485
|
{ id: "venice", label: "Venice", baseUrl: "https://api.venice.ai/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://venice.ai/settings/api" },
|
|
486
|
+
// 260710 GLM-5.2 context and path-specific ids: Tier-2 evidence in
|
|
487
|
+
// devlog/_plan/260710_provider_hardening/002_research_cn.md.
|
|
431
488
|
{
|
|
432
489
|
id: "zai", label: "Z.AI — GLM Coding Plan", baseUrl: "https://api.z.ai/api/coding/paas/v4", adapter: "openai-chat", authKind: "key",
|
|
433
490
|
dashboardUrl: "https://z.ai/manage-apikey/apikey-list", defaultModel: "glm-5.2",
|
|
434
491
|
note: "GLM-5.2 coding subscription",
|
|
435
492
|
models: ["glm-5.2", "glm-5.2[1m]", "glm-5.1", "glm-5", "glm-4.6"],
|
|
493
|
+
modelContextWindows: { "glm-5.2": 1_000_000, "glm-5.2[1m]": 1_000_000 },
|
|
494
|
+
// Z.AI's OpenAI path returns 400 code 1211 for bracketed model ids.
|
|
495
|
+
modelSuffixBracketStrip: true,
|
|
436
496
|
noVisionModels: ZAI_GLM_52_MODELS,
|
|
437
497
|
modelReasoningEfforts: Object.fromEntries(ZAI_GLM_52_MODELS.map(id => [id, ZAI_GLM_52_REASONING_EFFORTS])),
|
|
438
498
|
preserveReasoningContentModels: ZAI_GLM_52_MODELS,
|
|
439
499
|
},
|
|
440
500
|
{ id: "nanogpt", label: "NanoGPT", baseUrl: "https://nano-gpt.com/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://nano-gpt.com/api" },
|
|
441
501
|
{ id: "synthetic", label: "Synthetic", baseUrl: "https://api.synthetic.new/openai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://synthetic.new" },
|
|
502
|
+
// 2026-07-10: docs unverified; model data frozen. Evidence: devlog/_plan/260710_provider_hardening/002_research_cn.md.
|
|
442
503
|
{ id: "qwen-portal", label: "Qwen Portal", baseUrl: "https://portal.qwen.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://portal.qwen.ai" },
|
|
504
|
+
// 2026-07-10: docs unverified; model data frozen. Evidence: devlog/_plan/260710_provider_hardening/002_research_cn.md.
|
|
443
505
|
{ id: "qianfan", label: "Qianfan (Baidu)", baseUrl: "https://qianfan.baidubce.com/v2", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://console.bce.baidu.com/iam/#/iam/apikey/list" },
|
|
506
|
+
// 2026-07-10: docs unverified; model data frozen. Evidence: devlog/_plan/260710_provider_hardening/002_research_cn.md.
|
|
444
507
|
{ id: "alibaba", label: "Alibaba Coding Plan", baseUrl: "https://coding-intl.dashscope.aliyuncs.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://dashscope.console.aliyun.com/apiKey" },
|
|
508
|
+
// NEEDS_HUMAN 2026-07-10: kept for config compatibility, but this is a dashboard URL,
|
|
509
|
+
// no /models endpoint is documented, and tools are silently ignored upstream per docs.parallel.ai.
|
|
510
|
+
// Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
445
511
|
{ id: "parallel", label: "Parallel", baseUrl: "https://platform.parallel.ai", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.parallel.ai" },
|
|
512
|
+
// FREEZE 2026-07-10: model ids remain unverified. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
446
513
|
{ id: "zenmux", label: "ZenMux", baseUrl: "https://zenmux.ai/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://zenmux.ai" },
|
|
447
|
-
{
|
|
514
|
+
{
|
|
515
|
+
id: "litellm", label: "LiteLLM (self-hosted)", baseUrl: "http://localhost:4000/v1", adapter: "openai-chat", authKind: "key",
|
|
516
|
+
dashboardUrl: "https://docs.litellm.ai/docs/proxy/quick_start",
|
|
517
|
+
allowBaseUrlOverride: true,
|
|
518
|
+
// A self-hosted proxy may legitimately run without a master key.
|
|
519
|
+
keyOptional: true,
|
|
520
|
+
},
|
|
448
521
|
{
|
|
449
522
|
id: "ollama-cloud",
|
|
450
523
|
label: "Ollama Cloud",
|
|
@@ -452,29 +525,43 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
452
525
|
adapter: "openai-chat",
|
|
453
526
|
authKind: "key",
|
|
454
527
|
dashboardUrl: "https://ollama.com/settings/keys",
|
|
455
|
-
|
|
528
|
+
// Live IDs verified 2026-07-10; qwen3-coder:480b retires 2026-07-15.
|
|
529
|
+
// Evidence: .codexclaw/evidence/260710_wp9_ollama_cloud_model_ids.md.
|
|
530
|
+
models: ["glm-5.2", "deepseek-v4-pro", "qwen3-coder:480b", "gpt-oss:120b", "kimi-k2.6", "minimax-m3", "qwen3.5:397b", "gemma4:31b"],
|
|
456
531
|
defaultModel: "glm-5.2",
|
|
457
532
|
noVisionModels: [
|
|
458
533
|
"glm-5.2", "glm-5.1", "glm-5", "glm-4.7",
|
|
459
534
|
"minimax-m2.7", "minimax-m2.5", "minimax-m2.1",
|
|
460
535
|
"nemotron-3-ultra", "nemotron-3-super",
|
|
461
536
|
"deepseek-v4-pro", "deepseek-v4-flash",
|
|
462
|
-
"gpt-oss", "qwen3-coder",
|
|
537
|
+
"gpt-oss", "qwen3-coder:480b",
|
|
463
538
|
],
|
|
464
539
|
},
|
|
540
|
+
// FREEZE 2026-07-10: codestral-latest is unconfirmed behind auth. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
465
541
|
{ id: "mistral", label: "Mistral", baseUrl: "https://api.mistral.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://console.mistral.ai/api-keys", defaultModel: "codestral-latest" },
|
|
466
|
-
{
|
|
467
|
-
|
|
542
|
+
{
|
|
543
|
+
id: "minimax", label: "MiniMax — Coding Plan", baseUrl: "https://api.minimax.io/v1", adapter: "openai-chat", authKind: "key",
|
|
544
|
+
dashboardUrl: "https://platform.minimax.io", defaultModel: "MiniMax-M3", models: MINIMAX_MODELS,
|
|
545
|
+
modelContextWindows: MINIMAX_MODEL_CONTEXT_WINDOWS,
|
|
546
|
+
jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive", note: "Subscription Key or API Key",
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
id: "minimax-cn", label: "MiniMax — Coding Plan (CN)", baseUrl: "https://api.minimaxi.com/v1", adapter: "openai-chat", authKind: "key",
|
|
550
|
+
dashboardUrl: "https://platform.minimaxi.com", defaultModel: "MiniMax-M3", models: MINIMAX_MODELS,
|
|
551
|
+
modelContextWindows: MINIMAX_MODEL_CONTEXT_WINDOWS,
|
|
552
|
+
jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive", note: "中国区 Subscription Key",
|
|
553
|
+
},
|
|
468
554
|
{
|
|
469
555
|
id: "kimi-code", label: "Kimi (coding)", baseUrl: "https://api.kimi.com/coding/v1", adapter: "openai-chat", authKind: "key",
|
|
470
556
|
dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-k2.7-code",
|
|
471
|
-
models:
|
|
557
|
+
models: KIMI_CODING_MODELS,
|
|
558
|
+
modelContextWindows: KIMI_CODING_MODEL_CONTEXT_WINDOWS,
|
|
472
559
|
noReasoningModels: KIMI_THINKING_MODELS,
|
|
473
560
|
modelReasoningEfforts: Object.fromEntries(KIMI_THINKING_MODELS.map(id => [id, []])),
|
|
474
561
|
noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
475
562
|
noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
476
563
|
noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
477
|
-
autoToolChoiceOnlyModels:
|
|
564
|
+
autoToolChoiceOnlyModels: KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS,
|
|
478
565
|
preserveReasoningContentModels: KIMI_THINKING_MODELS,
|
|
479
566
|
},
|
|
480
567
|
{ id: "opencode-zen", label: "opencode zen", baseUrl: "https://opencode.ai/zen/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://opencode.ai/auth" },
|
|
@@ -482,7 +569,9 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
482
569
|
{ id: "xiaomi", label: "Xiaomi MiMo", baseUrl: "https://api.xiaomimimo.com/anthropic", adapter: "anthropic", authKind: "key", dashboardUrl: "https://xiaomimimo.com", defaultModel: "mimo-v2.5-pro" },
|
|
483
570
|
{ id: "kilo", label: "Kilo", baseUrl: "https://api.kilo.ai/api/gateway", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://kilo.ai" },
|
|
484
571
|
{ id: "cloudflare-ai-gateway", label: "Cloudflare AI Gateway", baseUrl: "https://gateway.ai.cloudflare.com/v1/{account-id}/{gateway}/anthropic", adapter: "anthropic", authKind: "key", dashboardUrl: "https://dash.cloudflare.com/?to=/:account/ai/ai-gateway" },
|
|
572
|
+
// FREEZE 2026-07-10: /models is auth-gated, so ids remain unverified. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
485
573
|
{ id: "github-copilot", label: "GitHub Copilot", baseUrl: "https://api.githubcopilot.com", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://github.com/settings/copilot" },
|
|
574
|
+
// FREEZE 2026-07-10: no public OpenAI-compatible endpoint is documented. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
486
575
|
{ id: "gitlab-duo", label: "GitLab Duo", baseUrl: "https://cloud.gitlab.com/ai/v1/proxy/openai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://gitlab.com/-/user_settings/personal_access_tokens" },
|
|
487
576
|
];
|
|
488
577
|
|
package/src/responses/parser.ts
CHANGED
|
@@ -250,6 +250,18 @@ export function parseRequest(body: unknown): OcxParsedRequest {
|
|
|
250
250
|
continue;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
if (effectiveType === "additional_tools") {
|
|
254
|
+
// Codex Desktop responses_lite WS path: tools ride INSIDE input as an
|
|
255
|
+
// `additional_tools` item ({type, role, tools:[...]}) instead of body.tools.
|
|
256
|
+
// Same spec wire shapes (function/namespace/custom/tool_search) — collect and
|
|
257
|
+
// merge through the exact buildTools path so surface detection (collabSurface)
|
|
258
|
+
// and chat-model tool listing see them. The item itself never becomes a message;
|
|
259
|
+
// the native passthrough keeps it verbatim in _rawBody.
|
|
260
|
+
const at = item as { tools?: unknown[] };
|
|
261
|
+
if (Array.isArray(at.tools)) loadedToolSpecs.push(...at.tools);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
253
265
|
if (effectiveType === "compaction" || effectiveType === "compaction_summary" || effectiveType === "context_compaction") {
|
|
254
266
|
// A stored summary from a previous compaction. Decode our ocx1 envelope into plain text so
|
|
255
267
|
// the routed model keeps the compacted context; real OpenAI-encrypted blobs degrade to a note.
|
package/src/router.ts
CHANGED
|
@@ -97,11 +97,21 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
|
|
|
97
97
|
const preserveReasoningContentModels = mergeStringArray(registryEntry.preserveReasoningContentModels, provider.preserveReasoningContentModels);
|
|
98
98
|
const thinkingToggleModels = mergeStringArray(registryEntry.thinkingToggleModels, provider.thinkingToggleModels);
|
|
99
99
|
const thinkingBudgetModels = mergeStringArray(registryEntry.thinkingBudgetModels, provider.thinkingBudgetModels);
|
|
100
|
+
const registryBaseUrlIsTemplate = /\{[^}]*\}/.test(registryEntry.baseUrl);
|
|
101
|
+
const userBaseUrl = typeof provider.baseUrl === "string" ? provider.baseUrl.trim() : "";
|
|
102
|
+
const userBaseUrlIsResolved = userBaseUrl.length > 0 && !/\{[^}]*\}/.test(userBaseUrl);
|
|
103
|
+
if (registryEntry.allowBaseUrlOverride && !userBaseUrlIsResolved) {
|
|
104
|
+
throw new Error(`Invalid baseUrl for provider "${providerName}": expected a nonblank URL without unresolved placeholders`);
|
|
105
|
+
}
|
|
106
|
+
// Registry template URLs are presets; local/self-hosted entries opt in explicitly.
|
|
107
|
+
const baseUrl = (registryBaseUrlIsTemplate || registryEntry.allowBaseUrlOverride) && userBaseUrlIsResolved
|
|
108
|
+
? userBaseUrl
|
|
109
|
+
: registryEntry.baseUrl;
|
|
100
110
|
|
|
101
111
|
return {
|
|
102
112
|
...provider,
|
|
103
113
|
adapter: registryEntry.adapter,
|
|
104
|
-
baseUrl
|
|
114
|
+
baseUrl,
|
|
105
115
|
authMode: canonicalAuthMode,
|
|
106
116
|
apiKey: resolveEnvValue(provider.apiKey),
|
|
107
117
|
// Backfill the Google wire mode + Vertex project/location from the registry when the user
|
|
@@ -113,6 +123,8 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
|
|
|
113
123
|
...(provider.contextWindow === undefined && registryEntry.contextWindow !== undefined ? { contextWindow: registryEntry.contextWindow } : {}),
|
|
114
124
|
...(provider.reasoningEfforts === undefined && registryEntry.reasoningEfforts !== undefined ? { reasoningEfforts: registryEntry.reasoningEfforts } : {}),
|
|
115
125
|
...(provider.escapeBuiltinToolNames === undefined && registryEntry.escapeBuiltinToolNames !== undefined ? { escapeBuiltinToolNames: registryEntry.escapeBuiltinToolNames } : {}),
|
|
126
|
+
...(provider.keyOptional === undefined && registryEntry.keyOptional !== undefined ? { keyOptional: registryEntry.keyOptional } : {}),
|
|
127
|
+
...(provider.modelSuffixBracketStrip === undefined && registryEntry.modelSuffixBracketStrip !== undefined ? { modelSuffixBracketStrip: registryEntry.modelSuffixBracketStrip } : {}),
|
|
116
128
|
// Scalar backfill: a persisted config created before the flag shipped inherits the registry
|
|
117
129
|
// opt-in, while an explicit user `false` keeps overriding registry `true`.
|
|
118
130
|
...(provider.parallelToolCalls === undefined && registryEntry.parallelToolCalls !== undefined ? { parallelToolCalls: registryEntry.parallelToolCalls } : {}),
|
|
@@ -535,12 +535,13 @@ export async function handleManagementAPI(req: Request, url: URL, config: OcxCon
|
|
|
535
535
|
return jsonResponse({
|
|
536
536
|
model: config.injectionModel ?? null,
|
|
537
537
|
effort: config.injectionEffort ?? null,
|
|
538
|
+
prompt: config.injectionPrompt ?? null,
|
|
538
539
|
efforts: CODEX_REASONING_LEVELS.map(l => l.effort),
|
|
539
540
|
available: [...nativeModels, ...routedModels],
|
|
540
541
|
});
|
|
541
542
|
}
|
|
542
543
|
if (url.pathname === "/api/injection-model" && req.method === "PUT") {
|
|
543
|
-
let body: { model?: unknown; effort?: unknown };
|
|
544
|
+
let body: { model?: unknown; effort?: unknown; prompt?: unknown };
|
|
544
545
|
try { body = await req.json(); } catch { return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
545
546
|
const { isCodexReasoningEffort } = await import("../reasoning-effort");
|
|
546
547
|
const model = typeof body.model === "string" && body.model.length > 0 ? body.model : undefined;
|
|
@@ -559,8 +560,15 @@ export async function handleManagementAPI(req: Request, url: URL, config: OcxCon
|
|
|
559
560
|
else delete config.injectionModel;
|
|
560
561
|
if (effort) config.injectionEffort = effort;
|
|
561
562
|
else delete config.injectionEffort;
|
|
563
|
+
// `prompt` key semantics mirror `effort`: absent -> unchanged; null/"" -> clear;
|
|
564
|
+
// non-empty string -> set (custom <multi_agent_mode> body, {{model}}/{{effort}}/{{roster}} placeholders).
|
|
565
|
+
if ("prompt" in body) {
|
|
566
|
+
if (typeof body.prompt === "string" && body.prompt.trim().length > 0) config.injectionPrompt = body.prompt;
|
|
567
|
+
else if (body.prompt === null || body.prompt === "") delete config.injectionPrompt;
|
|
568
|
+
else return jsonResponse({ error: "prompt must be a string or null" }, 400);
|
|
569
|
+
}
|
|
562
570
|
saveConfig(config);
|
|
563
|
-
return jsonResponse({ ok: true, model: config.injectionModel ?? null, effort: config.injectionEffort ?? null });
|
|
571
|
+
return jsonResponse({ ok: true, model: config.injectionModel ?? null, effort: config.injectionEffort ?? null, prompt: config.injectionPrompt ?? null });
|
|
564
572
|
}
|
|
565
573
|
|
|
566
574
|
// Subagent model picker: which ≤5 routed models Codex's spawn_agent advertises (it shows the
|
|
@@ -261,7 +261,10 @@ function captureUpstreamError(logCtx: RequestLogContext, text: string | null): v
|
|
|
261
261
|
type?: unknown;
|
|
262
262
|
error?: { message?: unknown };
|
|
263
263
|
last_error?: { message?: unknown };
|
|
264
|
-
response?: {
|
|
264
|
+
response?: {
|
|
265
|
+
error?: { type?: unknown; code?: unknown; message?: unknown };
|
|
266
|
+
incomplete_details?: { reason?: unknown };
|
|
267
|
+
};
|
|
265
268
|
};
|
|
266
269
|
captureTerminalHttpStatus(logCtx, json);
|
|
267
270
|
const message = json?.error?.message
|
|
@@ -269,12 +272,33 @@ function captureUpstreamError(logCtx: RequestLogContext, text: string | null): v
|
|
|
269
272
|
?? json?.response?.error?.message;
|
|
270
273
|
if (typeof message === "string" && message.trim()) {
|
|
271
274
|
logCtx.upstreamError = redactSecretString(message).slice(0, 500);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// No human-readable error message: fall back to the structured incomplete reason emitted by
|
|
278
|
+
// the bridge on a stall-timeout or adapter EOF (response.incomplete). Maps the raw reason to a
|
|
279
|
+
// reader-facing label so a generic 502 in /api/logs explains WHY the turn ended, not just the
|
|
280
|
+
// mapped HTTP code.
|
|
281
|
+
const reason = json?.response?.incomplete_details?.reason;
|
|
282
|
+
if (typeof reason === "string" && reason.trim()) {
|
|
283
|
+
logCtx.upstreamError = redactSecretString(incompleteReasonLabel(reason.trim())).slice(0, 500);
|
|
272
284
|
}
|
|
273
285
|
} catch {
|
|
274
286
|
/* not JSON; nothing to capture */
|
|
275
287
|
}
|
|
276
288
|
}
|
|
277
289
|
|
|
290
|
+
/** Map a raw `incomplete_details.reason` (emitted by the bridge) to a reader-facing label. */
|
|
291
|
+
function incompleteReasonLabel(reason: string): string {
|
|
292
|
+
switch (reason) {
|
|
293
|
+
case "upstream_stall_timeout":
|
|
294
|
+
return `Upstream stalled: no data for the stall-timeout window (${reason})`;
|
|
295
|
+
case "adapter_eof":
|
|
296
|
+
return `Upstream stream ended unexpectedly without a terminal event (${reason})`;
|
|
297
|
+
default:
|
|
298
|
+
return `Upstream incomplete: ${reason}`;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
278
302
|
function captureTerminalHttpStatus(
|
|
279
303
|
logCtx: RequestLogContext,
|
|
280
304
|
json: {
|