@bitkyc08/opencodex 2.80.0 → 2.82.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 (376) hide show
  1. package/README.md +3 -3
  2. package/bin/ocx.mjs +166 -73
  3. package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
  4. package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
  5. package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
  6. package/gui/dist/assets/index-D3FBCNiP.js +94 -0
  7. package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/gui/dist/provider-icons/claude-green.svg +1 -0
  10. package/package.json +3 -2
  11. package/src/adapters/anthropic-model-contract.ts +8 -2
  12. package/src/adapters/anthropic.ts +8 -3
  13. package/src/adapters/codebuddy/mcp-server.ts +2 -25
  14. package/src/adapters/codebuddy/tool-bridge.ts +1 -7
  15. package/src/adapters/coding-agent/mcp-server.ts +33 -0
  16. package/src/adapters/coding-agent/protocol.ts +122 -17
  17. package/src/adapters/coding-agent/tool-catalog.ts +75 -0
  18. package/src/adapters/coding-agent/turn.ts +101 -44
  19. package/src/adapters/cursor/catalog.ts +8 -0
  20. package/src/adapters/cursor/effort-map.ts +2 -0
  21. package/src/adapters/devin/live-models.ts +2 -0
  22. package/src/adapters/kiro-retry.ts +9 -4
  23. package/src/adapters/openai-chat/messages.ts +7 -6
  24. package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
  25. package/src/adapters/openai-responses/passthrough.ts +9 -4
  26. package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
  27. package/src/adapters/physical-send.ts +8 -5
  28. package/src/adapters/qoder/adapter.ts +41 -6
  29. package/src/adapters/qoder/scaffold-guard.ts +2 -3
  30. package/src/adapters/zed.ts +16 -1
  31. package/src/claude/agents-inject.ts +14 -3
  32. package/src/claude/context-windows.ts +19 -17
  33. package/src/claude/desktop-3p.ts +88 -12
  34. package/src/claude/desktop-profile.ts +2 -1
  35. package/src/claude/gateway-cache.ts +18 -1
  36. package/src/claude/inbound-cache-stabilize.ts +4 -9
  37. package/src/claude/inbound.ts +78 -3
  38. package/src/claude/intercept/cli-catalog.ts +3 -0
  39. package/src/claude/intercept/model-bindings.ts +1 -0
  40. package/src/claude/intercept/picker-ca-persistence.ts +7 -6
  41. package/src/claude/intercept/picker-listener.ts +71 -4
  42. package/src/claude/intercept/picker-models.ts +14 -6
  43. package/src/claude/intercept/settings.ts +3 -0
  44. package/src/claude/long-context.ts +66 -0
  45. package/src/claude/model-info.ts +19 -16
  46. package/src/claude/outbound.ts +94 -9
  47. package/src/claude/subagent-model.ts +40 -11
  48. package/src/cli/account-anthropic-threshold.ts +14 -5
  49. package/src/cli/account-api.ts +3 -1
  50. package/src/cli/account-extended.ts +13 -9
  51. package/src/cli/account-policy-dto.ts +1 -1
  52. package/src/cli/account-policy.ts +13 -4
  53. package/src/cli/account.ts +3 -3
  54. package/src/cli/agent.ts +9 -5
  55. package/src/cli/aside-profile-recovery.ts +33 -0
  56. package/src/cli/capabilities-accounts.ts +2 -2
  57. package/src/cli/capabilities-agents-routing.ts +24 -0
  58. package/src/cli/capabilities-base.ts +5 -5
  59. package/src/cli/capabilities-integrations.ts +18 -2
  60. package/src/cli/cli-path-diagnostics.ts +132 -0
  61. package/src/cli/codex-shim-autorestore.ts +3 -0
  62. package/src/cli/desktop-runtime-guidance.ts +65 -0
  63. package/src/cli/dispatch.ts +15 -1
  64. package/src/cli/doctor.ts +16 -3
  65. package/src/cli/help.ts +4 -1
  66. package/src/cli/index.ts +25 -14
  67. package/src/cli/integration-aside-sync.ts +2 -1
  68. package/src/cli/integration-plan-dto.ts +1 -0
  69. package/src/cli/integration-preview.ts +8 -1
  70. package/src/cli/integrations.ts +48 -6
  71. package/src/cli/message-args.ts +34 -0
  72. package/src/cli/message-command.ts +43 -0
  73. package/src/cli/message-runtime.ts +6 -0
  74. package/src/cli/observe.ts +3 -1
  75. package/src/cli/provider.ts +29 -6
  76. package/src/cli/registry.ts +24 -2
  77. package/src/cli/resolve.ts +25 -2
  78. package/src/cli/root.ts +4 -2
  79. package/src/cli/status.ts +64 -12
  80. package/src/cli/update-restart-child.ts +4 -2
  81. package/src/cli/update-restart-home.ts +28 -10
  82. package/src/cli/update-restart-service-record.ts +116 -0
  83. package/src/cli/update-restart-supervision.ts +96 -0
  84. package/src/cli/update-restart.ts +59 -13
  85. package/src/cli/v2.ts +27 -20
  86. package/src/cli/version-skew-notice.ts +77 -0
  87. package/src/clients/config-export/commandcode.ts +131 -0
  88. package/src/clients/config-export/contracts.ts +12 -0
  89. package/src/clients/config-export.ts +64 -1
  90. package/src/clients/lazycodex.ts +1 -1
  91. package/src/clients/omo-role-models.ts +32 -7
  92. package/src/codex/auth-api/account-list.ts +11 -3
  93. package/src/codex/auth-api/account-selection.ts +107 -0
  94. package/src/codex/auth-api/main-account-probe.ts +25 -8
  95. package/src/codex/auth-api/pool-mode-gate.ts +33 -9
  96. package/src/codex/auth-api/pool-quota-probe.ts +15 -1
  97. package/src/codex/auth-api/routes.ts +11 -49
  98. package/src/codex/auth-context.ts +20 -1
  99. package/src/codex/autostart-health.ts +12 -3
  100. package/src/codex/catalog/auto-review.ts +13 -6
  101. package/src/codex/catalog/build-entries.ts +33 -7
  102. package/src/codex/catalog/bundled.ts +155 -26
  103. package/src/codex/catalog/combo-member.ts +3 -2
  104. package/src/codex/catalog/control-plane.ts +45 -0
  105. package/src/codex/catalog/effort.ts +37 -6
  106. package/src/codex/catalog/gather-capture.ts +10 -3
  107. package/src/codex/catalog/model-hints.ts +15 -8
  108. package/src/codex/catalog/model-visibility.ts +3 -1
  109. package/src/codex/catalog/operator-tiers.ts +38 -0
  110. package/src/codex/catalog/parsing.ts +2 -1
  111. package/src/codex/catalog/provider-models.ts +58 -44
  112. package/src/codex/catalog/retained-sync.ts +7 -5
  113. package/src/codex/catalog/routed-gather.ts +15 -1
  114. package/src/codex/catalog-auto-refresh-sources.ts +3 -4
  115. package/src/codex/catalog-auto-refresh.ts +19 -0
  116. package/src/codex/catalog.ts +1 -1
  117. package/src/codex/codex-home-owner.ts +3 -0
  118. package/src/codex/config-write-lock.ts +333 -0
  119. package/src/codex/control-plane-models.ts +6 -0
  120. package/src/codex/convergence.ts +2 -1
  121. package/src/codex/features.ts +257 -200
  122. package/src/codex/inject/config-write-section.ts +34 -0
  123. package/src/codex/inject/multi-agent-v2.ts +14 -6
  124. package/src/codex/inject/remove.ts +36 -11
  125. package/src/codex/inject/restore.ts +85 -20
  126. package/src/codex/inject-coordination.ts +15 -6
  127. package/src/codex/inject.ts +62 -20
  128. package/src/codex/journal.ts +62 -17
  129. package/src/codex/main-account-cache.ts +43 -0
  130. package/src/codex/paths.ts +2 -0
  131. package/src/codex/prepared-home.ts +18 -0
  132. package/src/codex/prompt-journal.ts +25 -12
  133. package/src/codex/prompt-layers.ts +49 -26
  134. package/src/codex/prompt-lock-claim.ts +134 -0
  135. package/src/codex/prompt-lock-evidence.ts +26 -0
  136. package/src/codex/prompt-lock-io.ts +16 -0
  137. package/src/codex/prompt-lock-owner.ts +130 -0
  138. package/src/codex/prompt-lock.ts +84 -53
  139. package/src/codex/routing/failure-window.ts +146 -0
  140. package/src/codex/routing/selection.ts +4 -1
  141. package/src/codex/routing.ts +13 -17
  142. package/src/codex/runtime.ts +106 -18
  143. package/src/codex/shim.ts +45 -19
  144. package/src/combos/jev-decision-contract.ts +25 -2
  145. package/src/combos/jev-service-exchange.ts +281 -0
  146. package/src/combos/jev.ts +34 -219
  147. package/src/combos/types.ts +1 -1
  148. package/src/config/atomic-write.ts +1 -1
  149. package/src/config/codex-ws-reuse-setting.ts +10 -0
  150. package/src/config/diagnostics.ts +27 -16
  151. package/src/config/live-reconcile.ts +17 -0
  152. package/src/config/load-degrade.ts +42 -0
  153. package/src/config/rebase-provenance.ts +11 -0
  154. package/src/config/schema/anthropic-account-pool.ts +143 -0
  155. package/src/config/schema/config-schema.ts +11 -1
  156. package/src/config/schema/leaf-validators.ts +4 -0
  157. package/src/config.ts +2 -0
  158. package/src/generated/compatibility-version.json +566 -298
  159. package/src/generated/model-metadata.ts +7 -6
  160. package/src/grok/inject.ts +50 -4
  161. package/src/grok/sync.ts +2 -1
  162. package/src/images/loop.ts +30 -11
  163. package/src/integrations/aside-profiles.ts +10 -3
  164. package/src/integrations/catalog-refresh.ts +19 -3
  165. package/src/integrations/mutation-plan.ts +7 -0
  166. package/src/integrations/registry.ts +8 -0
  167. package/src/integrations/state.ts +9 -2
  168. package/src/integrations/writer.ts +17 -5
  169. package/src/lib/bun-path-runtime.d.mts +32 -0
  170. package/src/lib/bun-path-runtime.mjs +95 -0
  171. package/src/lib/bun-runtime-preflight.ts +140 -0
  172. package/src/lib/desktop-cli-handoff.d.mts +20 -0
  173. package/src/lib/desktop-cli-handoff.mjs +92 -0
  174. package/src/lib/desktop-cli-record.d.mts +29 -0
  175. package/src/lib/desktop-cli-record.mjs +102 -0
  176. package/src/lib/lab-live-route-production.ts +9 -1
  177. package/src/lib/request-execution-budget.ts +244 -58
  178. package/src/lib/spend-pool-alias-validation.ts +79 -0
  179. package/src/lib/spend-pool-continuity.ts +34 -0
  180. package/src/lib/spend-reservation-ledger.ts +412 -50
  181. package/src/lib/state-store-registrations.ts +20 -3
  182. package/src/lib/synced-state-location.ts +46 -6
  183. package/src/lib/test-home-guard.ts +87 -1
  184. package/src/lib/translator-budget.ts +17 -4
  185. package/src/lib/upstream-retry.ts +17 -5
  186. package/src/lib/windows-atomic-replace.ts +38 -16
  187. package/src/lib/windows-elevation.ts +6 -0
  188. package/src/lib/windows-secret-acl.ts +92 -25
  189. package/src/lib/winsw.ts +2 -3
  190. package/src/lib/workflow-budget.ts +53 -11
  191. package/src/messaging/budget.ts +45 -0
  192. package/src/messaging/discovery.ts +57 -0
  193. package/src/messaging/envelope.ts +49 -0
  194. package/src/messaging/input.ts +37 -0
  195. package/src/messaging/rpc.ts +196 -0
  196. package/src/messaging/send.ts +58 -0
  197. package/src/messaging/socket.ts +72 -0
  198. package/src/messaging/types.ts +29 -0
  199. package/src/oauth/anthropic-account-refusal.ts +90 -18
  200. package/src/oauth/anthropic-account-threshold.ts +8 -2
  201. package/src/oauth/anthropic-continuity.ts +15 -1
  202. package/src/oauth/anthropic-model-quota.ts +120 -48
  203. package/src/oauth/anthropic-model-routes.ts +11 -3
  204. package/src/oauth/anthropic-oauth-definitions.ts +98 -0
  205. package/src/oauth/anthropic-pool-config.ts +30 -0
  206. package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
  207. package/src/oauth/anthropic-routing.ts +1202 -866
  208. package/src/oauth/anthropic-send-ownership.ts +41 -0
  209. package/src/oauth/anthropic.ts +4 -1
  210. package/src/oauth/chatgpt.ts +113 -41
  211. package/src/oauth/generic-account-failover.ts +3 -1
  212. package/src/oauth/health.ts +8 -6
  213. package/src/oauth/index.ts +96 -175
  214. package/src/oauth/kiro.ts +22 -9
  215. package/src/oauth/login-cli.ts +2 -0
  216. package/src/oauth/model-discovery-auth.ts +43 -0
  217. package/src/oauth/pool-kernel.ts +11 -5
  218. package/src/oauth/pool-settings-capability.ts +5 -3
  219. package/src/oauth/store-anthropic-instance.ts +87 -0
  220. package/src/oauth/store.ts +68 -10
  221. package/src/oauth/token-guardian.ts +2 -0
  222. package/src/oauth/types.ts +2 -0
  223. package/src/protocols/plan-snapshot.ts +5 -0
  224. package/src/protocols/settings.ts +20 -5
  225. package/src/providers/anthropic-instance-id.ts +47 -0
  226. package/src/providers/anthropic-instance.ts +48 -0
  227. package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
  228. package/src/providers/azure-model-metadata.ts +115 -0
  229. package/src/providers/derive.ts +23 -0
  230. package/src/providers/kiro-models.ts +3 -0
  231. package/src/providers/label.ts +1 -1
  232. package/src/providers/model-rename-fields.ts +2 -0
  233. package/src/providers/quota/account-cache.ts +37 -11
  234. package/src/providers/quota/anthropic-account-quota.ts +93 -0
  235. package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
  236. package/src/providers/quota/report-cache.ts +24 -1
  237. package/src/providers/quota/vendor-probes-key.ts +127 -17
  238. package/src/providers/quota/vendor-probes-oauth.ts +46 -14
  239. package/src/providers/quota.ts +23 -33
  240. package/src/providers/registry/entries-core.ts +35 -30
  241. package/src/providers/registry/model-ids.ts +1 -0
  242. package/src/providers/registry/model-seeds.ts +7 -4
  243. package/src/providers/registry/types.ts +3 -0
  244. package/src/providers/registry.ts +3 -3
  245. package/src/responses/reasoning-envelope.ts +29 -1
  246. package/src/responses/reasoning-replay-cache.ts +17 -1
  247. package/src/responses/spill-store.ts +20 -3
  248. package/src/responses/state/metrics.ts +95 -0
  249. package/src/responses/state/snapshot-codec.ts +6 -0
  250. package/src/responses/state/spill-failure.ts +12 -0
  251. package/src/responses/state/spill-queue.ts +13 -5
  252. package/src/responses/state/unforced-store-false.ts +55 -0
  253. package/src/responses/state.ts +68 -76
  254. package/src/responses/task-input.ts +17 -0
  255. package/src/router.ts +28 -4
  256. package/src/routing/compatibility/assemble.ts +1 -1
  257. package/src/routing/quota.ts +7 -3
  258. package/src/server/auth-cors.ts +5 -0
  259. package/src/server/background-lifecycle.ts +27 -0
  260. package/src/server/chat-completions.ts +7 -1
  261. package/src/server/chat-native.ts +64 -28
  262. package/src/server/claude-messages.ts +59 -8
  263. package/src/server/gui-static.ts +38 -4
  264. package/src/server/inbound-body-admission.ts +1 -0
  265. package/src/server/index/claude-intercept-lifecycle.ts +3 -1
  266. package/src/server/index/serve-options.ts +10 -2
  267. package/src/server/index/spend-ledger-lifecycle.ts +12 -5
  268. package/src/server/index/startup-warnings.ts +19 -0
  269. package/src/server/index.ts +5 -2
  270. package/src/server/inference/context.ts +1 -1
  271. package/src/server/lifecycle.ts +1 -1
  272. package/src/server/management/agent-settings-routes.ts +123 -38
  273. package/src/server/management/anthropic-account-threshold.ts +12 -9
  274. package/src/server/management/anthropic-pool-settings.ts +72 -0
  275. package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
  276. package/src/server/management/codex-agent-role-routes.ts +4 -2
  277. package/src/server/management/codex-prompt-routes.ts +1 -0
  278. package/src/server/management/codex-role-auto-assign.ts +13 -2
  279. package/src/server/management/config-routes.ts +85 -14
  280. package/src/server/management/context.ts +2 -2
  281. package/src/server/management/decision-routes.ts +2 -1
  282. package/src/server/management/integration-routes.ts +25 -2
  283. package/src/server/management/oauth-account-routes.ts +79 -98
  284. package/src/server/management/provider-instance-ownership.ts +44 -0
  285. package/src/server/management/provider-routes.ts +21 -28
  286. package/src/server/management/route-registry.ts +3 -1
  287. package/src/server/management/routing-profile-routes.ts +4 -4
  288. package/src/server/management/shared.ts +3 -3
  289. package/src/server/management/system-restart.ts +44 -14
  290. package/src/server/management/system-routes.ts +24 -2
  291. package/src/server/management/vision-sidecar-options.ts +9 -6
  292. package/src/server/management/web-search-sidecar-options.ts +51 -2
  293. package/src/server/management-auth.ts +6 -0
  294. package/src/server/messages-native-eligibility.ts +3 -2
  295. package/src/server/messages-native-oauth.ts +91 -45
  296. package/src/server/messages-native-selector.ts +39 -0
  297. package/src/server/messages-native.ts +133 -38
  298. package/src/server/messages-response-headers.ts +14 -0
  299. package/src/server/request-log-conversation.ts +48 -5
  300. package/src/server/request-log.ts +36 -2
  301. package/src/server/responses/adapter-continuation.ts +31 -19
  302. package/src/server/responses/adapter-dispatch.ts +75 -24
  303. package/src/server/responses/antigravity-validation-refusal.ts +20 -6
  304. package/src/server/responses/codex-ws-exchange.ts +17 -2
  305. package/src/server/responses/codex-ws-pool.ts +100 -13
  306. package/src/server/responses/codex-ws-wire.ts +9 -0
  307. package/src/server/responses/combo-requested-effort.ts +3 -1
  308. package/src/server/responses/compact.ts +25 -8
  309. package/src/server/responses/compaction-recovery.ts +1 -1
  310. package/src/server/responses/core-codex-account.ts +22 -1
  311. package/src/server/responses/core-combo-native.ts +24 -4
  312. package/src/server/responses/core-combo.ts +38 -25
  313. package/src/server/responses/core-lifetime.ts +3 -36
  314. package/src/server/responses/core-normalize.ts +1 -0
  315. package/src/server/responses/core-options.ts +8 -1
  316. package/src/server/responses/core-replay.ts +78 -0
  317. package/src/server/responses/core.ts +3 -3
  318. package/src/server/responses/fetch-helpers.ts +26 -3
  319. package/src/server/responses/passthrough-delivery.ts +30 -4
  320. package/src/server/responses/passthrough-dispatch.ts +39 -9
  321. package/src/server/responses/request-prepare.ts +41 -5
  322. package/src/server/responses/request-send-budget.ts +150 -50
  323. package/src/server/responses/request-spend.ts +325 -33
  324. package/src/server/responses/request-transport.ts +132 -52
  325. package/src/server/responses/response-effects.ts +1 -0
  326. package/src/server/responses/run-turn-execution.ts +13 -3
  327. package/src/server/responses/sidecar-execution.ts +113 -22
  328. package/src/server/responses/sidecar-send-budget.ts +58 -0
  329. package/src/server/responses/ws-upstream.ts +19 -6
  330. package/src/server/responses-undeclared-tool-guard.ts +5 -1
  331. package/src/server/startup-health-cache.ts +2 -1
  332. package/src/server/workflow-refusal.ts +33 -0
  333. package/src/service/cli.ts +20 -4
  334. package/src/service/desktop-command-guard.ts +32 -0
  335. package/src/service/desktop-startup.ts +66 -40
  336. package/src/service/desktop-supervision.d.mts +26 -0
  337. package/src/service/desktop-supervision.mjs +140 -0
  338. package/src/service/managing-cli.ts +1 -0
  339. package/src/service/orchestration.ts +46 -20
  340. package/src/service/ownership-mutation-lease.d.mts +1 -1
  341. package/src/service/ownership-mutation-lease.mjs +5 -7
  342. package/src/service/repair.ts +25 -9
  343. package/src/service/state.ts +4 -3
  344. package/src/service/systemd.ts +17 -8
  345. package/src/service/windows-ops.ts +61 -22
  346. package/src/sidecar/anthropic-binding.ts +79 -0
  347. package/src/sidecar/auth.ts +76 -2
  348. package/src/storage/policy-job.ts +5 -1
  349. package/src/storage/policy-worker.ts +3 -1
  350. package/src/storage/policy.ts +9 -0
  351. package/src/types/anthropic-account-pool.ts +33 -0
  352. package/src/types/config.ts +33 -28
  353. package/src/types/provider.ts +5 -0
  354. package/src/types/request.ts +5 -0
  355. package/src/types/tools.ts +7 -0
  356. package/src/types/wire.ts +1 -0
  357. package/src/types.ts +4 -0
  358. package/src/update/index.ts +57 -9
  359. package/src/update/restart-ownership.ts +10 -3
  360. package/src/update/runtime-ownership.d.mts +6 -0
  361. package/src/update/runtime-ownership.mjs +23 -7
  362. package/src/usage/cost.ts +3 -2
  363. package/src/usage/expected-prices.ts +66 -14
  364. package/src/vision/anthropic-describe.ts +16 -3
  365. package/src/vision/index.ts +3 -0
  366. package/src/vision/plan.ts +19 -6
  367. package/src/web-search/alpha-search.ts +7 -1
  368. package/src/web-search/anthropic-executor.ts +16 -4
  369. package/src/web-search/index.ts +7 -3
  370. package/src/web-search/loop.ts +32 -7
  371. package/src/web-search/passthrough-bridge.ts +4 -1
  372. package/src/web-search/sidecar-providers.ts +4 -3
  373. package/gui/dist/assets/App-MbDVfUeC.js +0 -54
  374. package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
  375. package/gui/dist/assets/index-UJt6npXk.js +0 -94
  376. package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
