@bitkyc08/opencodex 2.7.1 → 2.7.3

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.
@@ -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
- // 260709 refresh: claude-fable-5 added (official models overview); evidence in
69
- // devlog/model_update/260709_model_refresh/002_cursor_registry_drift.md.
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": OPENAI_GPT56_CONTEXT_WINDOW,
85
- "openai/gpt-5.6-terra": OPENAI_GPT56_CONTEXT_WINDOW,
86
- "openai/gpt-5.6-luna": OPENAI_GPT56_CONTEXT_WINDOW,
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
- const KIMI_THINKING_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"];
126
- const KIMI_LOCKED_PARAMETER_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"];
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
- "moonshotai/Kimi-K2.5", "kimi-k2.6", "kimi-k2.7-code",
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: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"],
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: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
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
- "moonshotai/Kimi-K2.5", "kimi-k2.5-fast", "kimi-k2.6", "kimi-k2.6-fast",
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
- "moonshotai/Kimi-K2.5": [],
365
- "kimi-k2.5-fast": [],
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", "kimi-k2.5-fast", "kimi-k2.6-fast", "qwen3.5-397b-fast", "qwen3.6-35b-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
- { id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true, dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3-pro", jawcodeBundle: "google", extraMetadataAliases: ["gemini"] },
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-chat",
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
- { id: "cerebras", label: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://cloud.cerebras.ai/platform/apikeys", defaultModel: "llama-3.3-70b" },
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
- { id: "firepass", label: "Fire Pass (Fireworks Kimi)", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://fireworks.ai/account/api-keys" },
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: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"],
420
- noReasoningModels: KIMI_THINKING_MODELS,
421
- modelReasoningEfforts: Object.fromEntries(KIMI_THINKING_MODELS.map(id => [id, []])),
422
- noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
423
- noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
424
- noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
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: KIMI_THINKING_MODELS,
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
- { id: "litellm", label: "LiteLLM (self-hosted)", baseUrl: "http://localhost:4000/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://docs.litellm.ai/docs/proxy/quick_start" },
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
- models: ["glm-5.2", "deepseek-v4-pro", "qwen3-coder", "gpt-oss:120b", "kimi-k2.6", "minimax-m3", "qwen3.5", "gemma4"],
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
- { id: "minimax", label: "MiniMax — Coding Plan", baseUrl: "https://api.minimax.io/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.minimax.io", defaultModel: "MiniMax-M2.5", jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive", note: "Subscription Key or API Key" },
467
- { id: "minimax-cn", label: "MiniMax — Coding Plan (CN)", baseUrl: "https://api.minimaxi.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.minimaxi.com", defaultModel: "MiniMax-M2.5", jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive", note: "中国区 Subscription Key" },
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: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"],
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: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
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
 
@@ -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: registryEntry.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?: { error?: { type?: unknown; code?: unknown; message?: unknown } };
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: {