@bitkyc08/opencodex 2.84.0 → 2.87.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 (518) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  4. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  5. package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
  6. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  7. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  8. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  9. package/gui/dist/index.html +2 -2
  10. package/package.json +1 -2
  11. package/src/adapters/anthropic/beta-allowlist.ts +91 -11
  12. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  13. package/src/adapters/anthropic/client-identity.ts +1 -1
  14. package/src/adapters/anthropic/passthrough.ts +16 -3
  15. package/src/adapters/anthropic-progress.ts +84 -0
  16. package/src/adapters/anthropic.ts +108 -21
  17. package/src/adapters/azure.ts +19 -1
  18. package/src/adapters/base.ts +2 -0
  19. package/src/adapters/claude-cli/adapter.ts +111 -23
  20. package/src/adapters/claude-cli/profiles.ts +2 -1
  21. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  22. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  23. package/src/adapters/client-fingerprint.ts +24 -0
  24. package/src/adapters/coding-agent/protocol.ts +1 -1
  25. package/src/adapters/coding-agent/turn.ts +40 -11
  26. package/src/adapters/cursor/arg-codec.ts +17 -5
  27. package/src/adapters/cursor/catalog.ts +8 -0
  28. package/src/adapters/cursor/effort-map.ts +3 -0
  29. package/src/adapters/cursor/freeform-args.ts +75 -0
  30. package/src/adapters/cursor/protobuf-events.ts +36 -23
  31. package/src/adapters/dsh-account.ts +97 -0
  32. package/src/adapters/google-antigravity-wire.ts +22 -1
  33. package/src/adapters/google.ts +37 -10
  34. package/src/adapters/openai-chat/messages.ts +4 -3
  35. package/src/adapters/openai-chat/passthrough.ts +8 -2
  36. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  37. package/src/adapters/openai-chat/response-events.ts +59 -1
  38. package/src/adapters/openai-chat.ts +8 -5
  39. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  40. package/src/adapters/openai-responses/passthrough.ts +57 -125
  41. package/src/adapters/openai-responses/request-strips.ts +25 -3
  42. package/src/adapters/openai-responses/transport.ts +81 -0
  43. package/src/adapters/qoder/adapter.ts +44 -3
  44. package/src/adapters/registry.ts +5 -0
  45. package/src/adapters/zed.ts +26 -1
  46. package/src/advisor/activity.ts +55 -0
  47. package/src/advisor/budget.ts +62 -0
  48. package/src/advisor/consult.ts +223 -0
  49. package/src/advisor/context.ts +238 -0
  50. package/src/advisor/disclosure.ts +20 -0
  51. package/src/advisor/evidence-tool.ts +21 -0
  52. package/src/advisor/evidence.ts +117 -0
  53. package/src/advisor/runtime.ts +353 -0
  54. package/src/advisor/settings.ts +144 -0
  55. package/src/advisor/state.ts +369 -0
  56. package/src/advisor/synthetic-tool.ts +33 -0
  57. package/src/bridge/errors.ts +24 -1
  58. package/src/bridge/internal.ts +3 -1
  59. package/src/bridge/response-json.ts +18 -3
  60. package/src/bridge/sse.ts +58 -8
  61. package/src/bridge/terminal-refusal.ts +59 -0
  62. package/src/chat/inbound.ts +30 -6
  63. package/src/chat/outbound.ts +5 -0
  64. package/src/claude/desktop-3p-library.ts +72 -7
  65. package/src/claude/desktop-3p.ts +75 -19
  66. package/src/claude/desktop-first-party.ts +1 -1
  67. package/src/claude/desktop-picker-platform.ts +4 -0
  68. package/src/claude/desktop-picker-profile.ts +11 -4
  69. package/src/claude/desktop-picker.ts +15 -8
  70. package/src/claude/desktop-profile.ts +10 -1
  71. package/src/claude/desktop-remote-store-io.ts +5 -3
  72. package/src/claude/desktop-system-proxy.ts +50 -4
  73. package/src/claude/inbound-content-options.ts +11 -0
  74. package/src/claude/inbound.ts +154 -50
  75. package/src/claude/intercept/local-ca.ts +8 -1
  76. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  77. package/src/claude/intercept/picker-runtime.ts +6 -5
  78. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  79. package/src/claude/intercept/picker-trust.ts +19 -0
  80. package/src/claude/intercept/port-policy.ts +30 -0
  81. package/src/claude/intercept/runtime.ts +97 -94
  82. package/src/claude/model-default-settings.ts +154 -0
  83. package/src/claude/model-info.ts +7 -7
  84. package/src/claude/outbound.ts +51 -19
  85. package/src/cli/account-api.ts +1 -0
  86. package/src/cli/account-policy-dto.ts +6 -0
  87. package/src/cli/account-policy.ts +34 -5
  88. package/src/cli/account-preservation.ts +44 -0
  89. package/src/cli/account.ts +8 -2
  90. package/src/cli/advisor.ts +165 -0
  91. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  92. package/src/cli/capabilities-accounts.ts +20 -1
  93. package/src/cli/capabilities-agents-routing.ts +23 -2
  94. package/src/cli/capabilities-base.ts +39 -11
  95. package/src/cli/capabilities-observe-system.ts +63 -3
  96. package/src/cli/capabilities-provider-models.ts +11 -0
  97. package/src/cli/chatgpt-command.ts +35 -14
  98. package/src/cli/claude.ts +46 -6
  99. package/src/cli/codex-cli-update.ts +151 -41
  100. package/src/cli/codex-provider.ts +44 -0
  101. package/src/cli/combo-input.ts +10 -1
  102. package/src/cli/combo-result.ts +1 -1
  103. package/src/cli/combo.ts +2 -1
  104. package/src/cli/cross-home-owner.ts +17 -14
  105. package/src/cli/dispatch.ts +30 -5
  106. package/src/cli/doctor.ts +15 -1
  107. package/src/cli/ensure-desktop-cold-start.ts +382 -0
  108. package/src/cli/export-command.ts +1 -1
  109. package/src/cli/help.ts +4 -2
  110. package/src/cli/index.ts +14 -5
  111. package/src/cli/integration-plan-dto.ts +3 -0
  112. package/src/cli/log-history.ts +172 -0
  113. package/src/cli/models-runtime-subcommands.ts +1 -0
  114. package/src/cli/models-runtime.ts +72 -8
  115. package/src/cli/observe-stream.ts +2 -0
  116. package/src/cli/observe.ts +16 -4
  117. package/src/cli/provider.ts +27 -5
  118. package/src/cli/registry.ts +41 -9
  119. package/src/cli/restart-scope.ts +3 -1
  120. package/src/cli/runtime-api.ts +5 -3
  121. package/src/cli/system-command.ts +40 -8
  122. package/src/cli/uninstall-client-state.ts +32 -0
  123. package/src/cli/usage-compare.ts +309 -0
  124. package/src/cli/usage-model-search.ts +4 -4
  125. package/src/cli/usage-report.ts +2 -2
  126. package/src/cli/v2.ts +1 -1
  127. package/src/client/machine-listener.ts +1 -3
  128. package/src/client/runtime.ts +1 -0
  129. package/src/clients/client-path.ts +51 -0
  130. package/src/clients/config-export/contracts.ts +3 -1
  131. package/src/clients/config-export/model-metadata.ts +3 -2
  132. package/src/clients/config-export/qoder.ts +93 -0
  133. package/src/clients/config-export.ts +35 -48
  134. package/src/clients/gentle-shell-path.ts +87 -0
  135. package/src/codex/account-auto-switch.ts +2 -0
  136. package/src/codex/account-identity.ts +29 -0
  137. package/src/codex/account-label.ts +1 -1
  138. package/src/codex/account-lifecycle.ts +21 -5
  139. package/src/codex/account-preservation.ts +28 -0
  140. package/src/codex/account-use-remaining.ts +23 -0
  141. package/src/codex/app-server-processes.ts +97 -7
  142. package/src/codex/app-server-restart-service.ts +55 -4
  143. package/src/codex/auth-api/account-list.ts +4 -0
  144. package/src/codex/auth-api/account-pause-group.ts +6 -50
  145. package/src/codex/auth-api/account-preservation.ts +57 -0
  146. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  147. package/src/codex/auth-api/routes.ts +8 -0
  148. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  149. package/src/codex/auth-api.ts +0 -1
  150. package/src/codex/auth-context.ts +81 -7
  151. package/src/codex/autostart-health.ts +4 -0
  152. package/src/codex/catalog/auto-review.ts +20 -3
  153. package/src/codex/catalog/build-entries.ts +19 -2
  154. package/src/codex/catalog/combo-member.ts +14 -5
  155. package/src/codex/catalog/configured-tiers.ts +50 -0
  156. package/src/codex/catalog/effort.ts +4 -1
  157. package/src/codex/catalog/gather-capture.ts +2 -0
  158. package/src/codex/catalog/metadata.ts +11 -9
  159. package/src/codex/catalog/model-hints.ts +42 -19
  160. package/src/codex/catalog/model-visibility.ts +3 -1
  161. package/src/codex/catalog/native-ultrafast.ts +106 -0
  162. package/src/codex/catalog/parsing.ts +35 -86
  163. package/src/codex/catalog/provider-models.ts +40 -34
  164. package/src/codex/catalog/retained-sync.ts +2 -0
  165. package/src/codex/catalog/routed-gather.ts +33 -16
  166. package/src/codex/catalog/vendor-context.ts +82 -0
  167. package/src/codex/cli-install-provenance.ts +105 -17
  168. package/src/codex/cli-update-plan.ts +545 -0
  169. package/src/codex/config-write-lock.ts +63 -4
  170. package/src/codex/convergence.ts +2 -0
  171. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  172. package/src/codex/desktop-app/types.ts +2 -0
  173. package/src/codex/desktop-app/windows.ts +29 -16
  174. package/src/codex/desktop-app-restart.ts +25 -2
  175. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  176. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  177. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  178. package/src/codex/desktop-compatibility/installed-build.ts +73 -0
  179. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  180. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  181. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  182. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  183. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  184. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  185. package/src/codex/desktop-compatibility/runtime.ts +182 -0
  186. package/src/codex/desktop-compatibility/service.ts +30 -0
  187. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  188. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  189. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  190. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  191. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  192. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  193. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  194. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  195. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  196. package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
  197. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  198. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  199. package/src/codex/features.ts +85 -45
  200. package/src/codex/history-provider.ts +1 -1
  201. package/src/codex/inject/config-write-section.ts +4 -1
  202. package/src/codex/inject/history-failure.ts +27 -0
  203. package/src/codex/inject/remove.ts +18 -6
  204. package/src/codex/inject/restore.ts +24 -2
  205. package/src/codex/inject/routing-classify.ts +4 -3
  206. package/src/codex/inject.ts +18 -27
  207. package/src/codex/journal.ts +16 -1
  208. package/src/codex/main-account-hard-lock.ts +30 -1
  209. package/src/codex/main-account.ts +99 -15
  210. package/src/codex/native-main-refresh-mode.ts +217 -0
  211. package/src/codex/native-profile-startup.ts +1 -1
  212. package/src/codex/ocx-compaction-history.ts +12 -10
  213. package/src/codex/paths.ts +86 -1
  214. package/src/codex/provider-routing.ts +174 -0
  215. package/src/codex/refresh.ts +2 -2
  216. package/src/codex/routing/health-store.ts +2 -2
  217. package/src/codex/routing/pool-recovery.ts +79 -0
  218. package/src/codex/routing/selection.ts +4 -3
  219. package/src/codex/routing/thread-affinity.ts +1 -1
  220. package/src/codex/shim-diagnostics.ts +6 -0
  221. package/src/codex/shim-overlay.ts +7 -2
  222. package/src/codex/signed-in-identity.ts +43 -0
  223. package/src/codex/subagent-model-fallback.ts +1 -1
  224. package/src/codex/sync.ts +6 -1
  225. package/src/codex/user-identity.ts +16 -5
  226. package/src/codex/windows-local-appdata.ts +84 -0
  227. package/src/combos/failover.ts +6 -1
  228. package/src/combos/index.ts +1 -0
  229. package/src/combos/resolve.ts +46 -10
  230. package/src/combos/types.ts +15 -0
  231. package/src/config/diagnostics.ts +35 -7
  232. package/src/config/live-reconcile.ts +51 -2
  233. package/src/config/load-degrade.ts +23 -1
  234. package/src/config/opencode-wire-upgrade.ts +54 -0
  235. package/src/config/opencode-zen-upgrade.ts +52 -0
  236. package/src/config/owner-registry.ts +22 -17
  237. package/src/config/paths.ts +10 -3
  238. package/src/config/provider-validation.ts +45 -2
  239. package/src/config/schema/config-schema.ts +39 -0
  240. package/src/config/schema/desktop-compatibility.ts +9 -0
  241. package/src/config/schema/leaf-validators.ts +39 -9
  242. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  243. package/src/config/schema/native-reasoning-retention.ts +26 -0
  244. package/src/config.ts +2 -0
  245. package/src/generated/compatibility-version.json +962 -350
  246. package/src/generated/model-metadata.ts +1 -1
  247. package/src/grok/sync.ts +1 -1
  248. package/src/images/loop.ts +46 -8
  249. package/src/integrations/catalog-refresh.ts +1 -1
  250. package/src/integrations/config-io.ts +20 -2
  251. package/src/integrations/journal.ts +15 -1
  252. package/src/integrations/mutation-plan.ts +2 -0
  253. package/src/integrations/registry.ts +15 -0
  254. package/src/integrations/store.ts +3 -0
  255. package/src/integrations/writer.ts +10 -1
  256. package/src/lib/advisor-activation.ts +16 -0
  257. package/src/lib/background-work.ts +65 -0
  258. package/src/lib/bounded-file-read.ts +58 -0
  259. package/src/lib/bun-path-runtime.d.mts +10 -1
  260. package/src/lib/bun-path-runtime.mjs +62 -3
  261. package/src/lib/codex-restart-contract.ts +15 -0
  262. package/src/lib/desktop-proxy-route.ts +30 -0
  263. package/src/lib/desktop-upstream-tunnel.ts +72 -0
  264. package/src/lib/empty-tool-catalog.ts +62 -0
  265. package/src/lib/errors.ts +26 -3
  266. package/src/lib/gcp-adc.ts +96 -9
  267. package/src/lib/gcp-credential-input.ts +12 -0
  268. package/src/lib/lab-live-route-production.ts +5 -0
  269. package/src/lib/local-internal-call-capability.ts +55 -0
  270. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  271. package/src/lib/package-tree-integrity.ts +2 -2
  272. package/src/lib/redact.ts +2 -2
  273. package/src/lib/socks5-fetch.ts +48 -71
  274. package/src/lib/socks5-handshake.ts +112 -0
  275. package/src/lib/translator-budget.ts +7 -1
  276. package/src/oauth/account-quota-rank.ts +69 -2
  277. package/src/oauth/anthropic-account-refusal.ts +20 -2
  278. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  279. package/src/oauth/anthropic-routing.ts +1 -21
  280. package/src/oauth/anthropic.ts +38 -7
  281. package/src/oauth/callback-server.ts +27 -1
  282. package/src/oauth/chatgpt-device.ts +1 -1
  283. package/src/oauth/chatgpt-shared.ts +83 -0
  284. package/src/oauth/chatgpt.ts +8 -72
  285. package/src/oauth/dsh.ts +203 -0
  286. package/src/oauth/generic-account-failover.ts +62 -12
  287. package/src/oauth/generic-session-affinity.ts +125 -0
  288. package/src/oauth/index.ts +188 -111
  289. package/src/oauth/kiro-device-login.ts +2 -1
  290. package/src/oauth/kiro.ts +7 -1
  291. package/src/oauth/local-token-detect.ts +21 -1
  292. package/src/oauth/login-cli-errors.ts +44 -0
  293. package/src/oauth/login-cli.ts +69 -7
  294. package/src/oauth/login-flow-state.ts +371 -23
  295. package/src/oauth/meta-muse-errors.ts +41 -0
  296. package/src/oauth/meta-muse-keychain.ts +52 -0
  297. package/src/oauth/meta-muse.ts +51 -84
  298. package/src/oauth/model-discovery-auth.ts +3 -0
  299. package/src/oauth/pool-settings-capability.ts +4 -2
  300. package/src/oauth/refresh-flight-retirement.ts +48 -0
  301. package/src/oauth/session-affinity-key.ts +23 -0
  302. package/src/oauth/store-anthropic-instance.ts +9 -2
  303. package/src/oauth/store.ts +12 -5
  304. package/src/protocols/codecs/chat-ir.ts +467 -0
  305. package/src/protocols/codecs/chat.ts +10 -1
  306. package/src/protocols/codecs/messages-ir.ts +500 -0
  307. package/src/protocols/encoders/adapter-events.ts +5 -4
  308. package/src/protocols/encoders/messages.ts +9 -6
  309. package/src/protocols/features.ts +10 -2
  310. package/src/protocols/path.ts +11 -1
  311. package/src/protocols/plan-snapshot.ts +39 -16
  312. package/src/protocols/plan.ts +9 -1
  313. package/src/protocols/provider-summary.ts +2 -1
  314. package/src/protocols/settings.ts +8 -0
  315. package/src/providers/antigravity-account-roster.ts +102 -0
  316. package/src/providers/antigravity-models.ts +85 -7
  317. package/src/providers/api-key-cooldowns.ts +49 -0
  318. package/src/providers/api-key-resolve.ts +11 -6
  319. package/src/providers/api-key-response-ownership.ts +57 -0
  320. package/src/providers/api-key-selection.ts +31 -6
  321. package/src/providers/api-keys.ts +214 -3
  322. package/src/providers/claude-cli-usage.ts +96 -0
  323. package/src/providers/dsh-account-destination.ts +31 -0
  324. package/src/providers/dsh-account-models.ts +41 -0
  325. package/src/providers/gcp-credential-cleanup.ts +59 -0
  326. package/src/providers/key-failover.ts +54 -50
  327. package/src/providers/key-store.ts +7 -2
  328. package/src/providers/model-rename-fields.ts +5 -0
  329. package/src/providers/model-rename-migration.ts +7 -5
  330. package/src/providers/openai-tiers-destination.ts +11 -2
  331. package/src/providers/openai-tiers.ts +5 -12
  332. package/src/providers/opencode-go-transport.ts +3 -1
  333. package/src/providers/quota/account-cache.ts +6 -5
  334. package/src/providers/quota/account-row.ts +34 -0
  335. package/src/providers/quota/antigravity.ts +28 -7
  336. package/src/providers/quota/dsh-account.ts +70 -0
  337. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  338. package/src/providers/quota-types.ts +2 -0
  339. package/src/providers/quota.ts +14 -20
  340. package/src/providers/registry/entries-core.ts +44 -14
  341. package/src/providers/registry/entries-extended.ts +19 -0
  342. package/src/providers/registry/types.ts +1 -1
  343. package/src/providers/request-pacing.ts +77 -19
  344. package/src/providers/routed-config.ts +339 -0
  345. package/src/providers/service-tier.ts +2 -1
  346. package/src/quota/window-mapping.ts +5 -2
  347. package/src/responses/anthropic-progress.ts +34 -0
  348. package/src/responses/assistant-replay.ts +35 -0
  349. package/src/responses/compaction-history-framing.ts +65 -0
  350. package/src/responses/compaction.ts +6 -1
  351. package/src/responses/incomplete-reason.ts +31 -0
  352. package/src/responses/parser.ts +78 -22
  353. package/src/responses/reasoning-envelope.ts +4 -0
  354. package/src/responses/reasoning-replay-cache.ts +24 -2
  355. package/src/responses/reasoning-retention.ts +173 -0
  356. package/src/responses/retained-compaction.ts +46 -0
  357. package/src/responses/schema.ts +23 -0
  358. package/src/responses/state.ts +32 -29
  359. package/src/router.ts +8 -321
  360. package/src/routing/health.ts +37 -22
  361. package/src/routing/history/build-file.ts +92 -0
  362. package/src/routing/history/indexer.ts +254 -221
  363. package/src/routing/history/ingest.ts +242 -0
  364. package/src/routing/history/rebuild-job.ts +111 -0
  365. package/src/routing/history/rebuild-protocol.ts +15 -0
  366. package/src/routing/history/rebuild-worker.ts +34 -0
  367. package/src/routing/profile.ts +1 -1
  368. package/src/server/admission-model-scope.ts +21 -1
  369. package/src/server/antigravity-direct-eligibility.ts +208 -0
  370. package/src/server/auth-cors.ts +15 -0
  371. package/src/server/chat-completions.ts +82 -10
  372. package/src/server/chat-native-reasoning.ts +123 -0
  373. package/src/server/chat-native-sse.ts +110 -7
  374. package/src/server/chat-native.ts +76 -17
  375. package/src/server/claude-messages.ts +164 -433
  376. package/src/server/effort-policy.ts +1 -1
  377. package/src/server/images.ts +15 -2
  378. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  379. package/src/server/index/model-alias-id.ts +36 -0
  380. package/src/server/index/optional-listeners.ts +2 -0
  381. package/src/server/index/serve-options.ts +11 -3
  382. package/src/server/index/startup-warnings.ts +11 -0
  383. package/src/server/index/websocket-handler.ts +9 -0
  384. package/src/server/index.ts +14 -11
  385. package/src/server/inference/advisor-diagnostics.ts +31 -0
  386. package/src/server/inference/client-encoder-delivery.ts +6 -5
  387. package/src/server/login-shell-path.ts +146 -0
  388. package/src/server/management/advisor-routes.ts +202 -0
  389. package/src/server/management/agent-settings-routes.ts +22 -4
  390. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  391. package/src/server/management/combo-routes.ts +176 -50
  392. package/src/server/management/companion-routes.ts +4 -0
  393. package/src/server/management/config-routes.ts +77 -6
  394. package/src/server/management/context.ts +4 -0
  395. package/src/server/management/cursor-integration-routes.ts +1 -1
  396. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  397. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  398. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  399. package/src/server/management/model-routes.ts +13 -8
  400. package/src/server/management/model-rows.ts +14 -0
  401. package/src/server/management/native-integration-routes.ts +2 -7
  402. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  403. package/src/server/management/oauth-account-routes.ts +87 -14
  404. package/src/server/management/protocol-settings-patch.ts +1 -0
  405. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  406. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  407. package/src/server/management/provider-overwrite-carry.ts +7 -0
  408. package/src/server/management/provider-routes.ts +25 -30
  409. package/src/server/management/route-registry.ts +21 -1
  410. package/src/server/management/sibling-guard.ts +1 -0
  411. package/src/server/management-api.ts +9 -1
  412. package/src/server/messages-antigravity-direct.ts +195 -0
  413. package/src/server/messages-native-tool-guard.ts +185 -0
  414. package/src/server/messages-native.ts +106 -22
  415. package/src/server/messages-shared.ts +431 -0
  416. package/src/server/relay.ts +1 -1
  417. package/src/server/request-log-terminal-status.ts +1 -1
  418. package/src/server/request-log.ts +13 -1
  419. package/src/server/responses/account-change-state.ts +8 -2
  420. package/src/server/responses/adapter-continuation.ts +48 -19
  421. package/src/server/responses/adapter-delivery.ts +43 -8
  422. package/src/server/responses/adapter-dispatch.ts +48 -4
  423. package/src/server/responses/advisor-plan-slot.ts +27 -0
  424. package/src/server/responses/advisor-retention.ts +117 -0
  425. package/src/server/responses/advisor-slot.ts +368 -0
  426. package/src/server/responses/code-mode-collab-surface.ts +116 -8
  427. package/src/server/responses/codex-ws-wire.ts +15 -8
  428. package/src/server/responses/compact.ts +303 -125
  429. package/src/server/responses/compaction-recovery.ts +18 -10
  430. package/src/server/responses/compaction-retention.ts +61 -0
  431. package/src/server/responses/context-overflow.ts +11 -6
  432. package/src/server/responses/core-auth.ts +6 -0
  433. package/src/server/responses/core-codex-account.ts +11 -1
  434. package/src/server/responses/core-combo-failure.ts +12 -1
  435. package/src/server/responses/core-combo.ts +141 -36
  436. package/src/server/responses/core-errors.ts +3 -3
  437. package/src/server/responses/core-lifetime.ts +7 -2
  438. package/src/server/responses/core-normalize.ts +11 -0
  439. package/src/server/responses/core-opaque-recovery.ts +35 -0
  440. package/src/server/responses/core-options.ts +51 -0
  441. package/src/server/responses/core-pipeline.ts +141 -0
  442. package/src/server/responses/core-replay.ts +25 -6
  443. package/src/server/responses/core.ts +3 -116
  444. package/src/server/responses/fetch-helpers.ts +73 -19
  445. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  446. package/src/server/responses/native-response-control.ts +4 -2
  447. package/src/server/responses/passthrough-delivery.ts +19 -11
  448. package/src/server/responses/passthrough-dispatch.ts +29 -16
  449. package/src/server/responses/request-prepare.ts +144 -17
  450. package/src/server/responses/request-sidecar-auth.ts +2 -2
  451. package/src/server/responses/request-transport.ts +120 -27
  452. package/src/server/responses/response-effects.ts +1 -0
  453. package/src/server/responses/run-turn-execution.ts +166 -38
  454. package/src/server/responses/sidecar-execution.ts +98 -5
  455. package/src/server/responses/terminal-guard.ts +4 -17
  456. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  457. package/src/server/startup-action-control.ts +16 -2
  458. package/src/server/startup-health-cache.ts +1 -0
  459. package/src/service/auth-preflight.ts +71 -0
  460. package/src/service/cli.ts +4 -2
  461. package/src/service/definition.ts +97 -0
  462. package/src/service/desktop-startup.ts +14 -0
  463. package/src/service/guards.ts +1 -61
  464. package/src/service/health-sentinel-linux.ts +112 -0
  465. package/src/service/health-sentinel-main.ts +99 -61
  466. package/src/service/health-sentinel-probe.ts +24 -7
  467. package/src/service/health.ts +5 -89
  468. package/src/service/launchd.ts +27 -7
  469. package/src/service/logging.ts +135 -0
  470. package/src/service/repair.ts +2 -1
  471. package/src/service/state.ts +7 -8
  472. package/src/service/systemd.ts +1 -1
  473. package/src/service/windows-guarded-manager.ts +2 -1
  474. package/src/service/windows-ops.ts +1 -1
  475. package/src/service/windows-scheduler.ts +2 -7
  476. package/src/service/windows-taskxml.ts +2 -2
  477. package/src/service/windows-wscript.ts +14 -0
  478. package/src/service.ts +4 -2
  479. package/src/sidecar/auth.ts +2 -0
  480. package/src/tray/windows-tray.ps1 +43 -24
  481. package/src/types/config.ts +124 -6
  482. package/src/types/opencode-go.ts +61 -0
  483. package/src/types/opencode-zen.ts +29 -0
  484. package/src/types/provider.ts +39 -6
  485. package/src/types/request.ts +38 -2
  486. package/src/types/tools.ts +2 -0
  487. package/src/types/wire.ts +29 -37
  488. package/src/types.ts +1 -0
  489. package/src/update/async-check.ts +1 -1
  490. package/src/update/badge.ts +2 -2
  491. package/src/update/index.ts +88 -15
  492. package/src/update/job-supervised-restart.ts +107 -0
  493. package/src/update/job.ts +32 -32
  494. package/src/update/notify.ts +1 -1
  495. package/src/update/npm-invocation.mjs +12 -2
  496. package/src/update/refresh-scheduler.ts +2 -2
  497. package/src/update/supervised-recovery.d.mts +27 -0
  498. package/src/update/supervised-recovery.mjs +157 -0
  499. package/src/usage/cache-diagnostic.ts +2 -0
  500. package/src/usage/cost.ts +48 -12
  501. package/src/usage/expected-prices.ts +15 -0
  502. package/src/usage/ledger-retention.ts +2 -1
  503. package/src/usage/ledger-scanner.ts +6 -5
  504. package/src/usage/log.ts +17 -8
  505. package/src/usage/summary.ts +22 -9
  506. package/src/usage/user-cost-overlays.ts +14 -19
  507. package/src/usage/user-cost-validation.ts +38 -0
  508. package/src/vision/eligibility.ts +13 -0
  509. package/src/web-search/gemini-executor.ts +2 -2
  510. package/src/web-search/loop.ts +55 -5
  511. package/src/web-search/progress-stream.ts +4 -1
  512. package/src/web-search/run-turn-loop.ts +9 -1
  513. package/assets/sponsors/packycode.png +0 -0
  514. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  515. package/gui/dist/assets/App-CZtqiGcw.js +0 -56
  516. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  517. package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
  518. package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
