@bitkyc08/opencodex 2.83.0 → 2.86.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 (405) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-BjlttVLy.js +58 -0
  4. package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
  5. package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
  6. package/gui/dist/assets/index-BMssP4k3.js +94 -0
  7. package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/package.json +2 -3
  10. package/src/adapters/anthropic/beta-allowlist.ts +87 -8
  11. package/src/adapters/anthropic/client-identity.ts +1 -1
  12. package/src/adapters/anthropic/passthrough.ts +33 -4
  13. package/src/adapters/azure.ts +19 -1
  14. package/src/adapters/client-fingerprint.ts +24 -0
  15. package/src/adapters/coding-agent/protocol.ts +15 -8
  16. package/src/adapters/coding-agent/turn.ts +22 -9
  17. package/src/adapters/cursor/arg-codec.ts +17 -5
  18. package/src/adapters/cursor/catalog.ts +8 -0
  19. package/src/adapters/cursor/effort-map.ts +3 -0
  20. package/src/adapters/cursor/freeform-args.ts +75 -0
  21. package/src/adapters/cursor/protobuf-events.ts +36 -23
  22. package/src/adapters/google-antigravity-wire.ts +22 -1
  23. package/src/adapters/google.ts +2 -2
  24. package/src/adapters/ollama-native.ts +29 -1
  25. package/src/adapters/openai-chat/messages.ts +8 -4
  26. package/src/adapters/openai-chat/response-events.ts +59 -1
  27. package/src/adapters/openai-chat.ts +8 -5
  28. package/src/adapters/openai-responses/empty-assistant.ts +32 -0
  29. package/src/adapters/openai-responses/passthrough.ts +3 -0
  30. package/src/adapters/openai-responses/request-strips.ts +10 -2
  31. package/src/adapters/qoder/adapter.ts +56 -15
  32. package/src/advisor/consult.ts +220 -0
  33. package/src/advisor/context.ts +226 -0
  34. package/src/advisor/disclosure.ts +20 -0
  35. package/src/advisor/runtime.ts +305 -0
  36. package/src/advisor/settings.ts +135 -0
  37. package/src/advisor/state.ts +369 -0
  38. package/src/advisor/synthetic-tool.ts +33 -0
  39. package/src/bridge/errors.ts +24 -0
  40. package/src/bridge.ts +1 -1
  41. package/src/chat/inbound.ts +30 -6
  42. package/src/chat/outbound.ts +5 -0
  43. package/src/claude/context-windows.ts +54 -1
  44. package/src/claude/desktop-3p-library.ts +43 -3
  45. package/src/claude/desktop-3p.ts +22 -8
  46. package/src/claude/desktop-picker-profile.ts +11 -4
  47. package/src/claude/desktop-profile.ts +10 -1
  48. package/src/claude/desktop-remote-store-io.ts +5 -3
  49. package/src/claude/inbound.ts +121 -50
  50. package/src/claude/intercept/connect-proxy.ts +10 -3
  51. package/src/claude/intercept/local-ca.ts +8 -1
  52. package/src/claude/intercept/picker-ca-persistence.ts +3 -1
  53. package/src/claude/intercept/picker-listener.ts +49 -1
  54. package/src/claude/intercept/settings-no-proxy.ts +146 -0
  55. package/src/claude/intercept/settings.ts +101 -9
  56. package/src/claude/model-default-settings.ts +154 -0
  57. package/src/claude/model-info.ts +7 -7
  58. package/src/claude/outbound.ts +13 -6
  59. package/src/cli/account-api.ts +65 -0
  60. package/src/cli/account-auth.ts +26 -2
  61. package/src/cli/account.ts +41 -5
  62. package/src/cli/advisor.ts +151 -0
  63. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  64. package/src/cli/capabilities-access-remote.ts +8 -0
  65. package/src/cli/capabilities-agents-routing.ts +18 -0
  66. package/src/cli/capabilities-base.ts +14 -2
  67. package/src/cli/catalog-command-result.ts +2 -1
  68. package/src/cli/chatgpt-command.ts +35 -14
  69. package/src/cli/claude.ts +60 -11
  70. package/src/cli/config-command.ts +8 -2
  71. package/src/cli/debug.ts +8 -0
  72. package/src/cli/dispatch.ts +21 -4
  73. package/src/cli/doctor.ts +9 -0
  74. package/src/cli/ensure-desktop-cold-start.ts +329 -0
  75. package/src/cli/export-command.ts +15 -2
  76. package/src/cli/help.ts +2 -1
  77. package/src/cli/hub-disable.ts +99 -0
  78. package/src/cli/hub.ts +4 -2
  79. package/src/cli/index.ts +26 -8
  80. package/src/cli/integration-plan-dto.ts +2 -0
  81. package/src/cli/link-child.ts +104 -0
  82. package/src/cli/link-enrollment.ts +96 -0
  83. package/src/cli/link.ts +39 -24
  84. package/src/cli/observe-stream.ts +6 -1
  85. package/src/cli/observe.ts +10 -2
  86. package/src/cli/provider.ts +12 -0
  87. package/src/cli/registry.ts +20 -7
  88. package/src/cli/restart-scope.ts +3 -1
  89. package/src/cli/runtime-api.ts +27 -2
  90. package/src/cli/status.ts +104 -20
  91. package/src/client/connect.ts +39 -4
  92. package/src/client/hub-client.ts +14 -7
  93. package/src/client/hub-state.ts +25 -14
  94. package/src/client/link-home.ts +248 -0
  95. package/src/client/link-management-relay.ts +61 -0
  96. package/src/client/link-relay.ts +6 -1
  97. package/src/client/link-status.ts +14 -0
  98. package/src/client/machine-api.ts +14 -7
  99. package/src/client/machine-listener.ts +29 -4
  100. package/src/clients/config-export/contracts.ts +1 -0
  101. package/src/clients/config-export/model-metadata.ts +3 -2
  102. package/src/clients/config-export/qoder.ts +93 -0
  103. package/src/clients/config-export.ts +59 -16
  104. package/src/clients/omo-role-models.ts +6 -3
  105. package/src/codex/agent-role-models.ts +26 -8
  106. package/src/codex/auth-api/account-list.ts +17 -2
  107. package/src/codex/auth-api/account-selection.ts +17 -7
  108. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  109. package/src/codex/auth-api/routes.ts +5 -1
  110. package/src/codex/auth-context.ts +79 -5
  111. package/src/codex/autostart-health.ts +7 -1
  112. package/src/codex/catalog/build-entries.ts +11 -2
  113. package/src/codex/catalog/bundled.ts +5 -1
  114. package/src/codex/catalog/configured-tiers.ts +50 -0
  115. package/src/codex/catalog/effort.ts +6 -3
  116. package/src/codex/catalog/metadata.ts +4 -2
  117. package/src/codex/catalog/model-hints.ts +21 -3
  118. package/src/codex/catalog/native-models.ts +16 -2
  119. package/src/codex/catalog/native-ultrafast.ts +106 -0
  120. package/src/codex/catalog/parsing.ts +5 -0
  121. package/src/codex/catalog/provider-models.ts +4 -0
  122. package/src/codex/catalog/retained-sync.ts +2 -0
  123. package/src/codex/catalog/routed-gather.ts +22 -4
  124. package/src/codex/catalog/vendor-context.ts +82 -0
  125. package/src/codex/config-write-lock.ts +40 -2
  126. package/src/codex/convergence.ts +2 -0
  127. package/src/codex/desktop-app/types.ts +2 -0
  128. package/src/codex/desktop-app/windows.ts +29 -16
  129. package/src/codex/desktop-app-restart.ts +4 -2
  130. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  131. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  132. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  133. package/src/codex/desktop-compatibility/installed-build.ts +55 -0
  134. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  135. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  136. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  137. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  138. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  139. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  140. package/src/codex/desktop-compatibility/runtime.ts +173 -0
  141. package/src/codex/desktop-compatibility/service.ts +30 -0
  142. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  143. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  144. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  145. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  146. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  147. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  148. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  149. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  150. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  151. package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
  152. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  153. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  154. package/src/codex/history-provider.ts +1 -1
  155. package/src/codex/inject/config-write-section.ts +4 -1
  156. package/src/codex/inject/remove.ts +18 -6
  157. package/src/codex/journal.ts +16 -1
  158. package/src/codex/main-account.ts +99 -15
  159. package/src/codex/native-main-refresh-mode.ts +217 -0
  160. package/src/codex/native-profile-startup.ts +4 -1
  161. package/src/codex/prompt-journal.ts +20 -2
  162. package/src/codex/prompt-layers/encoding.ts +49 -0
  163. package/src/codex/prompt-layers/import-source.ts +30 -0
  164. package/src/codex/prompt-layers/toml-edit.ts +269 -37
  165. package/src/codex/prompt-layers/toml-read.ts +52 -4
  166. package/src/codex/prompt-layers.ts +383 -20
  167. package/src/codex/prompt-lock-claim.ts +13 -3
  168. package/src/codex/prompt-lock-evidence.ts +3 -3
  169. package/src/codex/prompt-lock-owner.ts +14 -6
  170. package/src/codex/prompt-text-probe.ts +34 -5
  171. package/src/codex/quota.ts +13 -6
  172. package/src/codex/role-auto-assign.ts +2 -5
  173. package/src/codex/routing/active-account.ts +21 -0
  174. package/src/codex/routing/health-store.ts +1 -1
  175. package/src/codex/routing/pool-recovery.ts +79 -0
  176. package/src/codex/routing.ts +1 -1
  177. package/src/codex/runtime.ts +196 -8
  178. package/src/codex/shim-diagnostics.ts +6 -0
  179. package/src/codex/shim-overlay.ts +15 -6
  180. package/src/codex/shim.ts +1 -1
  181. package/src/combos/failover.ts +3 -0
  182. package/src/config/api-key-reconcile.ts +34 -0
  183. package/src/config/diagnostics.ts +18 -1
  184. package/src/config/feature-flags.ts +5 -0
  185. package/src/config/live-reconcile.ts +56 -29
  186. package/src/config/live-topology.ts +29 -0
  187. package/src/config/load-degrade.ts +8 -2
  188. package/src/config/paths.ts +13 -3
  189. package/src/config/salvage.ts +3 -1
  190. package/src/config/schema/config-schema.ts +19 -0
  191. package/src/config/schema/desktop-compatibility.ts +9 -0
  192. package/src/config/schema/leaf-validators.ts +1 -0
  193. package/src/generated/compatibility-version.json +713 -285
  194. package/src/generated/model-metadata.ts +1 -1
  195. package/src/grok/grpc-web.ts +23 -1
  196. package/src/grok/reset-coupon-ledger.ts +75 -5
  197. package/src/grok/reset-coupons.ts +4 -2
  198. package/src/integrations/catalog-refresh.ts +1 -1
  199. package/src/integrations/config-io.ts +20 -2
  200. package/src/integrations/journal.ts +15 -1
  201. package/src/integrations/mutation-plan.ts +14 -7
  202. package/src/integrations/registry.ts +52 -11
  203. package/src/integrations/state.ts +19 -7
  204. package/src/integrations/store.ts +3 -0
  205. package/src/integrations/target.ts +42 -5
  206. package/src/integrations/writer.ts +24 -7
  207. package/src/lib/advisor-activation.ts +16 -0
  208. package/src/lib/app-owned-memory-stores.ts +4 -0
  209. package/src/lib/app-owned-memory.ts +11 -0
  210. package/src/lib/background-work.ts +65 -0
  211. package/src/lib/bounded-file-read.ts +58 -0
  212. package/src/lib/bun-path-runtime.d.mts +10 -1
  213. package/src/lib/bun-path-runtime.mjs +62 -3
  214. package/src/lib/desktop-proxy-route.ts +30 -0
  215. package/src/lib/desktop-upstream-tunnel.ts +107 -0
  216. package/src/lib/errors.ts +7 -0
  217. package/src/lib/json-byte-size.ts +31 -0
  218. package/src/lib/local-internal-call-capability.ts +55 -0
  219. package/src/lib/optional-shutdown-hooks.ts +35 -0
  220. package/src/lib/outbound-credential-registry.ts +210 -0
  221. package/src/lib/package-tree-integrity.ts +2 -2
  222. package/src/lib/passive-read.ts +35 -0
  223. package/src/lib/shadow-call.ts +44 -5
  224. package/src/lib/socks5-fetch.ts +48 -71
  225. package/src/lib/socks5-handshake.ts +112 -0
  226. package/src/lib/translator-budget.ts +7 -1
  227. package/src/link/codex-settings.ts +155 -0
  228. package/src/link/management-routes.ts +89 -0
  229. package/src/link/remote-probe.ts +38 -0
  230. package/src/link/ssh-argv.ts +26 -5
  231. package/src/link/ssh-runner.ts +58 -27
  232. package/src/link/status-projection.ts +22 -4
  233. package/src/link/supervisor.ts +144 -49
  234. package/src/link/tunnel-state.ts +11 -4
  235. package/src/oauth/anthropic-continuity.ts +4 -3
  236. package/src/oauth/anthropic-identity.ts +13 -2
  237. package/src/oauth/anthropic-model-quota.ts +13 -1
  238. package/src/oauth/anthropic-routing.ts +230 -23
  239. package/src/oauth/anthropic.ts +18 -4
  240. package/src/oauth/generic-account-failover.ts +29 -5
  241. package/src/oauth/google-antigravity.ts +24 -7
  242. package/src/oauth/index.ts +28 -17
  243. package/src/oauth/kiro-account-identity.ts +106 -0
  244. package/src/oauth/kiro-device-login.ts +2 -1
  245. package/src/oauth/kiro.ts +55 -6
  246. package/src/oauth/plan.ts +29 -0
  247. package/src/oauth/store.ts +40 -3
  248. package/src/oauth/types.ts +2 -0
  249. package/src/plugins/loader.ts +11 -0
  250. package/src/protocols/codecs/chat-ir.ts +467 -0
  251. package/src/protocols/codecs/chat.ts +10 -1
  252. package/src/protocols/codecs/messages-ir.ts +500 -0
  253. package/src/protocols/features.ts +10 -2
  254. package/src/protocols/path.ts +11 -1
  255. package/src/protocols/plan-snapshot.ts +39 -16
  256. package/src/protocols/plan.ts +9 -1
  257. package/src/protocols/settings.ts +8 -0
  258. package/src/providers/anthropic-billing-ownership.ts +74 -0
  259. package/src/providers/anthropic-billing-recovery.ts +31 -0
  260. package/src/providers/anthropic-billing-refusal.ts +55 -0
  261. package/src/providers/anthropic-reset-admission.ts +62 -0
  262. package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
  263. package/src/providers/antigravity-account-roster.ts +102 -0
  264. package/src/providers/antigravity-models.ts +85 -7
  265. package/src/providers/api-key-resolve.ts +17 -2
  266. package/src/providers/key-failover.ts +52 -5
  267. package/src/providers/model-rename-fields.ts +2 -0
  268. package/src/providers/model-rename-migration.ts +5 -3
  269. package/src/providers/model-rename-startup.ts +5 -3
  270. package/src/providers/quota/account-cache.ts +11 -0
  271. package/src/providers/quota/anthropic-account-quota.ts +4 -2
  272. package/src/providers/quota/report-cache.ts +14 -0
  273. package/src/providers/quota/vendor-probes-oauth.ts +3 -3
  274. package/src/providers/quota.ts +4 -1
  275. package/src/providers/registry/entries-core.ts +21 -20
  276. package/src/providers/registry/entries-extended.ts +19 -1
  277. package/src/providers/registry/model-seeds.ts +17 -0
  278. package/src/providers/request-pacing.ts +9 -0
  279. package/src/providers/stale-no-vision-seed-migration.ts +72 -0
  280. package/src/responses/compaction.ts +14 -0
  281. package/src/responses/parser.ts +37 -20
  282. package/src/responses/state.ts +32 -29
  283. package/src/routing/history/build-file.ts +92 -0
  284. package/src/routing/history/indexer.ts +248 -216
  285. package/src/routing/history/ingest.ts +238 -0
  286. package/src/routing/history/rebuild-job.ts +111 -0
  287. package/src/routing/history/rebuild-protocol.ts +15 -0
  288. package/src/routing/history/rebuild-worker.ts +34 -0
  289. package/src/server/admission-model-scope.ts +4 -1
  290. package/src/server/antigravity-direct-eligibility.ts +208 -0
  291. package/src/server/auth-cors.ts +46 -0
  292. package/src/server/chat-completions.ts +75 -9
  293. package/src/server/claude-messages.ts +110 -20
  294. package/src/server/configured-credentials.ts +194 -0
  295. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  296. package/src/server/index/link-management.ts +58 -0
  297. package/src/server/index/optional-listeners.ts +5 -1
  298. package/src/server/index/serve-options.ts +15 -2
  299. package/src/server/index/startup-warnings.ts +9 -0
  300. package/src/server/index/websocket-handler.ts +9 -0
  301. package/src/server/index.ts +13 -10
  302. package/src/server/inference/client-encoder-delivery.ts +6 -5
  303. package/src/server/lifecycle.ts +2 -1
  304. package/src/server/login-shell-path.ts +146 -0
  305. package/src/server/management/advisor-routes.ts +191 -0
  306. package/src/server/management/agent-settings-routes.ts +50 -14
  307. package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
  308. package/src/server/management/codex-agent-role-routes.ts +19 -6
  309. package/src/server/management/codex-prompt-routes.ts +136 -5
  310. package/src/server/management/codex-role-auto-assign.ts +1 -1
  311. package/src/server/management/companion-routes.ts +4 -0
  312. package/src/server/management/config-routes.ts +2 -2
  313. package/src/server/management/context.ts +4 -0
  314. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  315. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  316. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  317. package/src/server/management/grok-coupon-routes.ts +78 -27
  318. package/src/server/management/link-enrollment.ts +192 -0
  319. package/src/server/management/link-route-state.ts +185 -0
  320. package/src/server/management/link-routes.ts +82 -402
  321. package/src/server/management/model-routes.ts +2 -2
  322. package/src/server/management/model-rows.ts +3 -1
  323. package/src/server/management/protocol-settings-patch.ts +1 -0
  324. package/src/server/management/provider-overwrite-carry.ts +10 -0
  325. package/src/server/management/provider-routes.ts +1 -0
  326. package/src/server/management/route-registry.ts +16 -3
  327. package/src/server/management/shared.ts +3 -0
  328. package/src/server/management/sibling-guard.ts +1 -0
  329. package/src/server/management-api.ts +9 -1
  330. package/src/server/messages-antigravity-direct.ts +195 -0
  331. package/src/server/messages-native.ts +73 -43
  332. package/src/server/relay-eager.ts +2 -0
  333. package/src/server/request-log-filter.ts +6 -1
  334. package/src/server/request-log-terminal-status.ts +63 -6
  335. package/src/server/request-log.ts +7 -0
  336. package/src/server/responses/adapter-continuation.ts +35 -18
  337. package/src/server/responses/adapter-delivery.ts +24 -2
  338. package/src/server/responses/adapter-dispatch.ts +261 -81
  339. package/src/server/responses/advisor-plan-slot.ts +27 -0
  340. package/src/server/responses/advisor-slot.ts +336 -0
  341. package/src/server/responses/code-mode-collab-surface.ts +313 -0
  342. package/src/server/responses/codex-ws-exchange.ts +147 -6
  343. package/src/server/responses/codex-ws-wire.ts +203 -0
  344. package/src/server/responses/collaboration.ts +36 -7
  345. package/src/server/responses/compact.ts +126 -48
  346. package/src/server/responses/context-overflow.ts +13 -1
  347. package/src/server/responses/core-codex-account.ts +105 -1
  348. package/src/server/responses/core-combo-failure.ts +24 -1
  349. package/src/server/responses/core-combo.ts +28 -2
  350. package/src/server/responses/core-errors.ts +29 -1
  351. package/src/server/responses/core-lifetime.ts +2 -0
  352. package/src/server/responses/core-normalize.ts +11 -0
  353. package/src/server/responses/core-options.ts +48 -0
  354. package/src/server/responses/core-replay.ts +7 -4
  355. package/src/server/responses/core.ts +1 -1
  356. package/src/server/responses/deferred-collab-surface.ts +158 -0
  357. package/src/server/responses/deferred-send-reporter.ts +25 -0
  358. package/src/server/responses/fetch-helpers.ts +12 -1
  359. package/src/server/responses/passthrough-delivery.ts +20 -11
  360. package/src/server/responses/passthrough-dispatch.ts +29 -7
  361. package/src/server/responses/request-prepare.ts +74 -9
  362. package/src/server/responses/request-transport.ts +22 -8
  363. package/src/server/responses/run-turn-execution.ts +70 -8
  364. package/src/server/responses/sidecar-execution.ts +89 -5
  365. package/src/server/responses/terminal-guard.ts +2 -1
  366. package/src/server/responses/ws-upstream.ts +9 -1
  367. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  368. package/src/server/startup-action-control.ts +38 -4
  369. package/src/server/startup-health-cache.ts +5 -0
  370. package/src/server/system-env-shell.ts +7 -2
  371. package/src/server/system-env.ts +11 -1
  372. package/src/service/cli.ts +5 -1
  373. package/src/service/desktop-startup.ts +161 -34
  374. package/src/service/desktop-supervision.d.mts +27 -1
  375. package/src/service/desktop-supervision.mjs +111 -5
  376. package/src/service/health-sentinel-launch.ts +176 -0
  377. package/src/service/health-sentinel-main.ts +466 -0
  378. package/src/service/health-sentinel-policy.ts +241 -0
  379. package/src/service/health-sentinel-probe.ts +71 -0
  380. package/src/service/health-sentinel-windows.ts +87 -0
  381. package/src/service/linux-linger.ts +20 -0
  382. package/src/service/logging.ts +72 -0
  383. package/src/tray/windows-tray.ps1 +43 -24
  384. package/src/types/config.ts +68 -6
  385. package/src/types/provider.ts +7 -0
  386. package/src/types/request.ts +15 -0
  387. package/src/types/tools.ts +2 -0
  388. package/src/update/async-check.ts +1 -1
  389. package/src/update/badge.ts +2 -2
  390. package/src/update/index.ts +88 -15
  391. package/src/update/job-supervised-restart.ts +107 -0
  392. package/src/update/job.ts +32 -32
  393. package/src/update/notify.ts +1 -1
  394. package/src/update/refresh-scheduler.ts +2 -2
  395. package/src/update/supervised-recovery.d.mts +27 -0
  396. package/src/update/supervised-recovery.mjs +157 -0
  397. package/src/usage/cache-diagnostic.ts +2 -0
  398. package/src/usage/expected-prices.ts +16 -0
  399. package/src/usage/log.ts +19 -1
  400. package/src/web-search/gemini-executor.ts +2 -2
  401. package/assets/sponsors/packycode.png +0 -0
  402. package/gui/dist/assets/App-Ba8uG696.js +0 -54
  403. package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
  404. package/gui/dist/assets/index-BHRZdJuv.js +0 -94
  405. package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