@@ -16,6 +16,7 @@ import type { ExplicitOpenAiCallerAuth } from "../../providers/openai-sidecar";
16
16
  import type { CallerDirectAuth } from "../../providers/caller-authorization";
17
17
  import type { CompactionRoutingOverride } from "./compaction-routing";
18
18
  import type { TranslatorBudget } from "../../lib/translator-budget";
19
+ import type { NativeReasoningOwner } from "../../responses/reasoning-replay-cache";
19
20
  import type { TransientSendBudget } from "../../lib/upstream-retry";
20
21
  import type { RequestLogContext } from "../request-log";
21
22
  import type { UpstreamHostAdmissionLease } from "../../codex/upstream-host-health";
@@ -71,7 +72,7 @@ export interface HandleResponsesOptions {
71
72
  compactionRecoveryKind?: "compaction-v1" | "compaction-v2";
72
73
  onCompactionRecoveryRoute?: (route: RouteResult) => void;
73
74
  onCompactionRecoveryAdapterEvent?: (event: AdapterEvent) => void;
74
- /** Physical-send reports already delivered to the shared used setter, including booking settlement. */
75
+ /** Physical-send reports already delivered to the shared ledger, including exact booking settlement. */
75
76
  onCompactionRecoverySendsReported?: (count: number) => void;
76
77
  /** Private holder for the Kiro serving-account lease. */
77
78
  accountLoad?: { lease: AccountLease | null; cancelled: boolean };
@@ -134,6 +135,8 @@ export interface HandleResponsesOptions {
134
135
  * it. Omitted means a genuine Responses inbound.
135
136
  */
136
137
  inboundWire?: InboundWire;
138
+ nativeReasoningReplay?: ReadonlyMap<string, string>;
139
+ nativeReasoningMint?: { owner?: NativeReasoningOwner };
137
140
  /** Droid's per-request effort default; each concrete combo or policy target applies it only if its ladder allows it. */
138
141
  droidDefaultEffort?: string;
139
142
  /** PF-07: the Chat source a combo child may send natively; set only by the Chat ingress. */
@@ -158,6 +161,10 @@ export interface HandleResponsesOptions {
158
161
  callerDirectAuth?: CallerDirectAuth | null;
159
162
  /** Internal recursion guard; callers outside this module must not set it. */
160
163
  comboAttempt?: boolean;
164
+ /** Child-owned prepaid initial send; capacity remains charged until dispatch or unsent release. */
165
+ comboInitialSend?: { permit: SingleUseDispatchPermit; producerOwned?: boolean };
166
+ /** Exact externally booked combo hop, used for its child's spend preflight and send reports. */
167
+ comboDispatchPermit?: SingleUseDispatchPermit;
161
168
  /** Internal handoff: this combo was selected by shadow-call interception. */
162
169
  shadowCallIntercepted?: boolean;
163
170
  /** Internal handoff: the memory phase this turn belongs to, so combo children keep its routing. */
@@ -19,6 +19,9 @@ import {
19
19
  reasoningReplayCodexCredentialIdentity,
20
20
  reasoningReplayKeyCredentialIdentity,
21
21
  durableReplayDestinationIdentity,
22
+ nativeReasoningTag,
23
+ nativeReasoningTagMatches,
24
+ type NativeReasoningOwner,
22
25
  bindReasoningReplayScope,
23
26
  reasoningReplayServingIdentityChanged,
24
27
  reasoningReplayItemStoreChanged,
@@ -37,6 +40,11 @@ export function adapterNeedsForcedContinuation(name: string): boolean {
37
40
  return name === "kiro" || name === "cursor";
38
41
  }
39
42
 
43
+ /** Adapters opting into restricted store:false replay for pending client function calls. */
44
+ export function adapterNeedsToolCallContinuation(name: string): boolean {
45
+ return name === "qoder";
46
+ }
47
+
40
48
 
41
49
  export type ContinuationOwnerRead =
42
50
  | { kind: "missing" }
@@ -113,6 +121,16 @@ export function bindRouteReasoningReplayScope(args: {
113
121
  ...args,
114
122
  modelId: parsed.modelId,
115
123
  });
124
+ const owner = nativeReasoningOwnerForRoute(args);
125
+ if (owner) parsed._nativeReasoningBoundOwner = owner;
126
+ else delete parsed._nativeReasoningBoundOwner;
127
+ if (parsed._nativeReasoningReplay?.size) {
128
+ for (const [blob, tag] of parsed._nativeReasoningReplay) {
129
+ if (!nativeReasoningTagMatches(nativeReasoningTag(owner, blob), tag)) {
130
+ stripNativeReasoningBlob(parsed, blob);
131
+ }
132
+ }
133
+ }
116
134
  const continuationDestinationIdentity = providerContinuationDestinationIdentity(parsed, provider);
117
135
  const continuationOwner = providerContinuationOwnerFromReplayIdentity(
118
136
  replayIdentity && continuationDestinationIdentity
@@ -151,6 +169,66 @@ export function bindRouteReasoningReplayScope(args: {
151
169
  bindProviderContinuationForRoute(parsed, continuationOwner);
152
170
  }
153
171
 
172
+ function stripNativeReasoningBlob(parsed: OcxParsedRequest, blob: string): void {
173
+ const input = (parsed._rawBody as { input?: unknown } | undefined)?.input;
174
+ if (Array.isArray(input)) {
175
+ for (const item of input) {
176
+ if (item && typeof item === "object" && item.type === "reasoning" && item.encrypted_content === blob) {
177
+ delete item.encrypted_content;
178
+ delete item.id;
179
+ }
180
+ }
181
+ }
182
+ for (const message of parsed.context.messages) {
183
+ if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
184
+ for (const part of message.content) {
185
+ if (part.type !== "thinking" || typeof part.signature !== "string") continue;
186
+ try {
187
+ const item: unknown = JSON.parse(part.signature);
188
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
189
+ const reasoning = item as Record<string, unknown>;
190
+ if (reasoning.type !== "reasoning" || reasoning.encrypted_content !== blob) continue;
191
+ delete reasoning.encrypted_content;
192
+ delete reasoning.id;
193
+ part.signature = JSON.stringify(reasoning);
194
+ delete part.itemId;
195
+ } catch { /* A provider signature is not the parser's JSON carrier. */ }
196
+ }
197
+ }
198
+ }
199
+
200
+ export function nativeReasoningOwnerForRoute(args: {
201
+ provider: OcxProviderConfig;
202
+ oauthCredentialSnapshot?: Pick<OAuthAccessSnapshot, "accountId" | "generation">;
203
+ codexAuthContext?: CodexAuthContext;
204
+ }): NativeReasoningOwner | undefined {
205
+ const { provider } = args;
206
+ const salt = thoughtSignatureReplaySalt();
207
+ if (!salt || salt.length < 16) return undefined;
208
+ const destination = durableReplayDestinationIdentity(JSON.stringify([
209
+ provider.baseUrl.trim().replace(/\/+$/, ""), provider.responsesPath ?? "",
210
+ ]));
211
+ let credential: string | undefined;
212
+ if (provider.authMode === "oauth") {
213
+ const snapshot = args.oauthCredentialSnapshot;
214
+ if (snapshot?.accountId && snapshot.generation !== undefined) credential = durableReplayCredentialIdentity(
215
+ "oauth", `${snapshot.accountId}\0${snapshot.generation}`, provider.headers, salt,
216
+ );
217
+ } else if (provider.authMode === "forward") {
218
+ const context = args.codexAuthContext;
219
+ if (context && (context.kind === "pool" || context.kind === "main-pool")
220
+ && context.accountId && context.writerGeneration !== undefined
221
+ && (context.kind !== "pool" || context.generation !== undefined)) {
222
+ credential = durableReplayCredentialIdentity("codex", JSON.stringify([
223
+ context.accountId, context.kind === "pool" ? context.generation : null, context.writerGeneration,
224
+ ]), provider.headers, salt);
225
+ }
226
+ } else if (provider.authMode !== "local") {
227
+ credential = durableReplayCredentialIdentity("key", nonEmptyProviderApiKey(provider), provider.headers, salt);
228
+ }
229
+ return destination && credential ? { destination, credential } : undefined;
230
+ }
231
+
154
232
 
155
233
  function routeReasoningReplayIdentity(args: {
156
234
  providerName: string;
@@ -29,8 +29,6 @@ import { releaseUpstreamHostAdmission } from "../../codex/upstream-host-health";
29
29
  import { releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
30
30
  import { runWithCompactionRecovery } from "./compaction-recovery";
31
31
 
32
- /** Public Responses entry and compatibility exports. Implementations live with their owners. */
33
-
34
32
  /**
35
33
  * Route one `/v1/responses` request through the adapter pipeline: recovery loop, passthrough
36
34
  * wire, image/web-search bridges, and the terminal-guard continuation.
@@ -66,13 +64,15 @@ export async function handleResponses(
66
64
  // Once at ingress, spend observer included: a combo child inherits the parent's holder.
67
65
  sendBudget: options.sendBudget ?? createInferenceSendBudget(req, logCtx),
68
66
  }, handleResponsesInner);
69
- const finalResponse = ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget) : response;
67
+ const finalResponse = ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget, abortSignal) : response;
70
68
  if (!accountLoad.lease) { release(); return finalResponse; }
71
69
  return finalizeAccountLease(finalResponse, release);
72
70
  } catch (error) {
73
71
  release();
74
72
  if (ownsBudget) translatorBudget.dispose();
75
73
  throw error;
74
+ } finally {
75
+ if (!options.comboInitialSend?.producerOwned) options.comboInitialSend?.permit.release();
76
76
  }
77
77
  }
78
78
 
@@ -43,6 +43,10 @@ const pacingWebsocketDowngradeWarned = new Set<string>();
43
43
  */
44
44
  const EGRESS_DECIDED = Symbol.for("opencodex.provider-egress.decided");
45
45
  const UPSTREAM_REWRITTEN = Symbol.for("opencodex.plugins.upstream-rewritten");
46
+ // Survives queued credential rebuilds and executor replacement without importing budget owners.
47
+ const PHYSICAL_DISPATCH = Symbol.for("opencodex.physical-dispatch");
48
+ // One WS attempt and its HTTP fallback are one logical physical-send receipt.
49
+ const WS_FALLBACK_RECEIPT = Symbol.for("opencodex.ws-fallback-receipt");
46
50
 
47
51
  /**
48
52
  * Announce once, per provider, that an explicit egress route moved this provider off the
@@ -200,6 +204,7 @@ export function sendWithConnectionPolicy(
200
204
  const largeCodexBody = typeof body === "string"
201
205
  && /^https:\/\/chatgpt\.com\/backend-api\/codex\/responses(?:\/compact)?$/.test(target)
202
206
  && Buffer.byteLength(body, "utf8") >= 1024 * 1024;
207
+ (init as Record<symbol, (() => void) | undefined> | undefined)?.[PHYSICAL_DISPATCH]?.();
203
208
  return physicalFetch(input, {
204
209
  ...init,
205
210
  ...(largeCodexBody ? { body: Buffer.from(body, "utf8") } : {}),
@@ -233,10 +238,13 @@ export interface ProviderFetchOptions {
233
238
  onCodexWsQuota?: CodexWsQuotaObserver;
234
239
  /** Synchronous admission at actual credential dispatch, after pacing/backoff. */
235
240
  beforeDispatch?: (headers: Headers) => void;
241
+ /** One-shot receipt after final local HTTP admission, immediately before executor entry. */
242
+ onPhysicalDispatch?: () => void;
236
243
  /** Revalidate/rebuild a queued request at its physical send boundary, after pacing. */
237
244
  dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch) => Promise<Response>;
238
245
  }
239
246
 
247
+ /** Compose provider pacing and transport admission; receipts run only at executor entry. */
240
248
  export function providerFetch(
241
249
  provider: OcxProviderConfig,
242
250
  runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
@@ -301,7 +309,14 @@ export function providerFetch(
301
309
  options.beforeDispatch?.(new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined)));
302
310
  // No proxy option is attached here: a `dispatchOverride` may rebuild this request against
303
311
  // a different destination, so the route is decided at the physical send instead.
304
- const dispatchInit = { ...withUpstreamHttpVersion(input, init, provider), timeout: 0 };
312
+ const sharedReceipt = (init as Record<symbol, (() => void) | undefined> | undefined)?.[WS_FALLBACK_RECEIPT];
313
+ let notified = false;
314
+ const dispatchInit = { ...withUpstreamHttpVersion(input, init, provider), timeout: 0,
315
+ ...(options.onPhysicalDispatch ? { [PHYSICAL_DISPATCH]: () => {
316
+ if (sharedReceipt) sharedReceipt();
317
+ else if (!notified) { options.onPhysicalDispatch!(); notified = true; }
318
+ } } : {}),
319
+ };
305
320
  return options.dispatchOverride
306
321
  ? options.dispatchOverride(input, dispatchInit, dispatch)
307
322
  : dispatch(input, dispatchInit);
@@ -332,8 +347,16 @@ export function providerFetch(
332
347
  // used, protocol pin included: a WS turn that falls back is serving the
333
348
  // request over HTTP, and dropping the provider's `upstreamHttpVersion`
334
349
  // there would silently negotiate a transport the operator ruled out.
335
- return codexWsUpstreamFetch(input, init, httpFetch, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
336
- async () => { (await waitForPacing(init.signal ?? undefined))?.release(); });
350
+ let notified = false;
351
+ const notifyOnce = (): void => {
352
+ if (notified) return;
353
+ notified = true;
354
+ options.onPhysicalDispatch?.();
355
+ };
356
+ const fallback = ((url: Parameters<typeof globalThis.fetch>[0], fallbackInit?: RequestInit) =>
357
+ httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit)) as typeof globalThis.fetch;
358
+ return codexWsUpstreamFetch(input, init, fallback, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
359
+ async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce);
337
360
  }
338
361
  return httpFetch(input, init);
339
362
  };
@@ -24,13 +24,16 @@ import { teeWithBoundedInspection } from "../inspection-tee";
24
24
  import {
25
25
  codexForwardTerminalOutcomeRecorder,
26
26
  usesCodexForwardPoolAuth,
27
+ liveMainQuotaDispatch,
27
28
  codexQuotaOutcomeMeta,
28
29
  codexDenialOutcomeMeta,
29
30
  isFixedCodexAccount,
30
31
  shouldDeferCodexResetDerivedCooldown,
31
32
  } from "./core-codex-account";
33
+ import { isMainQuotaDispatchLive, isMainQuotaDispatchWsClaimed } from "../../codex/main-account-cache";
34
+ import { MAIN_CODEX_ACCOUNT_ID } from "../../codex/account-id";
32
35
  import type { ResponsesTerminalStatus } from "../../bridge";
33
- import { isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse } from "./ws-upstream";
36
+ import { isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse, isCodexWsPreludeProjection } from "./ws-upstream";
34
37
  import { recordSubagentQuotaFailureForThreadSpawn } from "../../codex/subagent-model-fallback";
35
38
  import { recordCodexUpstreamOutcome } from "../../codex/routing";
36
39
  import { codexProbeLeaseId, codexProbeQuotaScope, codexTransientProbeGrant, releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
@@ -139,6 +142,7 @@ import {
139
142
  httpStatusFromTerminalError,
140
143
  inspectResponseLogJson,
141
144
  inspectResponseLogSsePayloadParsed,
145
+ noteUpstreamRequestId,
142
146
  } from "../request-log";
143
147
  import { restoreRoutedCustomCallsInJson } from "../../responses/custom-tool-compat";
144
148
  import { restoreRoutedToolSearchCallsInJson } from "../../responses/tool-search-compat";
@@ -364,6 +368,7 @@ export async function deliverPassthroughResponse(
364
368
  | "subagentFallbackAccountId"
365
369
  | "clientRequestedStream"
366
370
  | "translatorBudget"
371
+ | "inboundWire"
367
372
  >,
368
373
  transportState: Pick<ResponsesTransport, "requestBindings">,
369
374
  sidecarState: Pick<ResponsesSidecarAuth, "openAiSidecar">,
@@ -405,6 +410,9 @@ export async function deliverPassthroughResponse(
405
410
  | "localUpstream"
406
411
  >,
407
412
  ): Promise<Response> {
413
+ const { route } = requestState;
414
+ // The proof must belong to the response whose headers are published.
415
+ const arrivalMainDispatch = liveMainQuotaDispatch(admissionState.authCtx, route.provider);
408
416
  const { logCtx, config, options, req } = requestContext;
409
417
  const {
410
418
  codexSafetyBufferingOptions,
@@ -428,7 +436,8 @@ export async function deliverPassthroughResponse(
428
436
  normalizeFunctionCompletionJson,
429
437
  } = nativeExchange;
430
438
  const { commitReasoningReplayServingRoute, recordTerminalOutcomes } = responseEffects;
431
- const { parsed, route, subagentQuotaFailureModel, clientRequestedStream, translatorBudget } = requestState;
439
+ const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget, inboundWire } = requestState;
440
+ const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";
432
441
  const { openAiSidecar } = sidecarState;
433
442
  const { requestBindings } = transportState;
434
443
 
@@ -450,6 +459,7 @@ export async function deliverPassthroughResponse(
450
459
  }
451
460
 
452
461
  const headers = sanitizePassthroughHeaders(upstreamResponse.headers, codexSafetyBufferingOptions);
462
+ if (!upstreamResponse.ok) noteUpstreamRequestId(logCtx, headers);
453
463
  const resolvedModel = headers.get("openai-model")?.trim();
454
464
  if (resolvedModel) {
455
465
  logCtx.servedModel = resolvedModel;
@@ -533,6 +543,20 @@ export async function deliverPassthroughResponse(
533
543
  ...(admissionState.authCtx.kind === "pool" ? { credentialGeneration: admissionState.authCtx.generation } : {}),
534
544
  });
535
545
  }
546
+ } else {
547
+ // The WS observer is the only plain-main publisher for a WebSocket exchange;
548
+ // a prelude projection carries the prelude snapshot, not fresh evidence.
549
+ // The dispatch claim is authoritative because downstream wrappers can replace the Response.
550
+ if (arrivalMainDispatch && !isMainQuotaDispatchWsClaimed(arrivalMainDispatch)
551
+ && !(isCodexWsUpstreamResponse(upstreamResponse) || isCodexWsPreludeProjection(upstreamResponse))) {
552
+ const { applyAccountQuotaFromUpstreamHeaders } = await import("../../codex/auth-api");
553
+ // Import yields; same-account token replacement leaves the identity writer live.
554
+ // Re-check the credential fence with no await before publication.
555
+ if (isMainQuotaDispatchLive(arrivalMainDispatch)) {
556
+ applyAccountQuotaFromUpstreamHeaders(MAIN_CODEX_ACCOUNT_ID, upstreamResponse.headers,
557
+ arrivalMainDispatch.configGeneration, arrivalMainDispatch.writer, { modelId: route.modelId });
558
+ }
559
+ }
536
560
  }
537
561
 
538
562
  // Non-2xx passthrough failures must never reach Codex as an empty body —
@@ -637,6 +661,7 @@ export async function deliverPassthroughResponse(
637
661
  route.provider.webSearchBridge?.backend,
638
662
  config,
639
663
  openAiSidecar,
664
+ route.providerName,
640
665
  );
641
666
  const webSearchBridgePlan = planPassthroughWebSearchBridge(parsed, route.provider, {
642
667
  providerName: route.providerName,
@@ -836,6 +861,7 @@ export async function deliverPassthroughResponse(
836
861
  providerExecutedCallTypes,
837
862
  declaredBareWireToolNames,
838
863
  recoverableBareCustomWireToolNames,
864
+ enforceDeclaredToolNames,
839
865
  )
840
866
  : undefined,
841
867
  grokUpstreamEchoEnabled
@@ -1293,7 +1319,7 @@ export async function deliverPassthroughResponse(
1293
1319
  if (grokUpstreamEchoEnabled) {
1294
1320
  clientJson = stripGrokUpstreamEnvelopeEchoFromResponsesJson(clientJson);
1295
1321
  }
1296
- // #1700: same fail-closed policy as the SSE relay above. Both the plain JSON answer and
1322
+ // #1700: same inbound-wire refusal policy as the SSE relay above. Both the plain JSON answer and
1297
1323
  // the reframed-SSE branch below are built from this body, so one check covers them. This
1298
1324
  // runs BEFORE the continuation cache write below: a refused turn must not become state a
1299
1325
  // later `previous_response_id` replay can expand from.
@@ -1312,7 +1338,7 @@ export async function deliverPassthroughResponse(
1312
1338
  return undefined;
1313
1339
  }
1314
1340
  })();
1315
- if (undeclared !== undefined) {
1341
+ if (enforceDeclaredToolNames && undeclared !== undefined) {
1316
1342
  return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared));
1317
1343
  }
1318
1344
  clientJson = normalizeDefaultNamespaceInJson(
@@ -12,6 +12,7 @@ import type { ResponsesTransport } from "./request-transport";
12
12
  import type { ResponsesEffects } from "./response-effects";
13
13
  import type { ResponsesSendBudget } from "./request-send-budget";
14
14
  import { transientSendCapFor } from "./request-send-budget";
15
+ import { renewMainQuotaDispatchForAttempt } from "../../codex/main-account-cache";
15
16
  import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
16
17
  import { isLocalUpstream } from "../../lib/local-upstream";
17
18
  import { codexSafetyBufferingFilterOptions, terminalStatusFromParsed } from "../relay";
@@ -163,6 +164,7 @@ import { ambiguousResendAllowanceFor, selfContainedResponsesBody } from "./reset
163
164
  import { upstreamErrorMessageFromPayload, ENCRYPTED_FUNCTION_OUTPUT_REJECTION } from "../../lib/errors";
164
165
  import { isTransientConsoleGoUploadRejection } from "../../providers/opencode-zen-rate-limit";
165
166
  import { planReasoningEffortDowngrade } from "../../providers/reasoning-metadata";
167
+ import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
166
168
 
167
169
  /** Prepares and recovers one native Responses exchange before client commitment. */
168
170
  export async function preparePassthroughExchange(
@@ -214,6 +216,7 @@ export async function preparePassthroughExchange(
214
216
  ResponsesSendBudget,
215
217
  | "remainingTransientSendBudget"
216
218
  | "noteTransientSends"
219
+ | "transientSendReporter"
217
220
  | "recoverySendAllowance"
218
221
  | "recoveryClassFor"
219
222
  | "sendBudgetExhausted"
@@ -223,6 +226,10 @@ export async function preparePassthroughExchange(
223
226
  | "pendingHopPermit"
224
227
  | "workflowRootId"
225
228
  | "sendsUsed"
229
+ | "targetSendsUsed"
230
+ | "initialSendAllowance"
231
+ | "noteInitialDispatch"
232
+ | "adapterSendBudget"
226
233
  >,
227
234
  ) {
228
235
  const { config, logCtx, options, req } = requestContext;
@@ -249,6 +256,7 @@ export async function preparePassthroughExchange(
249
256
  const {
250
257
  remainingTransientSendBudget,
251
258
  noteTransientSends,
259
+ transientSendReporter,
252
260
  recoverySendAllowance,
253
261
  recoveryClassFor,
254
262
  sendBudgetExhausted,
@@ -750,7 +758,7 @@ export async function preparePassthroughExchange(
750
758
  const transientSendPolicy = () => transientRetryPolicyFor(route.provider);
751
759
  const transientSendAttempts = (): number => transientSendCapFor(
752
760
  transientSendPolicy()?.attempts,
753
- sendBudgetState.sendsUsed,
761
+ sendBudgetState.targetSendsUsed,
754
762
  );
755
763
  const configuredTransientSendBudgetExhausted = (): boolean =>
756
764
  transientSendPolicy() !== null && transientSendAttempts() === 0;
@@ -794,7 +802,13 @@ export async function preparePassthroughExchange(
794
802
  */
795
803
  const sendAmbiguousReplacement = (
796
804
  signal: AbortSignal = upstream.signal,
797
- ): Promise<Response> => fetchWithHeaderTimeout(
805
+ ): Promise<Response> => {
806
+ if (admissionState.authCtx.kind === "main" && admissionState.authCtx.mainQuotaDispatch) {
807
+ admissionState.authCtx.mainQuotaDispatch = renewMainQuotaDispatchForAttempt(admissionState.authCtx.mainQuotaDispatch);
808
+ }
809
+ const report = transientSendReporter();
810
+ let started = false;
811
+ const run = () => fetchWithHeaderTimeout(
798
812
  request.url,
799
813
  applyUpstreamRecoveryInit({
800
814
  method: request.method,
@@ -829,11 +843,17 @@ export async function preparePassthroughExchange(
829
843
  // Charged to the SAME request counter every other send goes through. The
830
844
  // replacement is bought here rather than by a nested retry helper, so there is
831
845
  // one charge for one send and no per-layer counter to reconcile.
832
- noteTransientSends(1);
846
+ if (report.beforeSend?.() === false) throw new SendBudgetExhaustedError(safeHostLabel(request.url));
847
+ started = true;
833
848
  },
834
849
  }),
835
850
  route.provider.authMode === "forward",
836
851
  );
852
+ return (report.execute ? report.execute(run) : run()).finally(() => {
853
+ try { if (started) report(1); }
854
+ finally { report.close?.(); }
855
+ });
856
+ };
837
857
  /**
838
858
  * Refuse a built body that exceeds the operator's configured ceiling, before it is sent.
839
859
  *
@@ -891,7 +911,8 @@ export async function preparePassthroughExchange(
891
911
  releaseUpstreamHostAdmission(nativeHostState.lease);
892
912
  nativeHostState.lease = null;
893
913
  releaseCodexAuthContextProbeLease(admissionState.authCtx);
894
- return formatErrorResponse(429, "request_send_budget_exhausted", err.message);
914
+ return unboundPoolSpendRefusalResponse(logCtx)
915
+ ?? formatErrorResponse(429, "request_send_budget_exhausted", err.message);
895
916
  }
896
917
  const refusal = unwrapUpstreamRetryEvidenceError(err);
897
918
  // Pacing may outlive the selected account's admission. No fetch occurred, so do
@@ -955,6 +976,11 @@ export async function preparePassthroughExchange(
955
976
  };
956
977
  const initialBodyRefusal = refuseOversizedOutboundBody(request);
957
978
  if (initialBodyRefusal) return initialBodyRefusal;
979
+ // A combo-owned first send is settled by the physical-dispatch receipt unless spend
980
+ // enforcement is on, where the shared reporter already owns the permit lifecycle.
981
+ // Stable per request: the Combo booking (`reserveDispatch`) already started and froze the spend
982
+ // policy, so `spendEnforced` cannot change before dispatch even though no reporter starts it here.
983
+ const receiptMode = !sendBudgetState.adapterSendBudget?.spendEnforced && Boolean(options.comboInitialSend);
958
984
  try {
959
985
  // Transient-5xx pre-stream retry (devlog/_plan/260716_claudecode_hardening/010):
960
986
  // the ChatGPT backend emits transient 502/520s that an immediate retry absorbs.
@@ -981,6 +1007,7 @@ export async function preparePassthroughExchange(
981
1007
  dispatchOverride: oauthDispatch(request),
982
1008
  providerName: route.providerName,
983
1009
  modelId: route.modelId,
1010
+ onPhysicalDispatch: receiptMode ? sendBudgetState.noteInitialDispatch : undefined,
984
1011
  onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
985
1012
  beforeDispatch: isCanonicalOpenAiForwardProvider(route.provider)
986
1013
  ? createCodexAuthDispatchGuard(admissionState.authCtx, options.codexAuthPolicy ?? config, route.modelId, options.admission, options.visionDescribeTerminal === true) : undefined,
@@ -991,7 +1018,10 @@ export async function preparePassthroughExchange(
991
1018
  .then(adoptObservedResponse);
992
1019
  },
993
1020
  { abortSignal: upstream.signal, label: safeHostLabel(request.url),
994
- attempts: remainingTransientSendBudget(transientSendAttempts()), onSendsConsumed: noteTransientSends,
1021
+ attempts: sendBudgetState.initialSendAllowance(transientSendAttempts()),
1022
+ // Reporter path: direct requests and enforced spend. Receipt-mode sends are settled
1023
+ // by `onPhysicalDispatch`; a reporter as well would count them twice.
1024
+ ...(receiptMode ? {} : { onSendsConsumed: transientSendReporter() }),
995
1025
  claimAmbiguousResend: claimPreHeaderResend,
996
1026
  },
997
1027
  );
@@ -1094,7 +1124,7 @@ export async function preparePassthroughExchange(
1094
1124
  .then(adoptObservedResponse);
1095
1125
  },
1096
1126
  { abortSignal: upstream.signal, label: safeHostLabel(request.url), attempts: allowance.attempts,
1097
- onSendsConsumed: noteTransientSends, claimAmbiguousResend: claimPreHeaderResend },
1127
+ onSendsConsumed: transientSendReporter(allowance.permit), claimAmbiguousResend: claimPreHeaderResend },
1098
1128
  );
1099
1129
  } catch (err) {
1100
1130
  return { failed: transportFailureResponse(err) };
@@ -1226,7 +1256,7 @@ export async function preparePassthroughExchange(
1226
1256
  },
1227
1257
  { abortSignal: upstream.signal, label: safeHostLabel(request.url),
1228
1258
  attempts: remainingTransientSendBudget(transientSendAttempts()),
1229
- onSendsConsumed: noteTransientSends, claimAmbiguousResend: claimPreHeaderResend },
1259
+ onSendsConsumed: transientSendReporter(), claimAmbiguousResend: claimPreHeaderResend },
1230
1260
  );
1231
1261
  } catch (err) {
1232
1262
  return transportFailureResponse(err);
@@ -1354,7 +1384,7 @@ export async function preparePassthroughExchange(
1354
1384
  .then(adoptObservedResponse);
1355
1385
  },
1356
1386
  { abortSignal: upstream.signal, label: safeHostLabel(request.url), attempts: remainingTransientSendBudget(transientSendAttempts()),
1357
- onSendsConsumed: noteTransientSends, claimAmbiguousResend: claimPreHeaderResend },
1387
+ onSendsConsumed: transientSendReporter(), claimAmbiguousResend: claimPreHeaderResend },
1358
1388
  );
1359
1389
  } catch (err) {
1360
1390
  return transportFailureResponse(err);
@@ -1489,7 +1519,7 @@ export async function preparePassthroughExchange(
1489
1519
  .then(adoptObservedResponse);
1490
1520
  },
1491
1521
  { abortSignal: upstream.signal, label: safeHostLabel(request.url), attempts: remainingTransientSendBudget(transientSendAttempts()),
1492
- onSendsConsumed: noteTransientSends, claimAmbiguousResend: claimPreHeaderResend },
1522
+ onSendsConsumed: transientSendReporter(), claimAmbiguousResend: claimPreHeaderResend },
1493
1523
  );
1494
1524
  } catch (err) {
1495
1525
  return transportFailureResponse(err);
@@ -64,6 +64,7 @@ import type { OcxParsedRequest } from "../../types";
64
64
  import { buildToolBridgeMaps } from "./collaboration";
65
65
  import { parseRequest } from "../../responses/parser";
66
66
  import { anthropicSessionKeyFromParts } from "../../oauth/anthropic-routing";
67
+ import { configuredAnthropicInstance } from "../../providers/anthropic-instance";
67
68
  import { isTranslatorBudgetExceededError } from "../../lib/translator-budget";
68
69
  import { bindTurnTerminationScope, rememberDeliveredFinalAnswer } from "../../responses/turn-termination";
69
70
  import { observeCacheDiagnosticInbound, rebindCacheDiagnosticBody, requestLogSpeedLabel, readConfiguredCodexServiceTier } from "../request-log";
@@ -74,6 +75,7 @@ import {
74
75
  routeCompactionModel,
75
76
  routeModel,
76
77
  NoEligiblePolicyCandidateError,
78
+ AnthropicSecondaryInstanceUnavailableError,
77
79
  } from "../../router";
78
80
  import { evidenceFromBody } from "../../routing/request-evidence";
79
81
  import { OPENAI_CODEX_PROVIDER_ID, isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
@@ -379,6 +381,10 @@ export async function prepareResponsesRequest(
379
381
  let toolBridgeMaps: ReturnType<typeof buildToolBridgeMaps>;
380
382
  try {
381
383
  parsed = parseRequest(body);
384
+ if (options.inboundWire === "anthropic") {
385
+ parsed._nativeReasoningReplay = options.nativeReasoningReplay;
386
+ parsed._nativeReasoningMint = options.nativeReasoningMint;
387
+ }
382
388
  parsed._promptCacheKeyIsSharedCohort = options.promptCacheKeyIsSharedCohort;
383
389
  // The body may have been rebuilt since the inbound observation (previous-response
384
390
  // expansion); alias the parsed raw body to the same draft so the outbound
@@ -522,6 +528,23 @@ export async function prepareResponsesRequest(
522
528
  // or a subagent fallback rewrites it, so a refusal names the client's own
523
529
  // request rather than a destination it never asked for.
524
530
  const inboundSelector = parsed.modelId;
531
+ const selectorTargetsSecondary = (selector: string): boolean => {
532
+ const slash = selector.indexOf("/");
533
+ if (slash <= 0) return false;
534
+ const qualifier = selector.slice(0, slash);
535
+ // Match router precedence: exact custom keys (including uppercase names) win over aliases.
536
+ if (Object.hasOwn(config.providers, qualifier)) return qualifier === "anthropic2";
537
+ return qualifier === "anthropic2"
538
+ || config.providers.anthropic2?.alias?.trim().toLowerCase() === qualifier.toLowerCase();
539
+ };
540
+ const explicitlyRequestsSecondary = selectorTargetsSecondary(inboundSelector);
541
+ const secondaryProvider = config.providers.anthropic2;
542
+ // Reserve an explicit B selector before an absent row can default-route it to A.
543
+ // Combo parents have already dispatched above; concrete children retain their own target.
544
+ if (explicitlyRequestsSecondary && (!secondaryProvider || secondaryProvider.disabled === true
545
+ || secondaryProvider.authMode === "oauth" && configuredAnthropicInstance(config, "anthropic2") !== "anthropic2")) {
546
+ return formatErrorResponse(401, "authentication_error", "Anthropic Pool 2 requires an enabled configured provider");
547
+ }
525
548
  const admissionScope = resolveAdmissionModelScope(config, options.admission);
526
549
  const captureInboundRoutePolicy = (candidate: RouteResult, captureRequestPolicy = true): RouteResult => {
527
550
  // Every route this request path produces passes through here: the direct
@@ -553,11 +576,21 @@ export async function prepareResponsesRequest(
553
576
  // no canonical OpenAI route for (#2901). Only the initial compaction route
554
577
  // may fall back to the configured default provider; combo attempts and the
555
578
  // later fallback/recovery re-routes keep the ordinary reservation.
556
- const routeInbound = (modelId: string) => concreteSelection
557
- ? routeConcreteModel(config, modelId)
558
- : parsed._compactionRequest === true
559
- ? routeCompactionModel(config, modelId, evidenceFromBody(parsed._rawBody))
560
- : routeModel(config, modelId, evidenceFromBody(parsed._rawBody));
579
+ const routeInbound = (modelId: string) => {
580
+ const resolved = concreteSelection
581
+ ? routeConcreteModel(config, modelId)
582
+ : parsed._compactionRequest === true
583
+ ? routeCompactionModel(config, modelId, evidenceFromBody(parsed._rawBody))
584
+ : routeModel(config, modelId, evidenceFromBody(parsed._rawBody));
585
+ // A Pool 2 selector may leave Pool 2 only through an operator-configured cross-provider
586
+ // blocked-model redirect, which the router marks as a credential-domain rewrite. Any other
587
+ // destination means the selector fell through to default resolution, which stays refused.
588
+ const operatorRedirect = resolved.routeReason === "blocked-model-redirect" && resolved.credentialDomainRewrite === true;
589
+ if (selectorTargetsSecondary(modelId) && resolved.providerName !== "anthropic2" && !operatorRedirect) {
590
+ throw new Error("Anthropic Pool 2 selector cannot use the default provider");
591
+ }
592
+ return resolved;
593
+ };
561
594
  const resolveRoute = (modelId: string) => captureInboundRoutePolicy(routeInbound(modelId));
562
595
  // The phase's destination. Resolved through the admission-scoped resolver every other route
563
596
  // uses, and it fails closed exactly like the shadow target: falling back to the native model
@@ -644,6 +677,9 @@ export async function prepareResponsesRequest(
644
677
  logCtx.routeDecision = route.routeDecision;
645
678
  logCtx.policyEligibility = route.policyEligibility;
646
679
  } catch (err) {
680
+ if (err instanceof AnthropicSecondaryInstanceUnavailableError) {
681
+ return formatErrorResponse(401, "authentication_error", err.message);
682
+ }
647
683
  const policyRefusal = policyCandidateRefusalResponse(err);
648
684
  if (policyRefusal) return policyRefusal;
649
685
  if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);