@@ -12,6 +12,7 @@ import type { WsData } from "../ws-bridge";
12
12
  import {
13
13
  requestPacingMaxConcurrentRequests,
14
14
  sendTrackingRequestSlot,
15
+ trackProviderRequestSlotBody,
15
16
  waitForProviderRequestSlot,
16
17
  type ProviderRequestSlot,
17
18
  } from "../../providers/request-pacing";
@@ -49,6 +50,9 @@ const UPSTREAM_REWRITTEN = Symbol.for("opencodex.plugins.upstream-rewritten");
49
50
  const PHYSICAL_DISPATCH = Symbol.for("opencodex.physical-dispatch");
50
51
  // One WS attempt and its HTTP fallback are one logical physical-send receipt.
51
52
  const WS_FALLBACK_RECEIPT = Symbol.for("opencodex.ws-fallback-receipt");
53
+ // Replacement admission waits on the client signal, outside the response-header deadline.
54
+ const HEADER_DEADLINE = Symbol("header-deadline");
55
+ type HeaderDeadline = { clientSignal: AbortSignal; pause(): void; resume(): void };
52
56
 
53
57
  /**
54
58
  * Announce once, per provider, that an explicit egress route moved this provider off the
@@ -139,6 +143,7 @@ export function wantsFreshConnection(
139
143
  export interface PaceAwareFetch {
140
144
  waitForPacing?: (signal?: AbortSignal) => Promise<ProviderRequestSlot | undefined>;
141
145
  unpacedFetch?: typeof globalThis.fetch;
146
+ fetchWithPacingSlot?: (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => Promise<Response>;
142
147
  }
143
148
 
144
149
  export type ProviderFetch = typeof globalThis.fetch & PaceAwareFetch;
@@ -252,7 +257,8 @@ export interface ProviderFetchOptions {
252
257
  */