@@ -22,6 +22,8 @@ import type { RequestLogContext } from "../request-log";
22
22
  import type { UpstreamHostAdmissionLease } from "../../codex/upstream-host-health";
23
23
  import type { AccountLease } from "../../oauth/kiro-account-load";
24
24
  import type { PolicyRequestScope } from "./policy-request-scope";
25
+ import type { OcxParsedRequest } from "../../types";
26
+ import type { PolicyRequestEvidence } from "../../routing/evaluator";
25
27
 
26
28
  export interface ConsumedComboFailure {
27
29
  response: Response;
@@ -30,6 +32,8 @@ export interface ConsumedComboFailure {
30
32
  codexModelRefusal?: CodexAccountModelRefusal;
31
33
  /** Structured upstream `error.code` when present in the failure body. */
32
34
  upstreamCode?: string;
35
+ /** Sanitized structured `error.message`; compared only against proxy-owned constants, never emitted. */
36
+ upstreamMessage?: string;
33
37
  /** Complete structured provider type, retained for conservative recovery classification. */
34
38
  upstreamType?: string;
35
39
  /** Valid numeric/date value used only for cooldown calculation. */
@@ -59,6 +63,32 @@ export interface ClientEncoderOption {
59
63
  stream: boolean;
60
64
  model: string;
61
65
  inputTokenFloor?: number;
66
+ /**
67
+ * `direct` when the request side also skipped the internal Responses body (the Antigravity
68
+ * direct lane), so the attempt's request path is traced as `[inbound, ir, upstream]`.
69
+ */
70
+ requestLane?: "bridge" | "direct";
71
+ }
72
+
73
+ /**
74
+ * The Antigravity direct lane (`protocols.rollout.antigravityDirect`). The ingress sends the core
75
+ * a Responses body that carries only the request settings; the conversation is decoded straight
76
+ * from the client body. Request preparation applies `decode` at the parse seam, reads routing
77
+ * evidence from `evidence`, and calls `restoreBridgeConversation` if the final route is not the
78
+ * one the lane was decided for, so every `_rawBody` reader of another adapter sees the full
79
+ * bridge body.
80
+ */
81
+ export interface DirectRequestOption {
82
+ protocol: "chat" | "messages";
83
+ /** The concrete route the ingress decided the lane for. */
84
+ providerName: string;
85
+ modelId: string;
86
+ /** Policy-routing evidence from the client body; the settings-only body has no `input`. */
87
+ evidence: PolicyRequestEvidence;
88
+ /** The settings-only parse with the directly decoded conversation in place of its empty one. */
89
+ decode(base: OcxParsedRequest): OcxParsedRequest;
90
+ /** The bridge projection's conversation fields (`input`, `instructions`, `tools`). */
91
+ bridgeConversation(): Record<string, unknown>;
62
92
  }
63
93
 
64
94
  export interface HandleResponsesOptions {
@@ -135,6 +165,13 @@ export interface HandleResponsesOptions {
135
165
  * it. Omitted means a genuine Responses inbound.
136
166
  */
137
167
  inboundWire?: InboundWire;
168
+ /**
169
+ * Set by a translating ingress (Chat / Anthropic) when the client sent an explicit empty
170
+ * tool catalog. Those wires may defer tool delivery, so their translated body carries no
171
+ * readable catalog boundary; an explicit `[]` is still a deny-all declaration and the
172
+ * undeclared-tool guard enforces it. Absent catalogs and partial catalogs stay deferred.
173
+ */
174
+ clientDeclaredEmptyToolCatalog?: boolean;
138
175
  nativeReasoningReplay?: ReadonlyMap<string, string>;
139
176
  nativeReasoningMint?: { owner?: NativeReasoningOwner };
140
177
  /** Droid's per-request effort default; each concrete combo or policy target applies it only if its ladder allows it. */
@@ -200,6 +237,15 @@ export interface HandleResponsesOptions {
200
237
  * rebuilds headers and carries the fact through this flag.
201
238
  */
202
239
  visionDescribeTerminal?: boolean;
240
+ /**
241
+ * Terminal advisor marker: true when the inbound request IS the advisor sidecar's own loopback
242
+ * consultation. The advisor planner then never plans a consultation — a depth cap of 1 that
243
+ * keeps Worker → Advisor → Advisor recursion impossible (same structure as
244
+ * {@linkcode visionDescribeTerminal}). The Chat surface detects the raw
245
+ * `x-opencodex-advisor-internal` header before its bridge rebuilds headers and carries the
246
+ * fact through this flag.
247
+ */
248
+ advisorInternal?: boolean;
203
249
  /**
204
250
  * Set only by the Chat and Messages ingresses when `protocols.rollout.directEncoders` is on and
205
251
  * the settled route is one non-Responses target. Adapter delivery then encodes the adapter
@@ -208,6 +254,8 @@ export interface HandleResponsesOptions {
208
254
  * returning a Responses body, which the ingress converts as before.
209
255
  */
210
256
  clientEncoder?: ClientEncoderOption;
257
+ /** Set only by the Chat or Messages ingress on an eligible Antigravity route; see {@link DirectRequestOption}. */
258
+ directRequest?: DirectRequestOption;
211
259
  }
212
260
 
213
261
  /** Values shared by the call, not a bag of mutable pipeline state. */
@@ -125,11 +125,13 @@ export function bindRouteReasoningReplayScope(args: {
125
125
  if (owner) parsed._nativeReasoningBoundOwner = owner;
126
126
  else delete parsed._nativeReasoningBoundOwner;
127
127
  if (parsed._nativeReasoningReplay?.size) {
128
+ const rejected = new Set<string>();
128
129
  for (const [blob, tag] of parsed._nativeReasoningReplay) {
129
130
  if (!nativeReasoningTagMatches(nativeReasoningTag(owner, blob), tag)) {
130
- stripNativeReasoningBlob(parsed, blob);
131
+ rejected.add(blob);
131
132
  }
132
133
  }
134
+ if (rejected.size) stripNativeReasoningBlobs(parsed, rejected);
133
135
  }
134
136
  const continuationDestinationIdentity = providerContinuationDestinationIdentity(parsed, provider);
135
137
  const continuationOwner = providerContinuationOwnerFromReplayIdentity(
@@ -169,11 +171,11 @@ export function bindRouteReasoningReplayScope(args: {
169
171
  bindProviderContinuationForRoute(parsed, continuationOwner);
170
172
  }
171
173
 
172
- function stripNativeReasoningBlob(parsed: OcxParsedRequest, blob: string): void {
174
+ function stripNativeReasoningBlobs(parsed: OcxParsedRequest, rejected: ReadonlySet<string>): void {
173
175
  const input = (parsed._rawBody as { input?: unknown } | undefined)?.input;
174
176
  if (Array.isArray(input)) {
175
177
  for (const item of input) {
176
- if (item && typeof item === "object" && item.type === "reasoning" && item.encrypted_content === blob) {
178
+ if (item && typeof item === "object" && item.type === "reasoning" && rejected.has(item.encrypted_content)) {
177
179
  delete item.encrypted_content;
178
180
  delete item.id;
179
181
  }
@@ -187,7 +189,8 @@ function stripNativeReasoningBlob(parsed: OcxParsedRequest, blob: string): void
187
189
  const item: unknown = JSON.parse(part.signature);
188
190
  if (!item || typeof item !== "object" || Array.isArray(item)) continue;
189
191
  const reasoning = item as Record<string, unknown>;
190
- if (reasoning.type !== "reasoning" || reasoning.encrypted_content !== blob) continue;
192
+ if (reasoning.type !== "reasoning" || typeof reasoning.encrypted_content !== "string"
193
+ || !rejected.has(reasoning.encrypted_content)) continue;
191
194
  delete reasoning.encrypted_content;
192
195
  delete reasoning.id;
193
196
  part.signature = JSON.stringify(reasoning);
@@ -48,7 +48,7 @@ export async function handleResponses(
48
48
  abortSignal.addEventListener("abort", cancel, { once: true });
49
49
  try {
50
50
  const response = await runWithCompactionRecovery(req, config, logCtx, {
51
- ...options,
51
+ ...options, abortSignal,
52
52
  openAiSidecarAuth: options.openAiSidecarAuth === undefined
53
53
  ? captureExplicitOpenAiCallerAuth(req.headers, config) : options.openAiSidecarAuth,
54
54
  nativeCallerAuth: options.nativeCallerAuth === undefined
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Deferred V1 collaboration evidence from Codex's `<tools>` world-state fragment (#6844).
3
+ *
4
+ * When the model supports tool search (every OpenCodex catalog row), codex-rs registers the V1
5
+ * collaboration tools as DEFERRED (core/src/tools/spec_plan.rs, add_collaboration_tools) under the
6
+ * fixed namespace `multi_agent_v1` (core/src/tools/handlers/multi_agents_spec.rs). Neither the wire
7
+ * tools nor the code-mode exec description then mention them. With the deferred_tool_world_state
8
+ * feature on (default off upstream; Codex Desktop sessions were observed receiving it), codex-rs
9
+ * instead tells the model which namespaces it can load, in a developer fragment
10
+ * (core/src/context/world_state/tools.rs):
11
+ *
12
+ * <tools>
13
+ * Deferred tool namespaces:
14
+ * - multi_agent_v1: Tools for spawning and managing sub-agents.
15
+ * </tools>
16
+ *
17
+ * Later turns carry only diffs ("Added deferred tool namespaces:", "Removed deferred tool
18
+ * namespaces:", "No deferred tool namespaces remain."), so membership is replayed in input order.
19
+ * The fragment is capped at 4 KiB: rows may be name-only, and whole rows may be replaced by
20
+ * "... <n> additional namespaces omitted." (tools_budget.rs).
21
+ *
22
+ * The evidence is positive-only and conservative:
23
+ * - only developer messages are read, each text part on its own, and only a part that is exactly
24
+ * one fragment after trimming; text that merely mentions the tag is ignored;
25
+ * - a fragment that starts like one but breaks the line grammar clears what was carried, because a
26
+ * lost removal could have been multi_agent_v1; omitted removals clear for the same reason, and
27
+ * omitted rows never establish a namespace;
28
+ * - it counts only beside a host that can load deferred tools: a genuine code-mode exec or a
29
+ * structured tool_search tool. A caller's own exec or a function merely named tool_search is not
30
+ * one.
31
+ *
32
+ * Capability boundary: the row proves that some deferred multi_agent_v1 tool is registered, not
33
+ * that spawn_agent is. Upstream can split them only through an embedder-supplied ToolPolicy
34
+ * allowlist; its one production allowlist (the guardian reviewer) admits no V1 tool at all, and
35
+ * the V1 depth limit disables the surface as a unit, so Codex CLI and Desktop never send a
36
+ * management-only V1 namespace. A developer item can also be impersonated by whoever writes the
37
+ * conversation. Both cases receive advisory guidance they cannot act on; nothing is enforced here.
38
+ */
39
+ import type { OcxMessage, OcxParsedRequest, OcxTool } from "../../types";
40
+ import type { CodeModeExecAnalysis } from "./code-mode-collab-surface";
41
+
42
+ const MULTI_AGENT_V1_NAMESPACE = "multi_agent_v1";
43
+ const OPEN_TAG = "<tools>";
44
+ const CLOSE_TAG = "</tools>";
45
+ const SNAPSHOT_HEADER = "Deferred tool namespaces:";
46
+ const ADDED_HEADER = "Added deferred tool namespaces:";
47
+ const REMOVED_HEADER = "Removed deferred tool namespaces:";
48
+ const EMPTY_STATE = "No deferred tool namespaces remain.";
49
+ // A description is opaque: upstream copies its first line verbatim, which may hold any character
50
+ // except \n, including U+2028/U+2029 that `.` would refuse.
51
+ const ROW = /^- ([^:\s]+)(?:: [\s\S]*)?$/;
52
+ const OMISSION = /^\.\.\. \d+ additional namespaces omitted\.$/;
53
+
54
+ type Fragment =
55
+ | { kind: "malformed" }
56
+ | { kind: "snapshot"; rows: Set<string>; empty: boolean }
57
+ | { kind: "diff"; added: Set<string>; removed: Set<string>; removedOmitted: boolean; empty: boolean };
58
+
59
+ /** One standalone fragment, a malformed one, or nothing (text that is not a fragment). */
60
+ function parseFragment(text: string): Fragment | null {
61
+ const trimmed = text.trim();
62
+ if (!trimmed.startsWith(OPEN_TAG)) return null;
63
+ if (!trimmed.endsWith(CLOSE_TAG) || trimmed.length < OPEN_TAG.length + CLOSE_TAG.length) return { kind: "malformed" };
64
+ const body = trimmed.slice(OPEN_TAG.length, trimmed.length - CLOSE_TAG.length);
65
+ let group: "snapshot" | "added" | "removed" | undefined;
66
+ // Upstream renders each group at most once, Added before Removed, and skips empty groups.
67
+ const seen = new Set<"snapshot" | "added" | "removed">();
68
+ let groupHasEntries = true;
69
+ let empty = false;
70
+ const rows = { snapshot: new Set<string>(), added: new Set<string>(), removed: new Set<string>() };
71
+ let removedOmitted = false;
72
+ const open = (next: "snapshot" | "added" | "removed"): boolean => {
73
+ if (seen.has(next) || !groupHasEntries) return false;
74
+ if (next === "added" && seen.has("removed")) return false;
75
+ seen.add(next);
76
+ group = next;
77
+ groupHasEntries = false;
78
+ return true;
79
+ };
80
+ for (const raw of body.split("\n")) {
81
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
82
+ if (line === "") continue;
83
+ // The empty-state notice closes the fragment; nothing may follow it.
84
+ if (empty) return { kind: "malformed" };
85
+ if (line === SNAPSHOT_HEADER) { if (!open("snapshot")) return { kind: "malformed" }; continue; }
86
+ if (line === ADDED_HEADER) { if (!open("added")) return { kind: "malformed" }; continue; }
87
+ if (line === REMOVED_HEADER) { if (!open("removed")) return { kind: "malformed" }; continue; }
88
+ if (line === EMPTY_STATE) {
89
+ if (!groupHasEntries) return { kind: "malformed" };
90
+ empty = true;
91
+ continue;
92
+ }
93
+ if (group === undefined) return { kind: "malformed" };
94
+ groupHasEntries = true;
95
+ if (OMISSION.test(line)) {
96
+ if (group === "removed") removedOmitted = true;
97
+ continue;
98
+ }
99
+ const row = ROW.exec(line);
100
+ if (!row) return { kind: "malformed" };
101
+ rows[group].add(row[1]!);
102
+ }
103
+ if (!groupHasEntries) return { kind: "malformed" };
104
+ const sawSnapshot = seen.has("snapshot");
105
+ const sawDiff = seen.has("added") || seen.has("removed");
106
+ if (sawSnapshot && sawDiff) return { kind: "malformed" };
107
+ if (sawSnapshot) return { kind: "snapshot", rows: rows.snapshot, empty };
108
+ if (!sawDiff && !empty) return { kind: "malformed" };
109
+ for (const name of rows.added) if (rows.removed.has(name)) return { kind: "malformed" };
110
+ return { kind: "diff", added: rows.added, removed: rows.removed, removedOmitted, empty };
111
+ }
112
+
113
+ function developerTexts(message: OcxMessage): string[] {
114
+ if (message.role !== "developer") return [];
115
+ if (typeof message.content === "string") return [message.content];
116
+ return message.content.flatMap(part => (part.type === "text" ? [part.text] : []));
117
+ }
118
+
119
+ /** Replays every fragment in input order; true when multi_agent_v1 is currently known deferred. */
120
+ function deferredNamespacesIncludeV1(messages: readonly OcxMessage[]): boolean {
121
+ const current = new Set<string>();
122
+ for (const message of messages) {
123
+ for (const text of developerTexts(message)) {
124
+ const fragment = parseFragment(text);
125
+ if (fragment === null) continue;
126
+ switch (fragment.kind) {
127
+ case "malformed":
128
+ current.clear();
129
+ break;
130
+ case "snapshot":
131
+ current.clear();
132
+ for (const name of fragment.rows) current.add(name);
133
+ break;
134
+ case "diff":
135
+ if (fragment.removedOmitted) current.clear();
136
+ for (const name of fragment.added) current.add(name);
137
+ for (const name of fragment.removed) current.delete(name);
138
+ break;
139
+ }
140
+ if (fragment.kind !== "malformed" && fragment.empty) current.clear();
141
+ }
142
+ }
143
+ return current.has(MULTI_AGENT_V1_NAMESPACE);
144
+ }
145
+
146
+ function declaresStructuredToolSearch(tools: readonly OcxTool[] | undefined): boolean {
147
+ return (tools ?? []).some(tool => !tool.namespace && tool.toolSearch === true);
148
+ }
149
+
150
+ /**
151
+ * True when the request's own developer history says V1 collaboration tools are deferred and the
152
+ * request comes from a host that can load deferred tools.
153
+ */
154
+ export function deferredV1Evidence(parsed: OcxParsedRequest, codeMode: CodeModeExecAnalysis): boolean {
155
+ if (!codeMode.genuineExec && !declaresStructuredToolSearch(parsed.context.tools)) return false;
156
+ return deferredNamespacesIncludeV1(parsed.context.messages);
157
+ }
158
+
@@ -0,0 +1,25 @@
1
+ import type { PhysicalSendReporter } from "../../lib/request-execution-budget";
2
+
3
+ /** Retry thunks can fail in pacing; settle only starts admitted at the physical executor. */
4
+ export function deferPhysicalSendReporter(physical: PhysicalSendReporter) {
5
+ let dispatched = 0;
6
+ let reported = 0;
7
+ const report: PhysicalSendReporter = () => {
8
+ const count = dispatched - reported;
9
+ if (count <= 0) return;
10
+ physical(count);
11
+ reported += count;
12
+ };
13
+ // The retry helper owns attempts, while the physical callback owns spend admission.
14
+ report.beforeSend = () => true;
15
+ report.execute = physical.execute;
16
+ report.close = () => { report(0); physical.close?.(); };
17
+ return {
18
+ report,
19
+ beforeDispatch(): boolean {
20
+ if (physical.beforeSend && !physical.beforeSend()) return false;
21
+ dispatched++;
22
+ return true;
23
+ },
24
+ };
25
+ }
@@ -1,4 +1,5 @@
1
1
  import type { NativeResponseControl } from "./native-response-control";
2
+ import type { CodexWsPreOutputRefusalHold } from "./codex-ws-wire";
2
3
  import type { Server } from "bun";
3
4
  import {
4
5
  codexWsUpstreamFetch,
@@ -28,6 +29,7 @@ import {
28
29
  type ProviderEgressBinding,
29
30
  } from "../../lib/provider-egress";
30
31
  import { redactSecretString } from "../../lib/redact";
32
+ import { outboundCredentials } from "../../lib/outbound-credential-registry";
31
33
 
32
34
  export { withUpstreamHttpVersion };
33
35
 
@@ -205,6 +207,9 @@ export function sendWithConnectionPolicy(
205
207
  && /^https:\/\/chatgpt\.com\/backend-api\/codex\/responses(?:\/compact)?$/.test(target)
206
208
  && Buffer.byteLength(body, "utf8") >= 1024 * 1024;
207
209
  (init as Record<symbol, (() => void) | undefined> | undefined)?.[PHYSICAL_DISPATCH]?.();
210
+ // Final headers, after plugin rewrites: an upstream can only echo what it received here, so the
211
+ // request log refuses any diagnostic that repeats one of these values (see request-log-terminal-status).
212
+ outboundCredentials().remember(headers);
208
213
  return physicalFetch(input, {
209
214
  ...init,
210
215
  ...(largeCodexBody ? { body: Buffer.from(body, "utf8") } : {}),
@@ -240,6 +245,12 @@ export interface ProviderFetchOptions {
240
245
  beforeDispatch?: (headers: Headers) => void;
241
246
  /** One-shot receipt after final local HTTP admission, immediately before executor entry. */
242
247
  onPhysicalDispatch?: () => void;
248
+ /**
249
+ * Opt-in (#6969): let a Codex WebSocket send hold empty lifecycle frames so an authoritative
250
+ * upstream refusal before any output settles as a replayable gateway status. Only a caller that
251
+ * restores the projection afterwards (restoreCodexWsPreOutputRefusal) may set it.
252
+ */
253
+ preOutputRefusalHold?: CodexWsPreOutputRefusalHold;
243
254
  /** Revalidate/rebuild a queued request at its physical send boundary, after pacing. */
244
255
  dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch) => Promise<Response>;
245
256
  }
@@ -356,7 +367,7 @@ export function providerFetch(
356
367
  const fallback = ((url: Parameters<typeof globalThis.fetch>[0], fallbackInit?: RequestInit) =>
357
368
  httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit)) as typeof globalThis.fetch;
358
369
  return codexWsUpstreamFetch(input, init, fallback, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
359
- async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce);
370
+ async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce, options.preOutputRefusalHold);
360
371
  }
361
372
  return httpFetch(input, init);
362
373
  };
@@ -108,6 +108,7 @@ import { createResponsesFunctionToolRepairBlockRewrite } from "../responses-func
108
108
  import {
109
109
  createUndeclaredToolCallGuardBlockRewrite,
110
110
  currentTurnWireToolCatalogBody,
111
+ UNDECLARED_TOOL_CALL_ERROR_CODE,
111
112
  undeclaredToolCallNameInResponse,
112
113
  undeclaredToolCallMessage,
113
114
  normalizeDefaultNamespaceInJson,
@@ -408,6 +409,7 @@ export async function deliverPassthroughResponse(
408
409
  | "noteInspectedPayload"
409
410
  | "normalizeFunctionCompletionJson"
410
411
  | "localUpstream"
412
+ | "terminalOutcomeRecorded"
411
413
  >,
412
414
  ): Promise<Response> {
413
415
  const { route } = requestState;
@@ -436,8 +438,7 @@ export async function deliverPassthroughResponse(
436
438
  normalizeFunctionCompletionJson,
437
439
  } = nativeExchange;
438
440
  const { commitReasoningReplayServingRoute, recordTerminalOutcomes } = responseEffects;
439
- const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget, inboundWire } = requestState;
440
- const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";
441
+ const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget } = requestState;
441
442
  const { openAiSidecar } = sidecarState;
442
443
  const { requestBindings } = transportState;
443
444
 
@@ -486,7 +487,9 @@ export async function deliverPassthroughResponse(
486
487
  ? (status: ResponsesTerminalStatus, httpStatusOverride?: number): void => {
487
488
  if (terminalOutcomeRecorded) return;
488
489
  terminalOutcomeRecorded = true;
489
- recordTerminalOutcome(status, httpStatusOverride);
490
+ // A restored pre-output refusal whose account outcome dispatch already wrote (#6969) keeps
491
+ // every other terminal callback but must not write the same refusal a second time.
492
+ if (!nativeExchange.terminalOutcomeRecorded) recordTerminalOutcome(status, httpStatusOverride);
490
493
  }
491
494
  : undefined;
492
495
  const terminalBodyWillRecord = !!terminalRecorder && upstreamResponse.ok && isEventStream;
@@ -854,14 +857,17 @@ export async function deliverPassthroughResponse(
854
857
  : undefined,
855
858
  // Last: every rewrite above can still rename or reshape a call item, so the guard must
856
859
  // compare the names the client will actually receive against the declared catalog.
857
- nativeExchange.undeclaredToolGuardActive
860
+ // Deferred-catalog wires keep the rewrite for its normalization half only: a provider's
861
+ // `default.X` call still collapses to the declared `X`, but an undeclared name is the
862
+ // client's own deferred registry's business, not this proxy's verdict.
863
+ nativeExchange.undeclaredToolGuardActive || declaredWireToolNames.size > 0
858
864
  ? createUndeclaredToolCallGuardBlockRewrite(
859
865
  declaredWireToolNames,
860
866
  declaredNamelessClientCallTypes,
861
867
  providerExecutedCallTypes,
862
868
  declaredBareWireToolNames,
863
869
  recoverableBareCustomWireToolNames,
864
- enforceDeclaredToolNames,
870
+ { refuseUndeclared: nativeExchange.undeclaredToolGuardActive },
865
871
  )
866
872
  : undefined,
867
873
  grokUpstreamEchoEnabled
@@ -1319,10 +1325,7 @@ export async function deliverPassthroughResponse(
1319
1325
  if (grokUpstreamEchoEnabled) {
1320
1326
  clientJson = stripGrokUpstreamEnvelopeEchoFromResponsesJson(clientJson);
1321
1327
  }
1322
- // #1700: same inbound-wire refusal policy as the SSE relay above. Both the plain JSON answer and
1323
- // the reframed-SSE branch below are built from this body, so one check covers them. This
1324
- // runs BEFORE the continuation cache write below: a refused turn must not become state a
1325
- // later `previous_response_id` replay can expand from.
1328
+ // Both response formats use this body, before a refused turn can enter continuation state.
1326
1329
  if (nativeExchange.undeclaredToolGuardActive) {
1327
1330
  const undeclared = (() => {
1328
1331
  try {
@@ -1338,9 +1341,15 @@ export async function deliverPassthroughResponse(
1338
1341
  return undefined;
1339
1342
  }
1340
1343
  })();
1341
- if (enforceDeclaredToolNames && undeclared !== undefined) {
1342
- return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared));
1344
+ if (undeclared !== undefined) {
1345
+ return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared), {
1346
+ code: UNDECLARED_TOOL_CALL_ERROR_CODE,
1347
+ });
1343
1348
  }
1349
+ }
1350
+ // Same split as the SSE rewrite: normalization applies whenever a declared name exists,
1351
+ // even on wires whose deferred catalog stands the refusal down.
1352
+ if (declaredWireToolNames.size > 0) {
1344
1353
  clientJson = normalizeDefaultNamespaceInJson(
1345
1354
  clientJson,
1346
1355
  declaredWireToolNames,
@@ -21,6 +21,7 @@ import { imageGenToolCallAliases } from "../responses-image-gen-repair";
21
21
  import { rememberResponseState, isBodyNonPersistable } from "../../responses/state";
22
22
  import {
23
23
  currentTurnWireToolCatalogBody,
24
+ hasExplicitEmptyToolCatalog,
24
25
  hasExplicitWireToolCatalog,
25
26
  collectDeclaredWireToolNames,
26
27
  collectDeclaredBareWireToolNames,
@@ -91,7 +92,7 @@ import {
91
92
  safeHostLabel,
92
93
  } from "./fetch-helpers";
93
94
  import { classifyPoolRecoveryDispatch } from "../../routing/probe-lease";
94
- import { clientCancelledResponse } from "./core-errors";
95
+ import { anthropicPoolCooldownResponse, clientCancelledResponse } from "./core-errors";
95
96
  import {
96
97
  upstreamHostCircuitOpenResponse,
97
98
  usesCodexForwardPoolAuth,
@@ -101,6 +102,8 @@ import {
101
102
  shouldRetryCodexPoolAccountQuota,
102
103
  shouldRetryCodexPoolAccountTransient,
103
104
  retryCodexPoolOnAlternateAccount,
105
+ PoolPreOutputRefusals,
106
+ poolPreOutputRefusalHold,
104
107
  } from "./core-codex-account";
105
108
  import {
106
109
  clearCodexModelDenialEvidence,
@@ -185,6 +188,7 @@ export async function preparePassthroughExchange(
185
188
  | "selectedForwardHeaders"
186
189
  | "clientRequestedStream"
187
190
  | "inboundWire"
191
+ | "clientDeclaredEmptyToolCatalog"
188
192
  | "substituteMainCredential"
189
193
  | "callerAuthHeaders"
190
194
  | "subagentFallbackAccountId"
@@ -251,6 +255,7 @@ export async function preparePassthroughExchange(
251
255
  responseStateOptions,
252
256
  clientRequestedStream,
253
257
  inboundWire,
258
+ clientDeclaredEmptyToolCatalog,
254
259
  substituteMainCredential,
255
260
  callerAuthHeaders,
256
261
  } = requestState;
@@ -529,10 +534,18 @@ export async function preparePassthroughExchange(
529
534
  declaredWireToolNames.add(name);
530
535
  }
531
536
  }
537
+ // Chat and Anthropic inbounds may deliver tools deferred/partially: their client registry
538
+ // owns the full catalog, so this guard cannot read the translated body as the authorization
539
+ // boundary. An explicit empty catalog — flagged by the translating ingress or present
540
+ // verbatim in a replayed body — is still a deny-all the caller declared; everything else
541
+ // stays deferred to the caller's own tool handling.
532
542
  undeclaredToolGuardActive = (
533
- declaredWireToolNames.size > 0
534
- || clientDeclaredNamelessCallTypes.size > 0
535
- || clientExplicitWireToolCatalog
543
+ inboundWire === "responses"
544
+ ? declaredWireToolNames.size > 0
545
+ || clientDeclaredNamelessCallTypes.size > 0
546
+ || clientExplicitWireToolCatalog
547
+ : clientDeclaredEmptyToolCatalog
548
+ || hasExplicitEmptyToolCatalog(clientToolAuthorizationBody)
536
549
  ) && route.provider.authMode !== "forward";
537
550
  options.nativeControl?.configureToolAuthorization?.(
538
551
  undeclaredToolGuardActive,
@@ -949,8 +962,7 @@ export async function preparePassthroughExchange(
949
962
  return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(refusal));
950
963
  }
951
964
  if (refusal instanceof AnthropicAccountCooldownError) {
952
- return formatErrorResponse(429, "rate_limit_error", refusal.message,
953
- refusal.retryAfterSeconds === null ? undefined : { retryAfter: String(refusal.retryAfterSeconds) });
965
+ return anthropicPoolCooldownResponse(refusal, { comboAttempt: options.comboAttempt, logCtx });
954
966
  }
955
967
  return formatErrorResponse(403, "permission_error", publicOAuthAuthenticationErrorMessage(refusal));
956
968
  }
@@ -1005,6 +1017,9 @@ export async function preparePassthroughExchange(
1005
1017
  // Stable per request: the Combo booking (`reserveDispatch`) already started and froze the spend
1006
1018
  // policy, so `spendEnforced` cannot change before dispatch even though no reporter starts it here.
1007
1019
  const receiptMode = !sendBudgetState.adapterSendBudget?.spendEnforced && Boolean(options.comboInitialSend);
1020
+ // #6969: Pool sends hold empty lifecycle frames so a pre-output refusal reaches the ladder below.
1021
+ const preOutputRefusals = new PoolPreOutputRefusals();
1022
+ const preOutputRefusalHold = () => poolPreOutputRefusalHold(admissionState.authCtx, route.provider, connectMs, options.comboAttempt);
1008
1023
  try {
1009
1024
  // Transient-5xx pre-stream retry (devlog/_plan/260716_claudecode_hardening/010):
1010
1025
  // the ChatGPT backend emits transient 502/520s that an immediate retry absorbs.
@@ -1035,11 +1050,12 @@ export async function preparePassthroughExchange(
1035
1050
  onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
1036
1051
  beforeDispatch: isCanonicalOpenAiForwardProvider(route.provider)
1037
1052
  ? createCodexAuthDispatchGuard(admissionState.authCtx, options.codexAuthPolicy ?? config, route.modelId, options.admission, options.visionDescribeTerminal === true) : undefined,
1053
+ preOutputRefusalHold: preOutputRefusalHold(),
1038
1054
  }),
1039
1055
  route.provider.authMode === "forward")
1040
1056
  // Every real attempt response — including an intermediate 5xx the
1041
1057
  // retry wrapper replaces — proves the host was reached (#914 review).
1042
- .then(adoptObservedResponse);
1058
+ .then(adoptObservedResponse).then(preOutputRefusals.observe);
1043
1059
  },
1044
1060
  { abortSignal: upstream.signal, label: safeHostLabel(request.url),
1045
1061
  attempts: sendBudgetState.initialSendAllowance(transientSendAttempts()),
@@ -1050,6 +1066,8 @@ export async function preparePassthroughExchange(
1050
1066
  },
1051
1067
  );
1052
1068
  } catch (err) {
1069
+ preOutputRefusals.recordOnLocalExit(err, options.abortSignal?.aborted === true || req.signal.aborted,
1070
+ config, admissionState.authCtx, route.provider, route.modelId);
1053
1071
  return transportFailureResponse(err);
1054
1072
  } finally {
1055
1073
  request.releaseBodyObservation?.();
@@ -1676,6 +1694,7 @@ export async function preparePassthroughExchange(
1676
1694
  passthroughEstimate,
1677
1695
  stream: upstreamRequestsStream,
1678
1696
  httpOnly: canonicalBufferedJson,
1697
+ preOutputRefusalHold: preOutputRefusalHold(),
1679
1698
  onResponse: (response, retryAuthCtx, retryRequest) => {
1680
1699
  adoptCodexWsStage(response);
1681
1700
  captureAffinityResponse(
@@ -1702,6 +1721,8 @@ export async function preparePassthroughExchange(
1702
1721
  }
1703
1722
  }
1704
1723
  }
1724
+ // #6969: a projection nothing above recovered goes on as the original stream, before any reader.
1725
+ upstreamResponse = preOutputRefusals.restore(upstreamResponse);
1705
1726
  // The deterministic route record cannot classify history it never observed (restart, expiry,
1706
1727
  // eviction, or an older transcript). Inspect only a bounded clone of a 4xx whose exact outbound
1707
1728
  // Responses body still carries opaque state, then rebuild once through the ordinary adapter
@@ -1969,6 +1990,7 @@ export async function preparePassthroughExchange(
1969
1990
  connectMs,
1970
1991
  localUpstream,
1971
1992
  upstreamResponse,
1993
+ terminalOutcomeRecorded: preOutputRefusals.outcomeRecorded,
1972
1994
  };
1973
1995
  }
1974
1996