@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
@@ -1,14 +1,15 @@
1
+ import { createInferenceSendBudget } from "../inference/context";
1
2
  import type { ResponsesRequestContext } from "./core-options";
2
- import { createRequestExecutionBudget, isRequestExecutionBudget } from "../../lib/request-execution-budget";
3
+ import { claimDispatchSpendProof, createPhysicalSendReporter, createRequestExecutionBudget, isRequestExecutionBudget, reportDispatchSends } from "../../lib/request-execution-budget";
3
4
  import {
4
5
  chargeWorkflowSends,
5
6
  workflowSendCeilingReached,
6
7
  workflowSpendCeilingReached,
7
8
  } from "../../lib/workflow-budget";
8
- import { workflowRefusalResponse } from "../workflow-refusal";
9
+ import { poolContinuityRefusalReason, workflowRefusalResponse } from "../workflow-refusal";
9
10
  import type { AttemptRecoveryKind, AttemptRecoveryWithheld } from "../../usage/log";
10
11
  import { noteAttemptRecoveryWithheld, noteAttemptSend } from "../request-log";
11
- import { TRANSIENT_RETRY_MAX_ATTEMPTS } from "../../lib/upstream-retry";
12
+ import { TRANSIENT_RETRY_MAX_ATTEMPTS, SendBudgetExhaustedError } from "../../lib/upstream-retry";
12
13
  import type {
13
14
  DispatchDecision,
14
15
  DispatchIntent,
@@ -52,17 +53,42 @@ export function createResponsesSendBudget(
52
53
  // fresh default of 3. It is now a holder carried on options, so a combo child inherits the
53
54
  // parent's spend instead of starting over per target -- both halves of the measured
54
55
  // amplification in #4546.
55
- const sendBudget = options.sendBudget ?? createRequestExecutionBudget();
56
+ const sendBudget = options.sendBudget ?? createInferenceSendBudget(req, logCtx);
56
57
  // The root workflow is the user-visible task. A per-request cap cannot bound a fan-out that
57
58
  // sends once per child seven hundred times, so every send charged to the request is charged
58
59
  // to the root as well (#4546).
59
60
  const workflowRootId = req.headers.get("x-codex-parent-thread-id")?.trim() || undefined;
60
- const noteTransientSends = (used: number): void => {
61
+ // A child that owns its Combo booking settles that exact permit through the reporter, even
62
+ // when its caller handed ownership over only as `comboInitialSend`.
63
+ const inheritedPermit = options.compactionRecoveryPermit ?? options.comboDispatchPermit
64
+ ?? options.comboInitialSend?.permit;
65
+ let pendingHopPermit: SingleUseDispatchPermit | undefined = options.compactionRecoveryPermit;
66
+ let initialPermit = options.comboInitialSend?.permit;
67
+ // Count this owner's physical sends, not later bookings made by other shared-ledger owners.
68
+ // Direct callers retain sends reported before this owner was constructed.
69
+ let targetSendsUsed = initialPermit ? 0 : Math.max(0,
70
+ sendBudget.used - (options.compactionRecoveryPermit ? 1 : 0));
71
+ /** Attribute reported physical sends to this owner; the prepaid booking is spent once reported. */
72
+ const noteTargetSends = (charged: number): void => {
73
+ targetSendsUsed += charged;
74
+ if (charged > 0) initialPermit = undefined;
75
+ };
76
+ const recordTransientSends = (used: number, permit?: SingleUseDispatchPermit): void => {
61
77
  const charged = Math.max(0, used);
62
- sendBudget.used += charged;
78
+ noteTargetSends(charged);
79
+ reportDispatchSends(sendBudget, charged, permit);
63
80
  options.onCompactionRecoverySendsReported?.(charged);
64
81
  chargeWorkflowSends(workflowRootId, charged);
65
82
  };
83
+ const noteTransientSends = (used: number): void => recordTransientSends(used, pendingHopPermit ?? inheritedPermit);
84
+ // Capture at helper creation, before another leg can replace the pending handoff.
85
+ const transientSendReporter = (permit = pendingHopPermit ?? inheritedPermit) =>
86
+ createPhysicalSendReporter(sendBudget, () => ({ poolId: logCtx.spendPoolId ?? logCtx.provider,
87
+ identityId: logCtx.accountLogLabel }), permit, charged => {
88
+ noteTargetSends(Math.max(0, charged));
89
+ options.onCompactionRecoverySendsReported?.(charged);
90
+ chargeWorkflowSends(workflowRootId, charged);
91
+ });
66
92
  // Refused before any dispatch, and deliberately not by evicting the root's ledger entry:
67
93
  // dropping the record to make room would hand the fan-out a fresh allowance, which is the
68
94
  // laundering this ceiling exists to stop. The client is told the task needs a new grant
@@ -78,7 +104,17 @@ export function createResponsesSendBudget(
78
104
  // returns -- a refusal an operator cannot tell from an ordinary budget exhaustion, on a
79
105
  // ceiling they configured themselves. Asked before dispatch, it names the scope and the
80
106
  // number instead. Returns undefined and touches no ledger when no ceiling is configured.
81
- const spentCeiling = workflowSpendCeilingReached(workflowRootId);
107
+ // Passthrough reports sends after they leave. Invalid or conflicting identity evidence
108
+ // still refuses here as well as in reserve(), including requests with no workflow root.
109
+ // Positive unbound balances alone are instead overlaid on each candidate pool below.
110
+ const continuityRefusal = poolContinuityRefusalReason();
111
+ if (continuityRefusal) {
112
+ return workflowRefusalResponse(continuityRefusal, logCtx, undefined, workflowRootId);
113
+ }
114
+ const prepaid = isRequestExecutionBudget(sendBudget)
115
+ ? claimDispatchSpendProof(sendBudget, options.compactionRecoveryPermit ?? options.comboDispatchPermit) : undefined;
116
+ const admissionPolicy = isRequestExecutionBudget(sendBudget) ? sendBudget.spendAdmissionPolicy : undefined;
117
+ const spentCeiling = workflowSpendCeilingReached(workflowRootId, undefined, logCtx.spendPoolId ?? logCtx.provider, prepaid, admissionPolicy);
82
118
  if (spentCeiling) {
83
119
  return workflowRefusalResponse(
84
120
  "workflow-spend-exhausted",
@@ -115,6 +151,7 @@ export function createResponsesSendBudget(
115
151
  // Ordinal 1 is skipped because the caller normally records it before dispatch. An adapter
116
152
  // that reports every send asks for it to be counted here instead, so that the first send is
117
153
  // logged where it actually happens rather than before admission could still refuse it.
154
+ targetSendsUsed += 1;
118
155
  if (send.ordinal <= 1 && options.includeFirst !== true) return;
119
156
  noteAttemptSend(logCtx.activeAttempt, inputTokens, send.recovery);
120
157
  };
@@ -157,7 +194,6 @@ export function createResponsesSendBudget(
157
194
  * refused and the request would answer with a synthetic 502 in place of the real 429 the hop
158
195
  * was recovering from.
159
196
  */
160
- let pendingHopPermit: SingleUseDispatchPermit | undefined = options.compactionRecoveryPermit;
161
197
  /**
162
198
  * The budget an adapter's OWN dispatch ladder reserves against.
163
199
  *
@@ -171,19 +207,29 @@ export function createResponsesSendBudget(
171
207
  * The hop hands its reservation down through `pendingHopPermit`, the same seam the
172
208
  * passthrough ladder already uses, and this view spends it on the adapter's FIRST
173
209
  * reservation. Every later send in that ladder is a new physical send and is charged
174
- * normally. A permit the adapter takes but never sends under is released through the same
175
- * call it would have used for a reservation of its own, so an abandoned replay is refunded
176
- * rather than left charged.
210
+ * normally. Claiming preserves the refundable single-use booking until physical dispatch;
211
+ * an abandoned replay releases only that exact booking.
177
212
  */
178
- const adapterDispatchBudget: RequestExecutionBudget | undefined = adapterSendBudget === undefined
179
- ? undefined
180
- : adapterDispatchBudgetView(adapterSendBudget, {
181
- claimHopPermit: () => {
182
- const permit = pendingHopPermit;
183
- pendingHopPermit = undefined;
184
- return permit;
185
- },
186
- });
213
+ const permits = {
214
+ /** Transfer the initial booking once to the adapter dispatch owner. */
215
+ claimInitialPermit: () => {
216
+ const permit = initialPermit;
217
+ initialPermit = undefined;
218
+ return permit;
219
+ },
220
+ /** Transfer a pending recovery booking once, without creating a new allowance. */
221
+ claimHopPermit: () => {
222
+ const permit = pendingHopPermit;
223
+ pendingHopPermit = undefined;
224
+ return permit;
225
+ },
226
+ };
227
+ const adapterDispatchBudget = adapterSendBudget === undefined
228
+ ? undefined : adapterDispatchBudgetView(adapterSendBudget, permits);
229
+ // Preserve the existing opted-in view for direct callers, including its admitted replacement
230
+ // of an already-settled hop. Exact-owned Combo and sidecar handoffs use the ordinary view.
231
+ const refundableAdapterDispatchBudget = adapterSendBudget === undefined
232
+ ? undefined : adapterDispatchBudgetView(adapterSendBudget, permits, true);
187
233
  /**
188
234
  * How many sends a recovery leg may make, and the permit that authorises the last one.
189
235
  *
@@ -268,17 +314,41 @@ export function createResponsesSendBudget(
268
314
  return {
269
315
  workflowRootId,
270
316
  noteTransientSends,
317
+ transientSendReporter,
271
318
  remainingTransientSendBudget,
319
+ /** Only this child's prepaid send, not every pending external booking, is spendable here. */
320
+ initialSendAllowance: (cap: number): number => Math.min(cap,
321
+ remainingTransientSendBudget(cap) + (initialPermit ? 1 : 0)),
322
+ /** At final executor admission, settle only the owning booking or reserve this send. */
323
+ noteInitialDispatch: (prepaid = initialPermit): void => {
324
+ if (prepaid) {
325
+ if (!prepaid.assumeCharge()) throw new SendBudgetExhaustedError();
326
+ if (prepaid === initialPermit) initialPermit = undefined;
327
+ } else if (adapterSendBudget && (options.comboInitialSend || options.compactionRecoveryPermit)) {
328
+ const decision = adapterSendBudget.reserveDispatch({ sendClass: "transient",
329
+ targetKey: adapterSendBudget.lastTargetKey ?? `${logCtx.provider}/${logCtx.model}` });
330
+ if (!decision.allowed || !decision.permit.use()) throw new SendBudgetExhaustedError();
331
+ } else {
332
+ // Legacy direct callers can prebook without handing over a permit; keep their reporter.
333
+ noteTransientSends(1);
334
+ return;
335
+ }
336
+ targetSendsUsed += 1;
337
+ options.onCompactionRecoverySendsReported?.(1);
338
+ chargeWorkflowSends(workflowRootId, 1);
339
+ },
340
+ /** Physical sends reported by this owner; unrelated shared reservations cannot consume it. */
341
+ get targetSendsUsed(): number { return targetSendsUsed; },
272
342
  /**
273
- * Physical sends this logical request has already made.
343
+ * Charged sends, including open prepaid reservations, across this logical request.
274
344
  *
275
- * A live getter, not a snapshot: it is read once per dispatch leg to resolve a configured
276
- * ladder, and a value frozen at construction would answer for a request that had sent
277
- * nothing.
345
+ * A live getter, not a completed-send snapshot. Initial ladders use the child's own
346
+ * prepaid allowance and target-local count; shared admission keeps every booking spent.
278
347
  */
279
348
  get sendsUsed(): number { return sendBudget.used; },
280
349
  adapterSendBudget,
281
350
  adapterDispatchBudget,
351
+ refundableAdapterDispatchBudget,
282
352
  noteAdapterPhysicalSend,
283
353
  noteAdapterRecoveryWithheld,
284
354
  sendBudgetExhausted,
@@ -311,9 +381,21 @@ export type ResponsesSendBudget = Exclude<ReturnType<typeof createResponsesSendB
311
381
  */
312
382
  function adapterDispatchBudgetView(
313
383
  budget: RequestExecutionBudget,
314
- hop: { claimHopPermit: () => SingleUseDispatchPermit | undefined },
384
+ hop: {
385
+ claimInitialPermit: () => SingleUseDispatchPermit | undefined;
386
+ claimHopPermit: () => SingleUseDispatchPermit | undefined;
387
+ },
388
+ legacyHopReplacement = false,
315
389
  ): RequestExecutionBudget {
316
390
  return {
391
+ get physicalStarted() { return budget.physicalStarted; },
392
+ get physicalLimit() { return budget.physicalLimit; },
393
+ get spendEnforced() { return budget.spendEnforced; },
394
+ get spendPolicyStarted() { return budget.spendPolicyStarted; },
395
+ get spendAdmissionPolicy() { return budget.spendAdmissionPolicy; },
396
+ startRequest: target => budget.startRequest?.(target),
397
+ claimPhysicalSend: () => budget.claimPhysicalSend?.(),
398
+ beginSpendProducer: () => budget.beginSpendProducer?.(),
317
399
  get used(): number { return budget.used; },
318
400
  set used(next: number) { budget.used = next; },
319
401
  logicalRequestId: budget.logicalRequestId,
@@ -330,6 +412,19 @@ function adapterDispatchBudgetView(
330
412
  // A dispatch whose upstream state is unknown is refused on its own merits. A hop that
331
413
  // already paid does not make an unsafe replay safe, so that check stays with the budget.
332
414
  if (intent.replaySafe !== false) {
415
+ // The Combo reserves a logical provider/model identity. The child's first actual
416
+ // endpoint is not a recovery transition; retain the original refundable permit.
417
+ const initialPermit = hop.claimInitialPermit();
418
+ if (initialPermit) {
419
+ budget.bindPrepaidTarget?.(intent.targetKey);
420
+ return { allowed: true, permit: {
421
+ sendClass: initialPermit.sendClass,
422
+ execute: run => initialPermit.execute ? initialPermit.execute(run) : run(),
423
+ use: () => initialPermit.assumeCharge(),
424
+ assumeCharge: () => initialPermit.assumeCharge(),
425
+ release: () => initialPermit.release(),
426
+ } };
427
+ }
333
428
  const hopPermit = hop.claimHopPermit();
334
429
  if (hopPermit && intent.targetKey !== budget.lastTargetKey) {
335
430
  // The rotated credential can select a different regional endpoint. Replace the
@@ -338,32 +433,37 @@ function adapterDispatchBudgetView(
338
433
  hopPermit.release();
339
434
  return budget.reserveDispatch({ ...intent, sendClass: hopPermit.sendClass });
340
435
  }
341
- // Confirmed here rather than in `use()`: the adapter reserves immediately before it
342
- // opens the transport, which is the same boundary the hop's own confirmation uses.
343
- // A permit some other leg already settled returns false, and this falls through to a
344
- // real reservation rather than handing the adapter a dead permit -- an adapter whose
345
- // `use()` fails treats the request as exhausted and stops sending entirely.
346
- if (hopPermit !== undefined && hopPermit.assumeCharge()) {
347
- let spent = false;
348
- return {
349
- allowed: true,
350
- permit: {
436
+ // Reservation precedes pacing and beforeDispatch. Transfer the exact open booking;
437
+ // only physical consumption closes external reporting and makes it non-refundable.
438
+ // The underlying permit enforces single use, including settlement by another owner.
439
+ if (hopPermit !== undefined) {
440
+ if (legacyHopReplacement) {
441
+ let settled = false;
442
+ let executionPermit = hopPermit;
443
+ const confirm = (): boolean => {
444
+ if (settled) return false;
445
+ settled = true;
446
+ if (hopPermit.assumeCharge()) return true;
447
+ // Preserve the old fallback for a permit another leg already settled.
448
+ const fresh = budget.reserveDispatch(intent);
449
+ if (!fresh.allowed) return false;
450
+ executionPermit = fresh.permit;
451
+ return fresh.permit.assumeCharge();
452
+ };
453
+ return { allowed: true, permit: {
351
454
  sendClass: hopPermit.sendClass,
352
- use: (): boolean => {
353
- if (spent) return false;
354
- spent = true;
355
- return true;
356
- },
357
- assumeCharge: (): boolean => {
358
- if (spent) return false;
359
- spent = true;
360
- return true;
361
- },
362
- // The hop's charge is already settled and belongs to the leg that asked for it,
363
- // so there is nothing here to refund.
364
- release: (): void => {},
365
- },
366
- };
455
+ execute: run => executionPermit.execute ? executionPermit.execute(run) : run(),
456
+ use: confirm, assumeCharge: confirm,
457
+ release: () => { if (!settled) { settled = true; hopPermit.release(); } },
458
+ } };
459
+ }
460
+ return { allowed: true, permit: {
461
+ sendClass: hopPermit.sendClass,
462
+ execute: run => hopPermit.execute ? hopPermit.execute(run) : run(),
463
+ use: () => hopPermit.assumeCharge(),
464
+ assumeCharge: () => hopPermit.assumeCharge(),
465
+ release: () => hopPermit.release(),
466
+ } };
367
467
  }
368
468
  }
369
469
  return budget.reserveDispatch(intent);