253
258
  preOutputRefusalHold?: CodexWsPreOutputRefusalHold;
254
259
  /** Revalidate/rebuild a queued request at its physical send boundary, after pacing. */
255
- dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch) => Promise<Response>;
260
+ dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch,
261
+ reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => Promise<Response>;
256
262
  }
257
263
 
258
264
  /** Compose provider pacing and transport admission; receipts run only at executor entry. */
@@ -301,7 +307,8 @@ export function providerFetch(
301
307
  { preconnect },
302
308
  ) as typeof globalThis.fetch);
303
309
  const httpFetch = Object.assign(
304
- async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
310
+ async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit,
311
+ reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => {
305
312
  // Refuse before any dispatch side effect where that is sound. `beforeDispatch` commits
306
313
  // attempt accounting and consumes admission state, so a refusal firing after it would
307
314
  // charge an attempt for a send that never happens, and a throwing hook would mask the
@@ -329,30 +336,33 @@ export function providerFetch(
329
336
  } } : {}),
330
337
  };
331
338
  return options.dispatchOverride
332
- ? options.dispatchOverride(input, dispatchInit, dispatch)
339
+ ? options.dispatchOverride(input, dispatchInit, dispatch, reacquirePacing)
333
340
  : dispatch(input, dispatchInit);
