@yansigit/opencodex 2.33.0 → 2.35.0

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.
Files changed (196) hide show
  1. package/README.md +3 -3
  2. package/gui/dist/assets/index-BjCaHxdz.js +112 -0
  3. package/gui/dist/assets/index-DLkXOXLC.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +1 -1
  6. package/src/adapters/anthropic.ts +79 -2
  7. package/src/adapters/command-code.ts +141 -23
  8. package/src/adapters/cursor/call-id.ts +44 -0
  9. package/src/adapters/cursor/checkpoint-store.ts +15 -10
  10. package/src/adapters/cursor/discovery.ts +60 -2
  11. package/src/adapters/cursor/effort-map.ts +79 -1
  12. package/src/adapters/cursor/envelope-echo.ts +162 -0
  13. package/src/adapters/cursor/live-models.ts +7 -2
  14. package/src/adapters/cursor/live-transport.ts +17 -1
  15. package/src/adapters/cursor/message-mapper.ts +4 -1
  16. package/src/adapters/cursor/native-exec-fs.ts +13 -12
  17. package/src/adapters/cursor/native-exec-network.ts +3 -5
  18. package/src/adapters/cursor/native-exec-policy.ts +47 -0
  19. package/src/adapters/cursor/native-exec-shell.ts +116 -31
  20. package/src/adapters/cursor/native-exec.ts +38 -10
  21. package/src/adapters/cursor/protobuf-events.ts +28 -2
  22. package/src/adapters/cursor/protobuf-request.ts +93 -41
  23. package/src/adapters/cursor/request-builder.ts +39 -10
  24. package/src/adapters/cursor/tool-definitions.ts +27 -3
  25. package/src/adapters/cursor/tool-result-normalize.ts +51 -6
  26. package/src/adapters/cursor/types.ts +23 -4
  27. package/src/adapters/cursor.ts +170 -29
  28. package/src/adapters/google-aistudio-parser.ts +49 -0
  29. package/src/adapters/google-antigravity-replay.ts +105 -25
  30. package/src/adapters/google-antigravity-wire.ts +5 -0
  31. package/src/adapters/google-errors.ts +41 -12
  32. package/src/adapters/google-http.ts +12 -11
  33. package/src/adapters/google.ts +219 -36
  34. package/src/adapters/image.ts +1 -1
  35. package/src/adapters/kiro-constants.ts +15 -0
  36. package/src/adapters/kiro-tools.ts +43 -15
  37. package/src/adapters/kiro.ts +54 -9
  38. package/src/adapters/openai-chat.ts +286 -242
  39. package/src/adapters/openai-responses.ts +335 -24
  40. package/src/adapters/run-turn-queue.ts +36 -1
  41. package/src/adapters/tool-catalog-nudge.ts +2 -2
  42. package/src/adapters/xai-tool-schema.ts +436 -0
  43. package/src/bridge.ts +67 -26
  44. package/src/chat/inbound.ts +29 -1
  45. package/src/chat/outbound.ts +15 -7
  46. package/src/claude/agents-inject.ts +8 -1
  47. package/src/claude/outbound.ts +10 -8
  48. package/src/cli/account-api.ts +27 -7
  49. package/src/cli/account-extended.ts +10 -3
  50. package/src/cli/account.ts +29 -5
  51. package/src/cli/alias.ts +66 -0
  52. package/src/cli/claude.ts +26 -1
  53. package/src/cli/dispatch.ts +13 -1
  54. package/src/cli/help.ts +1 -0
  55. package/src/cli/index.ts +6 -1
  56. package/src/cli/init.ts +1 -0
  57. package/src/cli/models-runtime.ts +95 -0
  58. package/src/cli/models.ts +13 -7
  59. package/src/cli/provider-runtime.ts +16 -2
  60. package/src/cli/registry.ts +6 -1
  61. package/src/cli/telemetry-commands.ts +25 -0
  62. package/src/cli/v2.ts +34 -10
  63. package/src/codex/account-pause.ts +2 -1
  64. package/src/codex/account-priority.ts +3 -2
  65. package/src/codex/app-server-processes.ts +80 -6
  66. package/src/codex/auth-api.ts +48 -8
  67. package/src/codex/auth-context.ts +21 -18
  68. package/src/codex/catalog/aggregation.ts +6 -0
  69. package/src/codex/catalog/model-metadata.ts +13 -1
  70. package/src/codex/catalog/native-models.ts +5 -2
  71. package/src/codex/catalog/parsing.ts +16 -0
  72. package/src/codex/catalog/provider-fetch.ts +20 -3
  73. package/src/codex/catalog/sync.ts +127 -2
  74. package/src/codex/catalog.ts +1 -1
  75. package/src/codex/codex-write-lock.ts +3 -1
  76. package/src/codex/convergence-types.ts +1 -1
  77. package/src/codex/convergence.ts +22 -2
  78. package/src/codex/desired-state.ts +2 -2
  79. package/src/codex/desktop-app-restart.ts +18 -5
  80. package/src/codex/inject-coordination.ts +83 -0
  81. package/src/codex/inject.ts +14 -1
  82. package/src/codex/log-guard/inspect.ts +22 -4
  83. package/src/codex/model-entitlements.ts +9 -2
  84. package/src/codex/prompt-layers.ts +371 -25
  85. package/src/codex/prompt-text-probe.ts +238 -0
  86. package/src/codex/quota.ts +123 -18
  87. package/src/codex/routing.ts +9 -0
  88. package/src/codex/subagent-model-fallback.ts +198 -27
  89. package/src/codex/transition-state.ts +107 -8
  90. package/src/combos/types.ts +10 -0
  91. package/src/compatibility/openai-responses.ts +33 -1
  92. package/src/config/autonomous-remediation.ts +21 -0
  93. package/src/config/provider-validation.ts +14 -0
  94. package/src/config/rebase-provenance.ts +68 -0
  95. package/src/config.ts +191 -17
  96. package/src/generated/compatibility-version.json +279 -159
  97. package/src/generated/model-metadata.ts +3 -0
  98. package/src/images/loop.ts +5 -4
  99. package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
  100. package/src/lab/fabric/producer-child.ts +1 -1
  101. package/src/lib/config-ownership.ts +20 -0
  102. package/src/lib/errors.ts +11 -2
  103. package/src/lib/package-tree-integrity.ts +101 -0
  104. package/src/oauth/aistudio-credentials.ts +65 -0
  105. package/src/oauth/aistudio-native-daemon.ts +116 -0
  106. package/src/oauth/aistudio-session-sync.ts +95 -0
  107. package/src/oauth/generic-account-failover.ts +231 -0
  108. package/src/oauth/google-aistudio-auth.ts +98 -0
  109. package/src/oauth/index.ts +57 -5
  110. package/src/oauth/key-providers.ts +18 -1
  111. package/src/oauth/kiro.ts +45 -0
  112. package/src/oauth/login-cli.ts +65 -1
  113. package/src/oauth/types.ts +15 -0
  114. package/src/providers/codex-capacity.ts +5 -2
  115. package/src/providers/command-code-efforts.ts +38 -6
  116. package/src/providers/context-cap.ts +4 -3
  117. package/src/providers/default-aliases.ts +65 -0
  118. package/src/providers/derive.ts +29 -1
  119. package/src/providers/fastwire.ts +7 -1
  120. package/src/providers/model-presets.ts +119 -0
  121. package/src/providers/new-model-policy.ts +146 -0
  122. package/src/providers/provider-id-rewrite.ts +2 -1
  123. package/src/providers/quota.ts +157 -46
  124. package/src/providers/registry.ts +184 -71
  125. package/src/providers/slug-codec.ts +52 -0
  126. package/src/responses/code-mode-helper-compat.ts +50 -0
  127. package/src/responses/custom-tool-compat.ts +34 -10
  128. package/src/responses/parser.ts +4 -0
  129. package/src/responses/schema.ts +5 -1
  130. package/src/responses/thought-signature-replay.ts +17 -0
  131. package/src/router.ts +43 -2
  132. package/src/routing/account-pool/cooldown.ts +8 -0
  133. package/src/routing/account-pool/index.ts +1 -0
  134. package/src/routing/analytics.ts +1 -0
  135. package/src/routing/quota.ts +10 -0
  136. package/src/server/auth-cors.ts +24 -0
  137. package/src/server/chat-completions.ts +26 -16
  138. package/src/server/chat-native-sse.ts +3 -3
  139. package/src/server/chat-native.ts +30 -11
  140. package/src/server/claude-messages.ts +1 -1
  141. package/src/server/effort-policy.ts +16 -0
  142. package/src/server/index.ts +180 -14
  143. package/src/server/lifecycle.ts +52 -1
  144. package/src/server/management/agent-settings-routes.ts +31 -15
  145. package/src/server/management/codex-prompt-routes.ts +570 -0
  146. package/src/server/management/combo-routes.ts +2 -1
  147. package/src/server/management/config-routes.ts +27 -9
  148. package/src/server/management/context.ts +9 -0
  149. package/src/server/management/logs-usage-routes.ts +11 -5
  150. package/src/server/management/model-routes.ts +266 -0
  151. package/src/server/management/oauth-account-routes.ts +13 -3
  152. package/src/server/management/provider-routes.ts +137 -3
  153. package/src/server/management/routing-profile-routes.ts +2 -2
  154. package/src/server/management-api.ts +2 -0
  155. package/src/server/port-reclaim.ts +19 -1
  156. package/src/server/relay-eager.ts +147 -20
  157. package/src/server/relay.ts +251 -19
  158. package/src/server/request-log-conversation.ts +33 -0
  159. package/src/server/request-log.ts +48 -21
  160. package/src/server/responses/collaboration.ts +42 -5
  161. package/src/server/responses/combo-stream-preflight.ts +10 -3
  162. package/src/server/responses/core.ts +575 -140
  163. package/src/server/responses/empty-completion-guard.ts +35 -0
  164. package/src/server/responses/fetch-helpers.ts +14 -6
  165. package/src/server/responses/input-admission.ts +3 -1
  166. package/src/server/responses/passthrough-error.ts +33 -9
  167. package/src/server/responses/policy-fallback.ts +1 -1
  168. package/src/server/responses/responses-field-backfill.ts +105 -13
  169. package/src/server/responses/ws-upstream.ts +35 -5
  170. package/src/server/responses-custom-tool-repair.ts +52 -7
  171. package/src/server/responses-terminal-repair.ts +25 -4
  172. package/src/server/sse-frame-buffer.ts +31 -4
  173. package/src/server/ws-bridge.ts +14 -2
  174. package/src/smoke/fingerprint-cache.ts +133 -0
  175. package/src/smoke/live-scenarios.ts +33 -0
  176. package/src/smoke/runner.ts +119 -0
  177. package/src/telemetry/dispatcher.ts +44 -0
  178. package/src/telemetry/fingerprint.ts +24 -0
  179. package/src/telemetry/hook.ts +43 -0
  180. package/src/telemetry/ledger.ts +54 -0
  181. package/src/telemetry/types.ts +23 -0
  182. package/src/types/config.ts +66 -14
  183. package/src/types/provider.ts +79 -1
  184. package/src/types/request.ts +18 -10
  185. package/src/types/tools.ts +30 -11
  186. package/src/types.ts +1 -0
  187. package/src/usage/command-code-manifest.ts +116 -0
  188. package/src/usage/cost.ts +2 -2
  189. package/src/usage/expected-prices.ts +126 -24
  190. package/src/usage/log.ts +18 -8
  191. package/src/usage/summary.ts +34 -12
  192. package/src/web-search/exa-executor.ts +40 -9
  193. package/src/web-search/index.ts +16 -8
  194. package/src/web-search/loop.ts +5 -4
  195. package/gui/dist/assets/index-DKLr4LTE.js +0 -102
  196. package/gui/dist/assets/index-DrSQdTRd.css +0 -1