334
341
  },
335
342
  { preconnect },
336
- ) as typeof globalThis.fetch;
343
+ );
337
344
  // ChatGPT Codex backend: streaming turns ride the responses_websockets
338
345
  // transport (measured ~3s faster TTFT than the SSE POST queue); everything
339
346
  // else keeps the provider's HTTP fetch. See ws-upstream.ts for the details.
340
- const unpaced = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
347
+ const unpaced = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit,
348
+ reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => {
341
349
  const upstreamWebsocket = provider.upstreamWebsocket;
342
350
  if (!options.httpOnly && typeof input === "string" && init
343
351
  && shouldUseCodexWsUpstream(input, init, runtime, upstreamWebsocket)) {
344
- if (options.providerName && requestPacingMaxConcurrentRequests(provider, options.modelId) > 0) {
352
+ const keyedPacing = provider.requestPacing?.enabled
353
+ && (provider.requestPacing.keys || provider.requestPacing.keySelection === "round-robin");
354
+ if (keyedPacing || (options.providerName && requestPacingMaxConcurrentRequests(provider, options.modelId) > 0)) {
345
355
  if (!pacingWebsocketDowngradeWarned.has(providerName)
346
356
  && pacingWebsocketDowngradeWarned.size < EGRESS_DOWNGRADE_NOTICE_LIMIT) {
347
357
  pacingWebsocketDowngradeWarned.add(providerName);
348
- console.warn(`[opencodex] provider ${JSON.stringify(redactSecretString(providerName))} has a request concurrency cap; serving over HTTP/SSE.`);
358
+ console.warn(`[opencodex] provider ${JSON.stringify(redactSecretString(providerName))} requires guarded request pacing; serving over HTTP/SSE.`);
349
359
  }
350
- return httpFetch(input, init);
360
+ return httpFetch(input, init, reacquirePacing);
351
361
  }
352
362
  const egress = egressFor(input);
353
363
  if (providerEgressIsExplicit(egress)) {
354
364
  warnEgressWebsocketDowngradeOnce(providerName, describeProviderEgressForLog(egress));
355
- return httpFetch(input, init);
365
+ return httpFetch(input, init, reacquirePacing);
356
366
  }
357
367
  // The fallback has to be the same HTTP fetch the non-WS branch would have
358
368
  // used, protocol pin included: a WS turn that falls back is serving the
@@ -365,11 +375,11 @@ export function providerFetch(
365
375
  options.onPhysicalDispatch?.();
366
376
  };
367
377
  const fallback = ((url: Parameters<typeof globalThis.fetch>[0], fallbackInit?: RequestInit) =>
368
- httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit)) as typeof globalThis.fetch;
378
+ httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit, reacquirePacing)) as typeof globalThis.fetch;
369
379
  return codexWsUpstreamFetch(input, init, fallback, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
370
380
  async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce, options.preOutputRefusalHold);
371
381
  }
372
- return httpFetch(input, init);
382
+ return httpFetch(input, init, reacquirePacing);
373
383
  };
374
384
  let pacingSlotAcquired = options.pacingSlotAcquired === true;
375
385
  const waitForPacing = async (signal?: AbortSignal): Promise<ProviderRequestSlot | undefined> => {
@@ -392,15 +402,35 @@ export function providerFetch(
392
402
  throw new Error("A provider name is required for request concurrency pacing");
393
403
  }
394
404
  };
395
- const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
396
- return sendTrackingRequestSlot(await waitForPacing(init?.signal ?? undefined), () => unpaced(input, init));
405
+ const fetchWithPacingSlot = async (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
406
+ const reacquirePacing = async (refreshed: OcxProviderConfig, modelId?: string) => {
407
+ if (options.turnScopedPacing) throw new Error("Cannot replace a turn-scoped pacing lease during dispatch");
408
+ if (!options.providerName) throw new Error("Refreshed dispatch requires a named pacing owner");
409
+ slot?.release();
410
+ slot = undefined;
411
+ const deadline = (init as Record<symbol, HeaderDeadline> | undefined)?.[HEADER_DEADLINE];
412
+ deadline?.pause();
413
+ try {
414
+ slot = await waitForProviderRequestSlot(providerName, refreshed, modelId, deadline?.clientSignal ?? init?.signal ?? undefined);
415
+ } finally { deadline?.resume(); }
416
+ };
417
+ try {
418
+ const response = await unpaced(input, init, reacquirePacing);
419
+ return trackProviderRequestSlotBody(slot, response);
420
+ } catch (error) {
421
+ slot?.release();
422
+ throw error;
423
+ }
397
424
  };
425
+ const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) =>
426
+ fetchWithPacingSlot(await waitForPacing(init?.signal ?? undefined), input, init);
398
427
  // The returned wrapper forwards its init down to `dispatch`, which applies the route at the
399
428
  // physical send. Adapters that hand this executor back as `provider.fetch` (Cursor does)
400
429
  // therefore still carry a per-provider route instead of being refused as opaque.
401
430
  const paceAware = Object.assign(wrapped, {
402
431
  preconnect,
403
432
  waitForPacing,
433
+ fetchWithPacingSlot,
404
434
  unpacedFetch: Object.assign(unpaced, { preconnect }),
405
435
  });
406
436
  markEgressTransparentExecutor(paceAware as unknown as typeof globalThis.fetch);
@@ -432,6 +462,9 @@ export function storedPoolReplayDispatchNotifier(
432
462
  notified = true;
433
463
  onDispatch();
434
464
  };
465
+ const notifyWithSlot = (slot: ProviderRequestSlot | undefined): void => {
466
+ try { notifyOnce(); } catch (error) { slot?.release(); throw error; }
467
+ };
435
468
  const unpacedSource = executor.unpacedFetch ?? executor;
436
469
  const unpaced = Object.assign(
437
470
  (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
@@ -441,12 +474,21 @@ export function storedPoolReplayDispatchNotifier(
441
474
  { preconnect: unpacedSource.preconnect },
442
475
  ) as ProviderFetch["unpacedFetch"];
443
476
  const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
477
+ if (executor.fetchWithPacingSlot) {
478
+ const slot = await executor.waitForPacing?.(init?.signal ?? undefined);
479
+ notifyWithSlot(slot);
480
+ return executor.fetchWithPacingSlot(slot, input, init);
481
+ }
444
482
  return sendTrackingRequestSlot(await executor.waitForPacing?.(init?.signal ?? undefined), () => unpaced!(input, init));
445
483
  };
446
484
  return Object.assign(wrapped, {
447
485
  preconnect: executor.preconnect,
448
486
  waitForPacing: executor.waitForPacing,
449
487
  unpacedFetch: unpaced,
488
+ ...(executor.fetchWithPacingSlot ? { fetchWithPacingSlot: (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
489
+ notifyWithSlot(slot);
490
+ return executor.fetchWithPacingSlot!(slot, input, init);
491
+ } } : {}),
450
492
  }) as ProviderFetch;
451
493
  }
452
494
 