@@ -255,8 +255,19 @@ export interface OcxSubagentRole {
255
255
  enabled?: boolean;
256
256
  }
257
257
 
258
+ export interface OcxConfigRebaseProvenance {
259
+ version: 1;
260
+ deletedTopLevelKeys: string[];
261
+ }
262
+
258
263
  export interface OcxConfig {
259
264
  port: number;
265
+ autonomousRemediation?: {
266
+ enabled?: boolean;
267
+ instanceId?: string;
268
+ threshold?: number;
269
+ rollingWindowMs?: number;
270
+ };
260
271
  /** Opt in to one identical-turn retry when a Responses completion has no text or tool call. */
261
272
  emptyCompletionRetry?: boolean;
262
273
  /**
@@ -276,6 +287,22 @@ export interface OcxConfig {
276
287
  managementUsageMaxReadBytes?: number;
277
288
  providers: Record<string, OcxProviderConfig>;
278
289
  defaultProvider: string;
290
+ /** Persisted state for newly discovered provider models (#2464). Absent keeps legacy "on" behavior. */
291
+ modelDiscovery?: {
292
+ newModelPolicy?: "on" | "off";
293
+ knownModels?: Record<string, {
294
+ ids: string[];
295
+ removed: string[];
296
+ updatedAt: string;
297
+ /** Consecutive successful discoveries in which an active id was absent. */
298
+ missing?: Record<string, number>;
299
+ }>;
300
+ recentArrivals?: Record<string, Array<{ id: string; at: string }>>;
301
+ };
302
+ /** Enable the shipped model alias patterns for providers without an override. */
303
+ defaultModelAliases?: boolean;
304
+ /** Explicit top-level deletion intent used by stale whole-config rebases. */
305
+ configRebaseProvenance?: OcxConfigRebaseProvenance | Record<string, unknown>;
279
306
  /** OpenAI provider-contract migration marker (v2 = single `openai` provider with account mode). */
280
307
  openaiProviderTierVersion?: 1 | 2;
281
308
  /** One-time migration marker for Antigravity's static-catalog defaults. */
@@ -294,12 +321,7 @@ export interface OcxConfig {
294
321
  * into a selector-qualified group; Codex still advertises only the first 5 visible rows.
295
322
  */
296
323
  subagentModels?: string[];
297
- /**
298
- * Named specialist roles the parent may spawn: id, when-to-use description,
299
- * model, optional effort, and child developer instructions. Max 8 roles and
300
- * 5 unique enabled models (the spawn picker window). Do not store
301
- * `model_fallback` here — use `subagentModelFallbackByModel`.
302
- */
324
+ /** Named specialist roles the parent may spawn by id. */
303
325
  subagentRoles?: OcxSubagentRole[];
304
326
  /**
305
327
  * Project enabled roles into marker-owned `$CODEX_HOME/agents/ocx-<id>.toml`.
@@ -338,6 +360,10 @@ export interface OcxConfig {
338
360
  * reject the whole role file as an unknown field (#1190).
339
361
  */
340
362
  subagentModelFallbackByModel?: Record<string, string[]>;
363
+ /**
364
+ * Ordered candidates for spawned sub-agents, globally or keyed by role/model.
365
+ */
366
+ subagentCandidates?: string[] | Record<string, string[]>;
341
367
  /**
342
368
  * TTL (ms) for cached sub-agent model availability probes. Default 60_000.
343
369
  */
@@ -402,9 +428,7 @@ export interface OcxConfig {
402
428
  * remain unchanged),
403
429
  * `{{effort}}` -> injectionEffort, `{{roster}}` -> the resolved sub-agent roster
404
430
  * block ("" when nothing resolves), `{{fallback}}` -> the configured subagent
405
- * model fallback guidance block ("" when unset), `{{roles}}` -> the compact
406
- * enabled role catalog ("" when none). A custom prompt replaces the built-in
407
- * body; include `{{roles}}` to keep the catalog.
431
+ * model fallback guidance block ("" when unset).
408
432
  */
409
433
  injectionPrompt?: string;
410
434
  /**
@@ -468,12 +492,12 @@ export interface OcxConfig {
468
492
  * Routed parents get v2 tools; Sol/Terra can still spawn Grok/Claude (issue #92).
469
493
  */
470
494
  keepNativeChatGptOnV1?: boolean;
471
- /** Experimental routed target for eligible native V2 root parents. */
495
+ /** Optional v2-native parent override for spawn_agent routing. */
472
496
  v2NativeParentOverride?: { enabled?: boolean; model?: string };
473
497
  /** Experimental, default-off ChatGPT recovery for encrypted V2 routed tasks. */
474
498
  agentTaskRecovery?: {
475
499
  enabled?: boolean;
476
- /** ChatGPT model used by the recovery request. Default: gpt-5.6-luna. */
500
+ /** ChatGPT model used by the recovery request. Default: gpt-5.6-sol. */
477
501
  model?: string;
478
502
  /** Recovery request timeout in milliseconds. Default: 45000. */
479
503
  timeoutMs?: number;
@@ -516,6 +540,13 @@ export interface OcxConfig {
516
540
  * those are unset — Bun's fetch honors them for all outbound calls; localhost is excluded.
517
541
  */
518
542
  proxy?: string;
543
+ /**
544
+ * Hosts that bypass `proxy` for OpenCodex's own outbound provider calls, merged into
545
+ * NO_PROXY at startup. Accepts a comma-separated string (NO_PROXY syntax) or an array.
546
+ * Loopback is always excluded regardless of this setting, and an inherited NO_PROXY is
547
+ * preserved — this ADDS entries, it never replaces the environment.
548
+ */
549
+ noProxy?: string | string[];
519
550
  /**
520
551
  * Upstream stall timeout (seconds). After this many seconds of no upstream data, emits
521
552
  * response.incomplete. Default 300. Min 1.
@@ -587,6 +618,15 @@ export interface OcxConfig {
587
618
  * selector map remains visible for compatibility with hand-written configurations.
588
619
  */
589
620
  codexAccountPickerEnabled?: boolean;
621
+ /**
622
+ * Show the GPT-5.3-Codex-Spark weekly window on Codex quota surfaces. Default false.
623
+ *
624
+ * Spark is a single-model window that reads 0% for most operators, and on a multi-account
625
+ * pool it doubles the bar count for information almost nobody acts on. Hidden by default and
626
+ * revealed by an explicit `true`; a malformed value reads as hidden rather than rejecting the
627
+ * whole config.
628
+ */
629
+ showCodexSparkQuota?: boolean;
590
630
  /** Active pool account id for next session. undefined = main (passthrough as-is). */
591
631
  activeCodexAccountId?: string;
592
632
  /** Auto-switch threshold (0-100). Default 80. 0 = disabled. */
@@ -617,10 +657,22 @@ export interface OcxConfig {
617
657
  stickyLimit?: number;
618
658
  };
619
659
  /**
620
- * Opt-in Cursor OAuth account pool. Default OFF.
621
- * Sticky `_clientThreadId` affinity + bounded 429/auth failover when ≥2 OAuth accounts exist.
622
- * Does not wire weighted-round-robin `CursorCredentialRouter`.
660
+ * Generic OAuth multi-account 429 failover (#2568). Presence-driven by default.
661
+ *
662
+ * Rotates to another logged-in account of the SAME provider when one is rate-limited, for
663
+ * OAuth providers that have no pool of their own — xAI, Cursor, Kimi, GitHub Copilot,
664
+ * Antigravity, Nous. The Codex pool and the Anthropic pool own their own rotation and are
665
+ * excluded; this setting changes neither.
666
+ *
667
+ * With the key absent, rotation activates when a provider has 2 or more eligible stored
668
+ * accounts — the same consent rule API-key pools already apply to a 2+ key pool (#2568d). A
669
+ * single account is a strict no-op. Set `false` to keep strict single-account behaviour;
670
+ * `providers.<name>.oauthAccountFailover` overrides this per provider.
623
671
  */
672
+ oauthAccountFailover?: {
673
+ enabled?: boolean;
674
+ };
675
+ /** Cursor OAuth account pool rotation for api2.cursor.sh. */
624
676
  cursorAccountPool?: {
625
677
  enabled?: boolean;
626
678
  };
@@ -119,6 +119,15 @@ export interface TierObservationContext {
119
119
  fastWire: FastWire | null;
120
120
  demandDecision: "force-fast" | "force-default" | "inherit";
121
121
  callerTier?: string;
122
+ /**
123
+ * Whether the destination's echoed `service_tier` is authoritative about Fast scheduling.
124
+ *
125
+ * The ChatGPT-internal Codex backend returns `service_tier: "default"` on turns that were in
126
+ * fact scheduled as priority, so treating its echo as a downgrade produced a false
127
+ * `response-declined` on every Fast request (#2558). Absent means "assume authoritative",
128
+ * preserving the behaviour for the public API where the echo does mean what it says.
129
+ */
130
+ responseTierAuthoritative?: boolean;
122
131
  }
123
132
 
124
133
  export type TierDecision =
@@ -131,6 +140,12 @@ export type TierDecision =
131
140
  * retries are allowed; OAuth/forward credentials and local runtimes are never replayed.
132
141
  */
133
142
  export interface OcxProviderConfig {
143
+ /** Optional short provider namespace used only at request/catalog presentation time. */
144
+ alias?: string;
145
+ /** Native model id -> short, slash-free request alias. */
146
+ modelAliases?: Record<string, string>;
147
+ /** Override the global built-in model-alias switch for this provider. */
148
+ defaultAliases?: boolean;
134
149
  adapter: string;
135
150
  /**
136
151
  * Codex tool calling mode for routed models.
@@ -227,6 +242,19 @@ export interface OcxProviderConfig {
227
242
  * link-local, or unique-local upstreams. Metadata endpoints remain blocked.
228
243
  */
229
244
  allowPrivateNetwork?: boolean;
245
+ /**
246
+ * ChatGPT Codex backend WebSocket upstream transport.
247
+ * Defaults to false (routes streaming turns over standard HTTP/SSE).
248
+ * Set `true` to opt into the faster responses_websockets transport.
249
+ * `OCX_CODEX_WS_UPSTREAM=true` or `1` also enables it when this is omitted;
250
+ * `false` and `0` disable it. Invalid or absent values default to HTTP/SSE.
251
+ */
252
+ wsUpstream?: boolean;
253
+ /**
254
+ * Maximum WebSocket request frame size in bytes before routing over standard HTTP/SSE.
255
+ * Defaults to CODEX_WS_CREATE_FRAME_LIMIT_BYTES (~16 MiB minus margin).
256
+ */
257
+ maxWsFrameBytes?: number;
230
258
  /**
231
259
  * Pin the HTTP version used for upstream provider requests. Bun's fetch negotiates
232
260
  * HTTP/2 via TLS ALPN by default; some Cloudflare-fronted SSE endpoints hang on
@@ -284,6 +312,33 @@ export interface OcxProviderConfig {
284
312
  * full set so the user can pick). See devlog issue_052_provider-model-allowlist.
285
313
  */
286
314
  selectedModels?: string[];
315
+ /** Override for newly discovered models. Absent/"inherit" uses the install policy. */
316
+ newModelPolicy?: "on" | "off" | "inherit";
317
+ /**
318
+ * Model-preset marker for `selectedModels` (#2465). Absent means "all", exactly today's
319
+ * semantics — an existing provider is never narrowed by an upgrade.
320
+ *
321
+ * The preset is a SEED, not a lock: `selectedModels` holds concrete ids materialized from
322
+ * the shipped rules, so every existing consumer and older binaries keep working against a
323
+ * plain allowlist. Divergence is detected at the WRITE path rather than by diffing — any user
324
+ * edit while the mode is "preset" flips it to "custom", after which the proxy never
325
+ * re-materializes. That collapses upgrade reconciliation to a version compare.
326
+ *
327
+ * Deliberately distinct from `deriveProviderPresets`, which curates WHICH PROVIDERS to offer.
328
+ * This curates which MODELS a provider exposes; the code says "model preset" throughout.
329
+ */
330
+ modelPreset?: {
331
+ mode: "preset" | "all" | "custom";
332
+ /** MODEL_PRESETS version materialized into `selectedModels`. */
333
+ appliedVersion?: number;
334
+ appliedAt?: string;
335
+ /**
336
+ * Set when materialization matched nothing and the provider fell back to "all". A preset
337
+ * must never write an empty allowlist, because empty means ALL and would silently
338
+ * un-curate; the fallback marker lets the next convergence retry.
339
+ */
340
+ fallback?: "preset-empty";
341
+ };
287
342
  /** Provider-wide fallback when context metadata is absent; otherwise caps the reported window. */
288
343
  contextWindow?: number;
289
344
  /** Per-model fallback when context metadata is absent; otherwise caps the reported window. */
@@ -327,6 +382,16 @@ export interface OcxProviderConfig {
327
382
  * providers whose registry entry declares authKind "local" (management API enforces).
328
383
  */
329
384
  authMode?: "key" | "forward" | "oauth" | "local";
385
+ /**
386
+ * Per-provider override for generic OAuth multi-account 429 failover (#2568).
387
+ *
388
+ * Rotation is presence-driven by default — 2+ logged-in accounts activate it — so this exists
389
+ * for the operator who accepts rotation on one provider and refuses it on another. An explicit
390
+ * boolean here beats the global `oauthAccountFailover` and beats presence.
391
+ */
392
+ oauthAccountFailover?: {
393
+ enabled?: boolean;
394
+ };
330
395
  /** Allow an explicitly key/oauth provider to run without a credential (for keyless local proxies). */
331
396
  keyOptional?: boolean;
332
397
  /**
@@ -359,6 +424,18 @@ export interface OcxProviderConfig {
359
424
  * Responses backend rejects Codex summary-delivery fields for that model.
360
425
  */
361
426
  modelSupportsReasoningSummaries?: Record<string, boolean>;
427
+ /**
428
+ * Model-specific Codex Responses verbosity capability. Set false when the upstream ignores
429
+ * `text.verbosity`; the catalog hides the no-op picker and the Responses adapter strips stale
430
+ * or caller-supplied values while preserving other `text` fields.
431
+ */
432
+ modelSupportsVerbosity?: Record<string, boolean>;
433
+ /**
434
+ * Provider-wide Codex Responses verbosity capability, applied to models the per-model map
435
+ * does not enumerate (a live-discovered id, for example). Materialized from the registry at
436
+ * seed/enrich time so the catalog hint pass never has to read PROVIDER_REGISTRY.
437
+ */
438
+ supportsVerbosity?: boolean;
362
439
  /**
363
440
  * Per-model wire value for Responses `stream_options.reasoning_summary_delivery`.
364
441
  * Presence also advertises reasoning-summary support for that routed model.
@@ -526,8 +603,9 @@ export interface OcxProviderConfig {
526
603
  * Google adapter mode. "ai-studio" (default) = Generative Language API + x-goog-api-key.
527
604
  * "vertex" = Vertex AI project/location endpoints with GCP ADC (or x-goog-api-key).
528
605
  * "cloud-code-assist" = Google Antigravity (Cloud Code Assist) OAuth + CCA envelope.
606
+ * "ai-studio-web" = browser-relayed AI Studio Playground/Build session.
529
607
  */
530
- googleMode?: "ai-studio" | "vertex" | "cloud-code-assist";
608
+ googleMode?: "ai-studio" | "vertex" | "cloud-code-assist" | "ai-studio-web";
531
609
  /** Vertex AI GCP project id (or GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT env). */
532
610
  project?: string;
533
611
  /** Vertex AI location, e.g. "us-central1" or "global" (or GOOGLE_CLOUD_LOCATION env). */
@@ -70,6 +70,10 @@ export interface OcxParsedRequest {
70
70
  _clientThreadId?: string;
71
71
  /** True when promptCacheKey is a shared cache cohort rather than a conversation identity. */
72
72
  _promptCacheKeyIsSharedCohort?: boolean;
73
+ /** Provider-private Command Code session affinity id, stable across in-process request mutation. */
74
+ _commandCodeSessionId?: string;
75
+ /** Cursor-only thread owner; may be an opaque process-local Desktop session/thread identity. */
76
+ _cursorClientThreadId?: string;
73
77
  /** Conversation/provider/account/model-bound namespace for reasoning replay state. */
74
78
  _reasoningReplayScope?: OcxReasoningReplayScopeRef;
75
79
  /**
@@ -88,7 +92,7 @@ export interface OcxParsedRequest {
88
92
  */
89
93
  _cursorIsolateConversation?: boolean;
90
94
  /** Account-scoped, non-secret Kiro request metadata selected with the OAuth access token. */
91
- _kiroAuthContext?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion">;
95
+ _kiroAuthContext?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion" | "authType">;
92
96
  /** Provider-private continuation metadata resolved from the Responses previous_response_id chain. */
93
97
  _providerContinuation?: OcxProviderContinuationState;
94
98
  /** Persisted continuation considered only after the final physical route is known. */
@@ -196,8 +200,14 @@ export interface OcxImageContent {
196
200
  detail?: string;
197
201
  }
198
202
 
199
- /** A user/developer message content part: text or an image (vision). */
200
- export type OcxContentPart = OcxTextContent | OcxImageContent;
203
+ export interface OcxVideoContent {
204
+ type: "video";
205
+ /** A base64 `data:` URL from an OpenAI-compatible `video_url` part. */
206
+ videoUrl: string;
207
+ }
208
+
209
+ /** A user/developer message content part: text or native media. */
210
+ export type OcxContentPart = OcxTextContent | OcxImageContent | OcxVideoContent;
201
211
 
202
212
  export interface OcxThinkingContent {
203
213
  type: "thinking";
@@ -258,13 +268,9 @@ export interface OcxRequestOptions {
258
268
  /**
259
269
  * Responses `text.format` (json_schema / json_object), preserved for adapters whose
260
270
  * upstream wire has an equivalent. The openai-chat adapter re-nests it as chat
261
- * `response_format`, the exact inverse of responseFormatToText in src/chat/inbound.ts; the
262
- * Google adapter lowers supported requests to Gemini JSON mode (`responseMimeType` /
263
- * `responseSchema`) but skips requests with tools, Claude models, or image-capable models.
264
- * The `openai-chat` adapter can omit it for models in `noStructuredOutputModels`; Kiro
265
- * rejects structured output via `_structuredOutput`; and Cursor has no structured-output
266
- * wire field and rejects the request before transport.
267
- * Native passthrough does not consume this option and forwards `_rawBody.text` verbatim.
271
+ * `response_format`, the exact inverse of responseFormatToText in src/chat/inbound.ts.
272
+ * The native passthrough ignores it (it forwards `_rawBody.text` verbatim) and Kiro
273
+ * keeps rejecting structured output via `_structuredOutput`.
268
274
  */
269
275
  textFormat?: {
270
276
  type: "json_schema" | "json_object";
@@ -290,10 +296,12 @@ export type GoogleSafetyThreshold =
290
296
  | "BLOCK_ONLY_HIGH"
291
297
  | "BLOCK_NONE"
292
298
  | "OFF";
299
+
293
300
  export interface GoogleSafetySetting {
294
301
  category: GoogleSafetyCategory;
295
302
  threshold: GoogleSafetyThreshold;
296
303
  }
304
+
297
305
  export interface GoogleProviderOptions {
298
306
  thinkingBudget?: number;
299
307
  includeThoughts?: boolean;
@@ -32,30 +32,49 @@ export function namespacedToolName(namespace: string | undefined, name: string):
32
32
  }
33
33
 
34
34
  /**
35
- * Codex 0.149 unified-exec name normalization.
35
+ * Codex unified-exec name normalization.
36
36
  *
37
37
  * Codex's code-mode shell tool is declared as `exec` (a freeform custom tool whose own
38
38
  * description mentions the nested `await tools.exec_command(...)` helper). Routed models —
39
39
  * DeepSeek in particular — sometimes echo that helper name as the tool-call name, emitting
40
- * `exec_command` instead of the declared `exec`. Accept the legacy shell bridge names only
41
- * when the request catalog actually declares `exec` and does not itself declare the legacy
42
- * name (an MCP server may legitimately advertise `exec_command` under its own namespace).
40
+ * `exec_command` or `apply_patch` instead of the declared `exec`. Accept these nested helper
41
+ * names only when the request catalog actually declares `exec` and does not itself declare the
42
+ * emitted name (an MCP server may legitimately advertise one under its own namespace). Some
43
+ * hosted adapters also strip a unique namespace prefix or replace its separator with `_`; those
44
+ * forms are accepted only when one declaration can be identified unambiguously.
43
45
  */
44
46
  const LEGACY_SHELL_BRIDGE_TOOL_NAMES = ["exec_command", "shell_command"] as const;
47
+ const CODE_MODE_HELPER_TOOL_NAMES = [...LEGACY_SHELL_BRIDGE_TOOL_NAMES, "apply_patch"] as const;
45
48
 
46
49
  export function normalizeDeclaredToolName(
47
50
  name: string,
48
51
  declared: ReadonlySet<string> | undefined,
49
52
  ): string {
50
- if (!declared || !declared.has("exec")) return name;
53
+ if (!declared) return name;
51
54
  if (declared.has(name)) return name;
52
- // When the catalog explicitly declares any legacy shell bridge name, the environment
53
- // genuinely exposes that tool turn normalization off so a call is never mis-routed
54
- // to `exec`.
55
- if ((LEGACY_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).some(legacy => declared.has(legacy))) {
56
- return name;
55
+ if (declared.has("exec")) {
56
+ // When the catalog explicitly declares any legacy shell bridge name, the environment
57
+ // genuinely exposes that tool — turn normalization off so a call is never mis-routed
58
+ // to `exec`.
59
+ if ((LEGACY_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).some(legacy => declared.has(legacy))) {
60
+ return name;
61
+ }
62
+ if ((CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(name)) {
63
+ return "exec";
64
+ }
65
+ }
66
+ // Models may strip a namespace prefix or replace separators with underscores. Resolve only
67
+ // when exactly one declaration matches, so an ambiguous bare name remains fail-closed.
68
+ let matched: string | undefined;
69
+ for (const dec of declared) {
70
+ const matchesSuffix = !name.includes(":") && !name.includes("__")
71
+ && (dec.endsWith(`:${name}`) || dec.endsWith(`__${name}`));
72
+ const matchesSanitized = dec.replace(/[^A-Za-z0-9_-]/g, "_") === name;
73
+ if (!matchesSuffix && !matchesSanitized) continue;
74
+ if (matched !== undefined) return name;
75
+ matched = dec;
57
76
  }
58
- return (LEGACY_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).includes(name) ? "exec" : name;
77
+ return matched ?? name;
59
78
  }
60
79
 
61
80
  export function toolChoiceAliases(tool: Pick<OcxTool, "namespace" | "name">): string[] {
package/src/types.ts CHANGED
@@ -66,6 +66,7 @@ export type {
66
66
  OcxApiKeyEntry,
67
67
  OcxClientIntegrationsConfig,
68
68
  OcxSubagentRole,
69
+ OcxConfigRebaseProvenance,
69
70
  OcxConfig,
70
71
  OcxAccountPoolRotationStrategy,
71
72
  OcxComboStrategy,
@@ -0,0 +1,116 @@
1
+ import type { Cost4, ExpectedPriceOverlay } from "./expected-prices";
2
+
3
+ export interface CommandCodeModelPricing {
4
+ id: string;
5
+ name: string;
6
+ slug?: string;
7
+ contextWindow?: number;
8
+ cost4: Cost4;
9
+ }
10
+
11
+ export const COMMAND_CODE_MODELS_DATA_URL = "https://commandcode.ai/models.data";
12
+ export const COMMAND_CODE_PRICING_SOURCE = "https://commandcode.ai/models + https://commandcode.ai/models.data";
13
+
14
+ /**
15
+ * Parse Command Code models and 4-tuple per-1M token rates from the Turbo-stream
16
+ * payload served by https://commandcode.ai/models.data.
17
+ */
18
+ export function parseCommandCodeModelsData(raw: unknown): CommandCodeModelPricing[] {
19
+ if (!Array.isArray(raw) || raw.length < 10) return [];
20
+ const modelIndices = raw[9];
21
+ if (!Array.isArray(modelIndices)) return [];
22
+
23
+ const resolve = (val: unknown): unknown => {
24
+ if (typeof val === "number") {
25
+ if (val < 0) return val === -5 ? null : undefined;
26
+ if (val < raw.length) return raw[val];
27
+ }
28
+ return val;
29
+ };
30
+
31
+ const results: CommandCodeModelPricing[] = [];
32
+ for (const idx of modelIndices) {
33
+ if (typeof idx !== "number" || idx < 0 || idx >= raw.length) continue;
34
+ const obj = raw[idx];
35
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) continue;
36
+
37
+ const fields: Record<string, unknown> = {};
38
+ for (const [k, v] of Object.entries(obj)) {
39
+ if (!k.startsWith("_")) continue;
40
+ const keyIdx = Number.parseInt(k.slice(1), 10);
41
+ if (!Number.isFinite(keyIdx) || keyIdx < 0 || keyIdx >= raw.length) continue;
42
+ const keyName = raw[keyIdx];
43
+ if (typeof keyName === "string" && keyName.length > 0) {
44
+ fields[keyName] = resolve(v);
45
+ }
46
+ }
47
+
48
+ const id = typeof fields.id === "string" ? fields.id.trim() : "";
49
+ if (!id) continue;
50
+
51
+ const name = typeof fields.name === "string" && fields.name.trim() ? fields.name.trim() : id;
52
+ const slug = typeof fields.slug === "string" && fields.slug.trim() ? fields.slug.trim() : undefined;
53
+ const contextWindow = typeof fields.contextWindow === "number" && Number.isFinite(fields.contextWindow)
54
+ ? fields.contextWindow
55
+ : undefined;
56
+
57
+ const input = typeof fields.inputCost === "number" && Number.isFinite(fields.inputCost) && fields.inputCost >= 0
58
+ ? fields.inputCost
59
+ : 0;
60
+ const output = typeof fields.outputCost === "number" && Number.isFinite(fields.outputCost) && fields.outputCost >= 0
61
+ ? fields.outputCost
62
+ : 0;
63
+ const cacheRead = typeof fields.cacheReadCost === "number" && Number.isFinite(fields.cacheReadCost) && fields.cacheReadCost >= 0
64
+ ? fields.cacheReadCost
65
+ : 0;
66
+ const cacheWrite = typeof fields.cacheWriteCost === "number" && Number.isFinite(fields.cacheWriteCost) && fields.cacheWriteCost >= 0
67
+ ? fields.cacheWriteCost
68
+ : 0;
69
+
70
+ results.push({
71
+ id,
72
+ name,
73
+ ...(slug ? { slug } : {}),
74
+ ...(contextWindow !== undefined ? { contextWindow } : {}),
75
+ cost4: { input, output, cacheRead, cacheWrite },
76
+ });
77
+ }
78
+
79
+ return results;
80
+ }
81
+
82
+ /**
83
+ * Fetch and decode the latest Command Code model pricing from commandcode.ai.
84
+ */
85
+ export async function fetchCommandCodeModelPricing(options?: {
86
+ url?: string;
87
+ signal?: AbortSignal;
88
+ fetchFn?: typeof fetch;
89
+ }): Promise<CommandCodeModelPricing[]> {
90
+ const fetcher = options?.fetchFn ?? fetch;
91
+ const url = options?.url ?? COMMAND_CODE_MODELS_DATA_URL;
92
+ const res = await fetcher(url, { signal: options?.signal });
93
+ if (!res.ok) throw new Error(`Failed to fetch Command Code models data: ${res.status}`);
94
+ const data = await res.json();
95
+ return parseCommandCodeModelsData(data);
96
+ }
97
+
98
+ /**
99
+ * Convert parsed Command Code models to opencodex ExpectedPriceOverlay entries.
100
+ */
101
+ export function commandCodePricingToExpectedOverlays(
102
+ models: readonly CommandCodeModelPricing[],
103
+ verifiedAt = "2026-08-26",
104
+ ): ExpectedPriceOverlay[] {
105
+ return models
106
+ .filter(m => m.cost4.input > 0 || m.cost4.output > 0 || m.cost4.cacheRead > 0 || m.cost4.cacheWrite > 0)
107
+ .map(m => ({
108
+ provider: "command-code",
109
+ modelId: m.id,
110
+ cost4: m.cost4,
111
+ source: COMMAND_CODE_PRICING_SOURCE,
112
+ verifiedAt,
113
+ status: "verified",
114
+ }));
115
+ }
116
+
package/src/usage/cost.ts CHANGED
@@ -253,7 +253,7 @@ function resolveMatchedPriceExact(
253
253
  const verifiedOverride = overlays === EXPECTED_PRICE_OVERLAYS
254
254
  ? findVerifiedPriceOverride(provider, modelId)
255
255
  : undefined;
256
- if (verifiedOverride && validCost4(verifiedOverride.cost4) && hasNonZeroCost(verifiedOverride.cost4)) {
256
+ if (verifiedOverride && verifiedOverride.status !== "unverified" && validCost4(verifiedOverride.cost4) && hasNonZeroCost(verifiedOverride.cost4)) {
257
257
  return {
258
258
  provider,
259
259
  modelId,
@@ -261,7 +261,7 @@ function resolveMatchedPriceExact(
261
261
  source: "expected",
262
262
  sourceRef: verifiedOverride.source,
263
263
  verifiedAt: verifiedOverride.verifiedAt,
264
- status: "verified",
264
+ status: verifiedOverride.status,
265
265
  };
266
266
  }
267
267
  const metadataProvider = resolveMetadataProvider(provider);