@@ -470,24 +512,36 @@ export async function fetchWithHeaderTimeout(
470
512
  let timer: ReturnType<typeof setTimeout> | undefined;
471
513
  try {
472
514
  const timeout = new AbortController();
473
- timer = setTimeout(() => {
474
- if (!timeout.signal.aborted) timeout.abort(new DOMException("Timeout elapsed", "TimeoutError"));
475
- }, timeoutMs);
515
+ const deadline: HeaderDeadline = {
516
+ clientSignal: abortSignal,
517
+ pause() { if (timer) clearTimeout(timer); timer = undefined; },
518
+ resume() {
519
+ this.pause();
520
+ timer = setTimeout(() => {
521
+ if (!timeout.signal.aborted) timeout.abort(new DOMException("Timeout elapsed", "TimeoutError"));
522
+ }, timeoutMs);
523
+ },
524
+ };
525
+ deadline.resume();
476
526
  const headers = new Headers(init.headers);
477
527
  // Compressed SSE can be held until the decompressor has a complete block. Streaming calls
478
528
  // default to identity for low-latency frame delivery, while an explicit caller choice wins.
479
529
  if (preferIdentityEncoding && !headers.has("accept-encoding")) {
480
530
  headers.set("accept-encoding", "identity");
481
531
  }
482
- return await sendTrackingRequestSlot(slot, () => fetchExecutor(url, {
532
+ const dispatchInit = {
483
533
  ...init,
484
534
  headers,
485
535
  // Never replay provider credentials or request bodies to a redirect destination.
486
536
  // Preserve the 3xx for the owner's existing response/health policy (#914, #1471).
487
- redirect: "manual",
537
+ redirect: "manual" as const,
488
538
  signal: AbortSignal.any([abortSignal, timeout.signal]),
539
+ [HEADER_DEADLINE]: deadline,
489
540
  timeout: 0,
490
- }));
541
+ };
542
+ return pacing.fetchWithPacingSlot
543
+ ? await pacing.fetchWithPacingSlot(slot, url, dispatchInit)
544
+ : await sendTrackingRequestSlot(slot, () => fetchExecutor(url, dispatchInit));
491
545
  } finally {
492
546
  if (timer) clearTimeout(timer);
493
547
  if (!slot?.bodyTracked) slot?.release();
@@ -0,0 +1,109 @@
1
+ import type { OcxConfig } from "../../types";
2
+ import { resolveComboId } from "../../combos";
3
+ import { isBareOpenAiFamilyModel } from "../../router";
4
+ import { NATIVE_RESERVE_MODEL } from "../../codex/catalog/native-models";
5
+ import {
6
+ getMainAccountHardLockStatus,
7
+ resolveMainAccountHardLockFallback,
8
+ } from "../../codex/main-account-hard-lock";
9
+ import { codexQuotaScopeForModel } from "../../codex/routing/health-store";
10
+ import { getEligiblePoolAccounts } from "../../codex/routing/selection";
11
+ import { OPENAI_CODEX_PROVIDER_ID, isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
12
+ import { providerCodexAccountMode } from "../../providers/registry";
13
+
14
+ function isConfiguredComboSelector(config: OcxConfig, modelId: string): boolean {
15
+ const comboId = resolveComboId(config, modelId);
16
+ return Boolean(comboId && Object.hasOwn(config.combos ?? {}, comboId));
17
+ }
18
+
19
+ function targetsBlockedCanonicalOpenAi(config: OcxConfig, modelId: string): boolean {
20
+ if (isBareOpenAiFamilyModel(modelId) && !isConfiguredComboSelector(config, modelId)) return true;
21
+ const slash = modelId.indexOf("/");
22
+ if (slash <= 0) return false;
23
+ const providerName = modelId.slice(0, slash);
24
+ const rest = modelId.slice(slash + 1);
25
+ if (providerName !== OPENAI_CODEX_PROVIDER_ID || !isBareOpenAiFamilyModel(rest)) return false;
26
+ const provider = config.providers?.[OPENAI_CODEX_PROVIDER_ID];
27
+ return !provider || isCanonicalOpenAiForwardProvider({
28
+ ...provider,
29
+ ...(provider.authMode === undefined ? { authMode: "forward" as const } : {}),
30
+ });
31
+ }
32
+
33
+ /**
34
+ * What one rewritten request carries so the log can show the model the client asked for
35
+ * while the pipeline routes the fallback. The rewrite happens before combo dispatch reads
36
+ * `model`, so the rest of the pipeline sees an ordinary request for the target selector.
37
+ */
38
+ export interface MainHardLockFallbackOverride {
39
+ sourceModel: string;
40
+ targetModel: string;
41
+ }
42
+
43
+ function rawModelOf(body: unknown): { raw: Record<string, unknown>; model: string } | undefined {
44
+ if (!body || typeof body !== "object" || Array.isArray(body)) return undefined;
45
+ const raw = body as Record<string, unknown>;
46
+ return typeof raw.model === "string" && raw.model.trim().length > 0
47
+ ? { raw, model: raw.model }
48
+ : undefined;
49
+ }
50
+
51
+ /**
52
+ * Extended main-account hard-lock settings: when the lock blocks and no Codex account can
53
+ * serve the request, rewrite it to the configured fallback model instead of letting the
54
+ * dispatch refuse.
55
+ *
56
+ * Every guard bounds the rewrite to exactly the traffic the lock would otherwise fail, so
57
+ * the setting can never widen what the lock protects:
58
+ *
59
+ * - The lock must be enabled AND reading `blocked` right now — `ready`, `unknown` and the
60
+ * explicit opt-out all admit, and the release is this same observation, so the reroute
61
+ * ends on its own the moment the blocking windows drop below their thresholds.
62
+ * - `getEligiblePoolAccounts` is the selection choke point the dispatch itself consults,
63
+ * so "the pool can serve it" is one shared answer, not a second opinion: a usable pool
64
+ * account (or any eligible account) keeps the request on Codex, and a pool drained to
65
+ * empty is what makes the fallback the request's only way through. The main account is
66
+ * already excluded there while blocked.
67
+ * - Only bare Codex-native selectors qualify. Provider-qualified rows, combos, account
68
+ * namespaces and policy routes name their own destination on purpose; rewriting those
69
+ * would override an explicit choice, so they keep the lock's refusal.
70
+ * - A Codex-native fallback target is refused outright: rerouting gpt-* to gpt-* would
71
+ * re-enter the same refusal, and the honest behavior is the refusal, not a loop.
72
+ * - Luna Reserve keeps its own admission policy (`assertReserveAdmission`); its helper
73
+ * traffic is not operator chat and must not silently change models.
74
+ */
75
+ export function applyMainHardLockFallbackOverride(
76
+ body: unknown,
77
+ config: OcxConfig,
78
+ now = Date.now(),
79
+ ): MainHardLockFallbackOverride | null {
80
+ const parsed = rawModelOf(body);
81
+ if (!parsed) return null;
82
+ const fallback = resolveMainAccountHardLockFallback(config);
83
+ if (!fallback) return null;
84
+ if (targetsBlockedCanonicalOpenAi(config, fallback.model)) return null;
85
+ if (!isBareOpenAiFamilyModel(parsed.model)
86
+ || parsed.model === NATIVE_RESERVE_MODEL
87
+ || isConfiguredComboSelector(config, parsed.model)) return null;
88
+ if (getMainAccountHardLockStatus(config, now).state !== "blocked") return null;
89
+ const accountMode = providerCodexAccountMode(
90
+ OPENAI_CODEX_PROVIDER_ID,
91
+ config.providers?.[OPENAI_CODEX_PROVIDER_ID],
92
+ );
93
+ if (accountMode !== "direct"
94
+ && getEligiblePoolAccounts(config, undefined, now, codexQuotaScopeForModel(parsed.model)).length > 0) {
95
+ return null;
96
+ }
97
+
98
+ parsed.raw.model = fallback.model;
99
+ if (fallback.reasoningEffort !== undefined) {
100
+ const reasoning = parsed.raw.reasoning;
101
+ parsed.raw.reasoning = {
102
+ ...(reasoning && typeof reasoning === "object" && !Array.isArray(reasoning)
103
+ ? reasoning as Record<string, unknown>
104
+ : {}),
105
+ effort: fallback.reasoningEffort,
106
+ };
107
+ }
108
+ return { sourceModel: parsed.model, targetModel: fallback.model };
109
+ }
@@ -4,6 +4,7 @@ import type { NativeSteeringReplayObserver } from "./native-steering-replay";
4
4
  import type { ProviderExecutedCallType } from "../responses-undeclared-tool-guard";
5
5
 
6
6
  import { isInjectionRequest } from "./native-injection-protocol";
7
+ import { registerProviderResponseProvenance } from "../../providers/request-pacing";
7
8
 
8
9
  /** Shared transport ownership, not a shared steer/inject protocol state machine. */
9
10
  export interface NativeResponseControl {
@@ -25,9 +26,10 @@ export interface NativeResponseControl {
25
26
 
26
27
  export const OPENAI_API_RESPONSES_URL = "https://api.openai.com/v1/responses";
27
28
 
28
- const nativeControlResponses = new WeakSet<Response>();
29
+ const nativeControlResponses = new WeakMap<Response, true>();
30
+ registerProviderResponseProvenance(nativeControlResponses);
29
31
  /** Mark the exact response for multi-response delivery without serializing a wire field. */
30
- export function markNativeControlResponse(response: Response): Response { nativeControlResponses.add(response); return response; }
32
+ export function markNativeControlResponse(response: Response): Response { nativeControlResponses.set(response, true); return response; }
31
33
  /** Recognize a marked native response by identity, not by caller-controlled content. */
32
34
  export function isNativeControlResponse(response: Response): boolean { return nativeControlResponses.has(response); }
33
35
 
@@ -58,6 +58,7 @@ import {
58
58
  hasResponsesSnapshotRepair,
59
59
  createResponsesSnapshotBlockRewrite,
60
60
  } from "../responses-snapshot-repair";
61
+ import { rewriteResponsesIncompleteReasonJson } from "../../responses/incomplete-reason";
61
62
  import { createResponsesModelPayloadRewrite, rewriteResponsesModelJson } from "../responses-model-rewrite";
62
63
  import { createImageGenCallRestoreRewrite, restoreImageGenCallsInJson } from "../responses-image-gen-repair";
63
64
  import {
@@ -108,6 +109,7 @@ import { createResponsesFunctionToolRepairBlockRewrite } from "../responses-func
108
109
  import {
109
110
  createUndeclaredToolCallGuardBlockRewrite,
110
111
  currentTurnWireToolCatalogBody,
112
+ UNDECLARED_TOOL_CALL_ERROR_CODE,
111
113
  undeclaredToolCallNameInResponse,
112
114
  undeclaredToolCallMessage,
113
115
  normalizeDefaultNamespaceInJson,
@@ -437,8 +439,7 @@ export async function deliverPassthroughResponse(
437
439
  normalizeFunctionCompletionJson,
438
440
  } = nativeExchange;
439
441
  const { commitReasoningReplayServingRoute, recordTerminalOutcomes } = responseEffects;
440
- const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget, inboundWire } = requestState;
441
- const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";
442
+ const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget } = requestState;
442
443
  const { openAiSidecar } = sidecarState;
443
444
  const { requestBindings } = transportState;
444
445
 
@@ -768,6 +769,7 @@ export async function deliverPassthroughResponse(
768
769
  : undefined;
769
770
  // Compose opt-in payload rewrites into one parse/stringify pass (image-gen restore first).
770
771
  const payloadRewrites = [
772
+ rewriteResponsesIncompleteReasonJson,
771
773
  createImageGenCallRestoreRewrite(imageGenCallAliases),
772
774
  // #3217: a call whose namespace repeats its own name is unroutable in codex-rs.
773
775
  createSelfNamedToolCallNamespaceScrubRewrite(selfNamedNamespaceScrubAuthorization),
@@ -857,14 +859,17 @@ export async function deliverPassthroughResponse(
857
859
  : undefined,
858
860
  // Last: every rewrite above can still rename or reshape a call item, so the guard must
859
861
  // compare the names the client will actually receive against the declared catalog.
860
- nativeExchange.undeclaredToolGuardActive
862
+ // Deferred-catalog wires keep the rewrite for its normalization half only: a provider's
863
+ // `default.X` call still collapses to the declared `X`, but an undeclared name is the
864
+ // client's own deferred registry's business, not this proxy's verdict.
865
+ nativeExchange.undeclaredToolGuardActive || declaredWireToolNames.size > 0
861
866
  ? createUndeclaredToolCallGuardBlockRewrite(
862
867
  declaredWireToolNames,
863
868
  declaredNamelessClientCallTypes,
864
869
  providerExecutedCallTypes,
865
870
  declaredBareWireToolNames,
866
871
  recoverableBareCustomWireToolNames,
867
- enforceDeclaredToolNames,
872
+ { refuseUndeclared: nativeExchange.undeclaredToolGuardActive },
868
873
  )
869
874
  : undefined,
870
875
  grokUpstreamEchoEnabled
@@ -1314,7 +1319,7 @@ export async function deliverPassthroughResponse(
1314
1319
  const modelRewritten = parsed._responseModelId !== undefined && parsed._responseModelId !== parsed.modelId
1315
1320
  ? rewriteResponsesModelJson(repaired, parsed._responseModelId)
1316
1321
  : repaired;
1317
- return modelRewritten;
1322
+ return rewriteResponsesIncompleteReasonJson(modelRewritten);
1318
1323
  })();
1319
1324
  if (plaintextV2RestoreFailed) {
1320
1325
  return formatErrorResponse(502, "upstream_error", PLAINTEXT_V2_AGENT_MESSAGE_RESTORE_OVERFLOW_MESSAGE);
@@ -1322,10 +1327,7 @@ export async function deliverPassthroughResponse(
1322
1327
  if (grokUpstreamEchoEnabled) {
1323
1328
  clientJson = stripGrokUpstreamEnvelopeEchoFromResponsesJson(clientJson);
1324
1329
  }
1325
- // #1700: same inbound-wire refusal policy as the SSE relay above. Both the plain JSON answer and
1326
- // the reframed-SSE branch below are built from this body, so one check covers them. This
1327
- // runs BEFORE the continuation cache write below: a refused turn must not become state a
1328
- // later `previous_response_id` replay can expand from.
1330
+ // Both response formats use this body, before a refused turn can enter continuation state.
1329
1331
  if (nativeExchange.undeclaredToolGuardActive) {
1330
1332
  const undeclared = (() => {
1331
1333
  try {
@@ -1341,9 +1343,15 @@ export async function deliverPassthroughResponse(
1341
1343
  return undefined;
1342
1344
  }
1343
1345
  })();
1344
- if (enforceDeclaredToolNames && undeclared !== undefined) {
1345
- return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared));
1346
+ if (undeclared !== undefined) {
1347
+ return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared), {
1348
+ code: UNDECLARED_TOOL_CALL_ERROR_CODE,
1349
+ });
1346
1350
  }
1351
+ }
1352
+ // Same split as the SSE rewrite: normalization applies whenever a declared name exists,
1353
+ // even on wires whose deferred catalog stands the refusal down.
1354
+ if (declaredWireToolNames.size > 0) {
1347
1355
  clientJson = normalizeDefaultNamespaceInJson(
1348
1356
  clientJson,
1349
1357
  declaredWireToolNames,
@@ -1,4 +1,6 @@
1
1
  import { spendLedgerStorageErrorResponse } from "./spend-storage-error";
2
+ import { requestPacingOverloadResponse } from "./pacing-overload";
3
+ import { recordDistributedResponseFailure } from "../../providers/api-key-response-ownership";
2
4
  import { createSteeringSettingsNormalizer } from "./native-steering-policy";
3
5
  import { nativeResponseControlEligible } from "./native-response-control";
4
6
  import { NativeInjectionReplay } from "./native-injection-replay";
@@ -21,6 +23,7 @@ import { imageGenToolCallAliases } from "../responses-image-gen-repair";
21
23
  import { rememberResponseState, isBodyNonPersistable } from "../../responses/state";
22
24
  import {
23
25
  currentTurnWireToolCatalogBody,
26
+ hasExplicitEmptyToolCatalog,
24
27
  hasExplicitWireToolCatalog,
25
28
  collectDeclaredWireToolNames,
26
29
  collectDeclaredBareWireToolNames,
@@ -140,7 +143,7 @@ import { resolveWireProtocolOverride } from "../adapter-resolve";
140
143
  import { refreshPoolForwardAuth, refreshNativeMainForwardAuth, withClaudeNativeSession } from "./core-auth";
141
144
  import { bindRouteReasoningReplayScope } from "./core-replay";
142
145
  import type { OAuthAccessSnapshot } from "../../oauth";
143
- import { OAuthAccountPausedError, OAuthLoginRequiredError, publicOAuthAuthenticationErrorMessage } from "../../oauth";
146
+ import { OAuthAccountPausedError, OAuthLoginRequiredError, OAuthTokenRefreshBusyError, publicOAuthAuthenticationErrorMessage } from "../../oauth";
144
147
  import { AnthropicAccountCooldownError } from "../../oauth/anthropic-routing";
145
148
  import { resolveCopilotApiBaseUrl } from "../../oauth/github-copilot";
146
149
  import {
@@ -153,7 +156,7 @@ import { captureCodexAffinityDiagnostic } from "../../codex/affinity-debug";
153
156
  import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS } from "../../codex/catalog/native-models";
154
157
  import {
155
158
  attemptOpaqueBlobRecovery,
156
- isEncryptedFunctionOutputRejection,
159
+ isEncryptedFunctionOutputRejection, retainedReasoningStreamCandidate, retainedReasoningStreamRejection,
157
160
  outboundResponsesBodyCarriesEncryptedFunctionOutput,
158
161
  resetStreamedOpaqueBlobLogContext,
159
162
  consoleGoUploadRejectionBody,
@@ -187,6 +190,7 @@ export async function preparePassthroughExchange(
187
190
  | "selectedForwardHeaders"
188
191
  | "clientRequestedStream"
189
192
  | "inboundWire"
193
+ | "clientDeclaredEmptyToolCatalog"
190
194
  | "substituteMainCredential"
191
195
  | "callerAuthHeaders"
192
196
  | "subagentFallbackAccountId"
@@ -253,6 +257,7 @@ export async function preparePassthroughExchange(
253
257
  responseStateOptions,
254
258
  clientRequestedStream,
255
259
  inboundWire,
260
+ clientDeclaredEmptyToolCatalog,
256
261
  substituteMainCredential,
257
262
  callerAuthHeaders,
258
263
  } = requestState;
@@ -531,10 +536,18 @@ export async function preparePassthroughExchange(
531
536
  declaredWireToolNames.add(name);
532
537
  }
533
538
  }
539
+ // Chat and Anthropic inbounds may deliver tools deferred/partially: their client registry
540
+ // owns the full catalog, so this guard cannot read the translated body as the authorization
541
+ // boundary. An explicit empty catalog — flagged by the translating ingress or present
542
+ // verbatim in a replayed body — is still a deny-all the caller declared; everything else
543
+ // stays deferred to the caller's own tool handling.
534
544
  undeclaredToolGuardActive = (
535
- declaredWireToolNames.size > 0
536
- || clientDeclaredNamelessCallTypes.size > 0
537
- || clientExplicitWireToolCatalog
545
+ inboundWire === "responses"
546
+ ? declaredWireToolNames.size > 0
547
+ || clientDeclaredNamelessCallTypes.size > 0
548
+ || clientExplicitWireToolCatalog
549
+ : clientDeclaredEmptyToolCatalog
550
+ || hasExplicitEmptyToolCatalog(clientToolAuthorizationBody)
538
551
  ) && route.provider.authMode !== "forward";
539
552
  options.nativeControl?.configureToolAuthorization?.(
540
553
  undeclaredToolGuardActive,
@@ -731,7 +744,9 @@ export async function preparePassthroughExchange(
731
744
  const stage = readCodexWsStage(response);
732
745
  if (stage && logCtx.activeAttempt) logCtx.activeAttempt.codexWsStage = stage;
733
746
  };
747
+ let upstreamResponse: Response, lastDistributedResponse: Response | undefined;
734
748
  const adoptObservedResponse = <T extends Response>(response: T): T => {
749
+ if (!isNonReplayableResponse(response)) lastDistributedResponse = response.status === 401 || response.status === 429 ? response : undefined;
735
750
  settleObservedHostResponse();
736
751
  adoptCodexWsStage(response);
737
752
  return response;
@@ -748,7 +763,6 @@ export async function preparePassthroughExchange(
748
763
  const upstream = new AbortController();
749
764
  const cleanupUpstreamAbort = linkAbortSignal(upstream, options.abortSignal ?? req.signal);
750
765
  const connectMs = config.connectTimeoutMs ?? 200_000;
751
- let upstreamResponse: Response;
752
766
  /**
753
767
  * This leg's transient-5xx ladder cap, from the provider's own `transientRetryOn5xx`.
754
768
  *
@@ -881,7 +895,6 @@ export async function preparePassthroughExchange(
881
895
  ): Response | undefined => {
882
896
  const result = checkOutboundBodySize(builtRequest.body, config.maxUpstreamBodyBytes);
883
897
  if (result.admitted) return undefined;
884
-
885
898
  // This returns before the surrounding fetch/finally owns the observation, so release
886
899
  // it here or one refused body holds translator budget for the process lifetime.
887
900
  builtRequest.releaseBodyObservation?.();
@@ -919,6 +932,7 @@ export async function preparePassthroughExchange(
919
932
  releaseCodexAuthContextProbeLease(admissionState.authCtx);
920
933
  return clientCancelledResponse();
921
934
  }
935
+ if (lastDistributedResponse) recordDistributedResponseFailure(lastDistributedResponse, config, route.providerName, options.abortSignal);
922
936
  // A budget refusal is a proxy decision, not an upstream fault. Reporting it as
923
937
  // 502 upstream_error would blame the provider for a limit this process applied, and
924
938
  // would record a fake reachability failure against the account's health.
@@ -934,12 +948,12 @@ export async function preparePassthroughExchange(
934
948
  bindRefreshedAdapter(refusal.adapter);
935
949
  return handoffToChat(refusal.adapter);
936
950
  }
937
- const copilotRefusal = copilotRefusalResponse(refusal);
938
- if (copilotRefusal) {
951
+ const dispatchRefusal = requestPacingOverloadResponse(refusal) ?? copilotRefusalResponse(refusal);
952
+ if (dispatchRefusal) {
939
953
  releaseUpstreamHostAdmission(nativeHostState.lease);
940
954
  nativeHostState.lease = null;
941
955
  releaseCodexAuthContextProbeLease(admissionState.authCtx);
942
- return copilotRefusal;
956
+ return dispatchRefusal;
943
957
  }
944
958
  // Pacing may outlive the selected account's admission. No fetch occurred, so do
945
959
  // not turn an operator pause into a 502 or charge it to host/account health.
@@ -1195,7 +1209,6 @@ export async function preparePassthroughExchange(
1195
1209
 
1196
1210
  // Keep recovery kinds in sync with the generic `recovery:` loop below.
1197
1211
  passthroughRecovery: for (;;) {
1198
-
1199
1212
  if (
1200
1213
  upstreamResponse.status === 401
1201
1214
  && (admissionState.authCtx.kind === "main-pool" || admissionState.authCtx.kind === "pool")
@@ -1347,6 +1360,8 @@ export async function preparePassthroughExchange(
1347
1360
  } catch (err) {
1348
1361
  upstream.abort();
1349
1362
  releaseCodexAuthContextProbeLease(admissionState.authCtx);
1363
+ if (options.abortSignal?.aborted || req.signal.aborted) return clientCancelledResponse();
1364
+ if (err instanceof OAuthTokenRefreshBusyError) return formatErrorResponse(503, "server_error", publicOAuthAuthenticationErrorMessage(err), { retryAfter: "1" });
1350
1365
  return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
1351
1366
  }
1352
1367
  if (route.provider.googleMode === "cloud-code-assist" && !refreshed.projectId) {
@@ -1515,7 +1530,6 @@ export async function preparePassthroughExchange(
1515
1530
  )) noteAttemptRecoveryWithheld(logCtx.activeAttempt, "rotation-send-budget");
1516
1531
  }
1517
1532
  }
1518
-
1519
1533
  // Same-target 429 wait-and-retry (opt-in `retryOn429`) for key-auth providers on the
1520
1534
  // passthrough wire. This branch returns before the recovery loop below, so Responses-shaped
1521
1535
  // key-auth gateways (e.g. the built-in DeepSeek preset) would otherwise surface 429
@@ -1586,7 +1600,6 @@ export async function preparePassthroughExchange(
1586
1600
  return transportFailureResponse(err);
1587
1601
  }
1588
1602
  }
1589
-
1590
1603
  const captureAffinityResponse = (
1591
1604
  response: Response,
1592
1605
  captureAuthCtx: CodexAuthContext = admissionState.authCtx,
@@ -1608,8 +1621,8 @@ export async function preparePassthroughExchange(
1608
1621
  status: response.status,
1609
1622
  });
1610
1623
  };
1624
+ recordDistributedResponseFailure(lastDistributedResponse ?? upstreamResponse, config, route.providerName, options.abortSignal);
1611
1625
  captureAffinityResponse(upstreamResponse);
1612
-
1613
1626
  if (usesCodexForwardPoolAuth(admissionState.authCtx, route.provider)) {
1614
1627
  let poolRetryOutcome: number | undefined;
1615
1628
  // A success is the freshest evidence there is about this pair, and it outranks any earlier
@@ -1738,7 +1751,7 @@ export async function preparePassthroughExchange(
1738
1751
  && (recoveryContentType.includes("text/event-stream") || (!recoveryContentType && upstreamRequestsStream))
1739
1752
  && !opaqueBlobRecoveryGuard.attempted
1740
1753
  && !configuredTransientSendBudgetExhausted()
1741
- && outboundResponsesBodyCarriesEncryptedFunctionOutput(request.body);
1754
+ && (outboundResponsesBodyCarriesEncryptedFunctionOutput(request.body) || (!sendBudgetState.ambiguousResendSpent && retainedReasoningStreamCandidate(parsed, request.body)));
1742
1755
  if (streamedFunctionOutputCandidate) {
1743
1756
  const preflightLog: RequestLogContext = { model: logCtx.model, provider: logCtx.provider };
1744
1757
  const preflight = await preflightComboStreamResponse(upstreamResponse, preflightLog,
@@ -1751,7 +1764,7 @@ export async function preparePassthroughExchange(
1751
1764
  // intentionally understands only Responses error/message fields. Preserve only this
1752
1765
  // exact identity so the projected 502 can enter the existing single-shot recovery.
1753
1766
  if (decryptRejection) preflightLog.upstreamError = ENCRYPTED_FUNCTION_OUTPUT_REJECTION;
1754
- return decryptRejection;
1767
+ return decryptRejection || (!sendBudgetState.ambiguousResendSpent && retainedReasoningStreamRejection(payload, parsed, request.body, preflightLog));
1755
1768
  }, {
1756
1769
  allowMissingContentType: !recoveryContentType && upstreamRequestsStream,
1757
1770
  replayReadErrors: true,