@bitkyc08/opencodex 2.83.0 → 2.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (405) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-BjlttVLy.js +58 -0
  4. package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
  5. package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
  6. package/gui/dist/assets/index-BMssP4k3.js +94 -0
  7. package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/package.json +2 -3
  10. package/src/adapters/anthropic/beta-allowlist.ts +87 -8
  11. package/src/adapters/anthropic/client-identity.ts +1 -1
  12. package/src/adapters/anthropic/passthrough.ts +33 -4
  13. package/src/adapters/azure.ts +19 -1
  14. package/src/adapters/client-fingerprint.ts +24 -0
  15. package/src/adapters/coding-agent/protocol.ts +15 -8
  16. package/src/adapters/coding-agent/turn.ts +22 -9
  17. package/src/adapters/cursor/arg-codec.ts +17 -5
  18. package/src/adapters/cursor/catalog.ts +8 -0
  19. package/src/adapters/cursor/effort-map.ts +3 -0
  20. package/src/adapters/cursor/freeform-args.ts +75 -0
  21. package/src/adapters/cursor/protobuf-events.ts +36 -23
  22. package/src/adapters/google-antigravity-wire.ts +22 -1
  23. package/src/adapters/google.ts +2 -2
  24. package/src/adapters/ollama-native.ts +29 -1
  25. package/src/adapters/openai-chat/messages.ts +8 -4
  26. package/src/adapters/openai-chat/response-events.ts +59 -1
  27. package/src/adapters/openai-chat.ts +8 -5
  28. package/src/adapters/openai-responses/empty-assistant.ts +32 -0
  29. package/src/adapters/openai-responses/passthrough.ts +3 -0
  30. package/src/adapters/openai-responses/request-strips.ts +10 -2
  31. package/src/adapters/qoder/adapter.ts +56 -15
  32. package/src/advisor/consult.ts +220 -0
  33. package/src/advisor/context.ts +226 -0
  34. package/src/advisor/disclosure.ts +20 -0
  35. package/src/advisor/runtime.ts +305 -0
  36. package/src/advisor/settings.ts +135 -0
  37. package/src/advisor/state.ts +369 -0
  38. package/src/advisor/synthetic-tool.ts +33 -0
  39. package/src/bridge/errors.ts +24 -0
  40. package/src/bridge.ts +1 -1
  41. package/src/chat/inbound.ts +30 -6
  42. package/src/chat/outbound.ts +5 -0
  43. package/src/claude/context-windows.ts +54 -1
  44. package/src/claude/desktop-3p-library.ts +43 -3
  45. package/src/claude/desktop-3p.ts +22 -8
  46. package/src/claude/desktop-picker-profile.ts +11 -4
  47. package/src/claude/desktop-profile.ts +10 -1
  48. package/src/claude/desktop-remote-store-io.ts +5 -3
  49. package/src/claude/inbound.ts +121 -50
  50. package/src/claude/intercept/connect-proxy.ts +10 -3
  51. package/src/claude/intercept/local-ca.ts +8 -1
  52. package/src/claude/intercept/picker-ca-persistence.ts +3 -1
  53. package/src/claude/intercept/picker-listener.ts +49 -1
  54. package/src/claude/intercept/settings-no-proxy.ts +146 -0
  55. package/src/claude/intercept/settings.ts +101 -9
  56. package/src/claude/model-default-settings.ts +154 -0
  57. package/src/claude/model-info.ts +7 -7
  58. package/src/claude/outbound.ts +13 -6
  59. package/src/cli/account-api.ts +65 -0
  60. package/src/cli/account-auth.ts +26 -2
  61. package/src/cli/account.ts +41 -5
  62. package/src/cli/advisor.ts +151 -0
  63. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  64. package/src/cli/capabilities-access-remote.ts +8 -0
  65. package/src/cli/capabilities-agents-routing.ts +18 -0
  66. package/src/cli/capabilities-base.ts +14 -2
  67. package/src/cli/catalog-command-result.ts +2 -1
  68. package/src/cli/chatgpt-command.ts +35 -14
  69. package/src/cli/claude.ts +60 -11
  70. package/src/cli/config-command.ts +8 -2
  71. package/src/cli/debug.ts +8 -0
  72. package/src/cli/dispatch.ts +21 -4
  73. package/src/cli/doctor.ts +9 -0
  74. package/src/cli/ensure-desktop-cold-start.ts +329 -0
  75. package/src/cli/export-command.ts +15 -2
  76. package/src/cli/help.ts +2 -1
  77. package/src/cli/hub-disable.ts +99 -0
  78. package/src/cli/hub.ts +4 -2
  79. package/src/cli/index.ts +26 -8
  80. package/src/cli/integration-plan-dto.ts +2 -0
  81. package/src/cli/link-child.ts +104 -0
  82. package/src/cli/link-enrollment.ts +96 -0
  83. package/src/cli/link.ts +39 -24
  84. package/src/cli/observe-stream.ts +6 -1
  85. package/src/cli/observe.ts +10 -2
  86. package/src/cli/provider.ts +12 -0
  87. package/src/cli/registry.ts +20 -7
  88. package/src/cli/restart-scope.ts +3 -1
  89. package/src/cli/runtime-api.ts +27 -2
  90. package/src/cli/status.ts +104 -20
  91. package/src/client/connect.ts +39 -4
  92. package/src/client/hub-client.ts +14 -7
  93. package/src/client/hub-state.ts +25 -14
  94. package/src/client/link-home.ts +248 -0
  95. package/src/client/link-management-relay.ts +61 -0
  96. package/src/client/link-relay.ts +6 -1
  97. package/src/client/link-status.ts +14 -0
  98. package/src/client/machine-api.ts +14 -7
  99. package/src/client/machine-listener.ts +29 -4
  100. package/src/clients/config-export/contracts.ts +1 -0
  101. package/src/clients/config-export/model-metadata.ts +3 -2
  102. package/src/clients/config-export/qoder.ts +93 -0
  103. package/src/clients/config-export.ts +59 -16
  104. package/src/clients/omo-role-models.ts +6 -3
  105. package/src/codex/agent-role-models.ts +26 -8
  106. package/src/codex/auth-api/account-list.ts +17 -2
  107. package/src/codex/auth-api/account-selection.ts +17 -7
  108. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  109. package/src/codex/auth-api/routes.ts +5 -1
  110. package/src/codex/auth-context.ts +79 -5
  111. package/src/codex/autostart-health.ts +7 -1
  112. package/src/codex/catalog/build-entries.ts +11 -2
  113. package/src/codex/catalog/bundled.ts +5 -1
  114. package/src/codex/catalog/configured-tiers.ts +50 -0
  115. package/src/codex/catalog/effort.ts +6 -3
  116. package/src/codex/catalog/metadata.ts +4 -2
  117. package/src/codex/catalog/model-hints.ts +21 -3
  118. package/src/codex/catalog/native-models.ts +16 -2
  119. package/src/codex/catalog/native-ultrafast.ts +106 -0
  120. package/src/codex/catalog/parsing.ts +5 -0
  121. package/src/codex/catalog/provider-models.ts +4 -0
  122. package/src/codex/catalog/retained-sync.ts +2 -0
  123. package/src/codex/catalog/routed-gather.ts +22 -4
  124. package/src/codex/catalog/vendor-context.ts +82 -0
  125. package/src/codex/config-write-lock.ts +40 -2
  126. package/src/codex/convergence.ts +2 -0
  127. package/src/codex/desktop-app/types.ts +2 -0
  128. package/src/codex/desktop-app/windows.ts +29 -16
  129. package/src/codex/desktop-app-restart.ts +4 -2
  130. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  131. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  132. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  133. package/src/codex/desktop-compatibility/installed-build.ts +55 -0
  134. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  135. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  136. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  137. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  138. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  139. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  140. package/src/codex/desktop-compatibility/runtime.ts +173 -0
  141. package/src/codex/desktop-compatibility/service.ts +30 -0
  142. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  143. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  144. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  145. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  146. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  147. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  148. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  149. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  150. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  151. package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
  152. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  153. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  154. package/src/codex/history-provider.ts +1 -1
  155. package/src/codex/inject/config-write-section.ts +4 -1
  156. package/src/codex/inject/remove.ts +18 -6
  157. package/src/codex/journal.ts +16 -1
  158. package/src/codex/main-account.ts +99 -15
  159. package/src/codex/native-main-refresh-mode.ts +217 -0
  160. package/src/codex/native-profile-startup.ts +4 -1
  161. package/src/codex/prompt-journal.ts +20 -2
  162. package/src/codex/prompt-layers/encoding.ts +49 -0
  163. package/src/codex/prompt-layers/import-source.ts +30 -0
  164. package/src/codex/prompt-layers/toml-edit.ts +269 -37
  165. package/src/codex/prompt-layers/toml-read.ts +52 -4
  166. package/src/codex/prompt-layers.ts +383 -20
  167. package/src/codex/prompt-lock-claim.ts +13 -3
  168. package/src/codex/prompt-lock-evidence.ts +3 -3
  169. package/src/codex/prompt-lock-owner.ts +14 -6
  170. package/src/codex/prompt-text-probe.ts +34 -5
  171. package/src/codex/quota.ts +13 -6
  172. package/src/codex/role-auto-assign.ts +2 -5
  173. package/src/codex/routing/active-account.ts +21 -0
  174. package/src/codex/routing/health-store.ts +1 -1
  175. package/src/codex/routing/pool-recovery.ts +79 -0
  176. package/src/codex/routing.ts +1 -1
  177. package/src/codex/runtime.ts +196 -8
  178. package/src/codex/shim-diagnostics.ts +6 -0
  179. package/src/codex/shim-overlay.ts +15 -6
  180. package/src/codex/shim.ts +1 -1
  181. package/src/combos/failover.ts +3 -0
  182. package/src/config/api-key-reconcile.ts +34 -0
  183. package/src/config/diagnostics.ts +18 -1
  184. package/src/config/feature-flags.ts +5 -0
  185. package/src/config/live-reconcile.ts +56 -29
  186. package/src/config/live-topology.ts +29 -0
  187. package/src/config/load-degrade.ts +8 -2
  188. package/src/config/paths.ts +13 -3
  189. package/src/config/salvage.ts +3 -1
  190. package/src/config/schema/config-schema.ts +19 -0
  191. package/src/config/schema/desktop-compatibility.ts +9 -0
  192. package/src/config/schema/leaf-validators.ts +1 -0
  193. package/src/generated/compatibility-version.json +713 -285
  194. package/src/generated/model-metadata.ts +1 -1
  195. package/src/grok/grpc-web.ts +23 -1
  196. package/src/grok/reset-coupon-ledger.ts +75 -5
  197. package/src/grok/reset-coupons.ts +4 -2
  198. package/src/integrations/catalog-refresh.ts +1 -1
  199. package/src/integrations/config-io.ts +20 -2
  200. package/src/integrations/journal.ts +15 -1
  201. package/src/integrations/mutation-plan.ts +14 -7
  202. package/src/integrations/registry.ts +52 -11
  203. package/src/integrations/state.ts +19 -7
  204. package/src/integrations/store.ts +3 -0
  205. package/src/integrations/target.ts +42 -5
  206. package/src/integrations/writer.ts +24 -7
  207. package/src/lib/advisor-activation.ts +16 -0
  208. package/src/lib/app-owned-memory-stores.ts +4 -0
  209. package/src/lib/app-owned-memory.ts +11 -0
  210. package/src/lib/background-work.ts +65 -0
  211. package/src/lib/bounded-file-read.ts +58 -0
  212. package/src/lib/bun-path-runtime.d.mts +10 -1
  213. package/src/lib/bun-path-runtime.mjs +62 -3
  214. package/src/lib/desktop-proxy-route.ts +30 -0
  215. package/src/lib/desktop-upstream-tunnel.ts +107 -0
  216. package/src/lib/errors.ts +7 -0
  217. package/src/lib/json-byte-size.ts +31 -0
  218. package/src/lib/local-internal-call-capability.ts +55 -0
  219. package/src/lib/optional-shutdown-hooks.ts +35 -0
  220. package/src/lib/outbound-credential-registry.ts +210 -0
  221. package/src/lib/package-tree-integrity.ts +2 -2
  222. package/src/lib/passive-read.ts +35 -0
  223. package/src/lib/shadow-call.ts +44 -5
  224. package/src/lib/socks5-fetch.ts +48 -71
  225. package/src/lib/socks5-handshake.ts +112 -0
  226. package/src/lib/translator-budget.ts +7 -1
  227. package/src/link/codex-settings.ts +155 -0
  228. package/src/link/management-routes.ts +89 -0
  229. package/src/link/remote-probe.ts +38 -0
  230. package/src/link/ssh-argv.ts +26 -5
  231. package/src/link/ssh-runner.ts +58 -27
  232. package/src/link/status-projection.ts +22 -4
  233. package/src/link/supervisor.ts +144 -49
  234. package/src/link/tunnel-state.ts +11 -4
  235. package/src/oauth/anthropic-continuity.ts +4 -3
  236. package/src/oauth/anthropic-identity.ts +13 -2
  237. package/src/oauth/anthropic-model-quota.ts +13 -1
  238. package/src/oauth/anthropic-routing.ts +230 -23
  239. package/src/oauth/anthropic.ts +18 -4
  240. package/src/oauth/generic-account-failover.ts +29 -5
  241. package/src/oauth/google-antigravity.ts +24 -7
  242. package/src/oauth/index.ts +28 -17
  243. package/src/oauth/kiro-account-identity.ts +106 -0
  244. package/src/oauth/kiro-device-login.ts +2 -1
  245. package/src/oauth/kiro.ts +55 -6
  246. package/src/oauth/plan.ts +29 -0
  247. package/src/oauth/store.ts +40 -3
  248. package/src/oauth/types.ts +2 -0
  249. package/src/plugins/loader.ts +11 -0
  250. package/src/protocols/codecs/chat-ir.ts +467 -0
  251. package/src/protocols/codecs/chat.ts +10 -1
  252. package/src/protocols/codecs/messages-ir.ts +500 -0
  253. package/src/protocols/features.ts +10 -2
  254. package/src/protocols/path.ts +11 -1
  255. package/src/protocols/plan-snapshot.ts +39 -16
  256. package/src/protocols/plan.ts +9 -1
  257. package/src/protocols/settings.ts +8 -0
  258. package/src/providers/anthropic-billing-ownership.ts +74 -0
  259. package/src/providers/anthropic-billing-recovery.ts +31 -0
  260. package/src/providers/anthropic-billing-refusal.ts +55 -0
  261. package/src/providers/anthropic-reset-admission.ts +62 -0
  262. package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
  263. package/src/providers/antigravity-account-roster.ts +102 -0
  264. package/src/providers/antigravity-models.ts +85 -7
  265. package/src/providers/api-key-resolve.ts +17 -2
  266. package/src/providers/key-failover.ts +52 -5
  267. package/src/providers/model-rename-fields.ts +2 -0
  268. package/src/providers/model-rename-migration.ts +5 -3
  269. package/src/providers/model-rename-startup.ts +5 -3
  270. package/src/providers/quota/account-cache.ts +11 -0
  271. package/src/providers/quota/anthropic-account-quota.ts +4 -2
  272. package/src/providers/quota/report-cache.ts +14 -0
  273. package/src/providers/quota/vendor-probes-oauth.ts +3 -3
  274. package/src/providers/quota.ts +4 -1
  275. package/src/providers/registry/entries-core.ts +21 -20
  276. package/src/providers/registry/entries-extended.ts +19 -1
  277. package/src/providers/registry/model-seeds.ts +17 -0
  278. package/src/providers/request-pacing.ts +9 -0
  279. package/src/providers/stale-no-vision-seed-migration.ts +72 -0
  280. package/src/responses/compaction.ts +14 -0
  281. package/src/responses/parser.ts +37 -20
  282. package/src/responses/state.ts +32 -29
  283. package/src/routing/history/build-file.ts +92 -0
  284. package/src/routing/history/indexer.ts +248 -216
  285. package/src/routing/history/ingest.ts +238 -0
  286. package/src/routing/history/rebuild-job.ts +111 -0
  287. package/src/routing/history/rebuild-protocol.ts +15 -0
  288. package/src/routing/history/rebuild-worker.ts +34 -0
  289. package/src/server/admission-model-scope.ts +4 -1
  290. package/src/server/antigravity-direct-eligibility.ts +208 -0
  291. package/src/server/auth-cors.ts +46 -0
  292. package/src/server/chat-completions.ts +75 -9
  293. package/src/server/claude-messages.ts +110 -20
  294. package/src/server/configured-credentials.ts +194 -0
  295. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  296. package/src/server/index/link-management.ts +58 -0
  297. package/src/server/index/optional-listeners.ts +5 -1
  298. package/src/server/index/serve-options.ts +15 -2
  299. package/src/server/index/startup-warnings.ts +9 -0
  300. package/src/server/index/websocket-handler.ts +9 -0
  301. package/src/server/index.ts +13 -10
  302. package/src/server/inference/client-encoder-delivery.ts +6 -5
  303. package/src/server/lifecycle.ts +2 -1
  304. package/src/server/login-shell-path.ts +146 -0
  305. package/src/server/management/advisor-routes.ts +191 -0
  306. package/src/server/management/agent-settings-routes.ts +50 -14
  307. package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
  308. package/src/server/management/codex-agent-role-routes.ts +19 -6
  309. package/src/server/management/codex-prompt-routes.ts +136 -5
  310. package/src/server/management/codex-role-auto-assign.ts +1 -1
  311. package/src/server/management/companion-routes.ts +4 -0
  312. package/src/server/management/config-routes.ts +2 -2
  313. package/src/server/management/context.ts +4 -0
  314. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  315. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  316. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  317. package/src/server/management/grok-coupon-routes.ts +78 -27
  318. package/src/server/management/link-enrollment.ts +192 -0
  319. package/src/server/management/link-route-state.ts +185 -0
  320. package/src/server/management/link-routes.ts +82 -402
  321. package/src/server/management/model-routes.ts +2 -2
  322. package/src/server/management/model-rows.ts +3 -1
  323. package/src/server/management/protocol-settings-patch.ts +1 -0
  324. package/src/server/management/provider-overwrite-carry.ts +10 -0
  325. package/src/server/management/provider-routes.ts +1 -0
  326. package/src/server/management/route-registry.ts +16 -3
  327. package/src/server/management/shared.ts +3 -0
  328. package/src/server/management/sibling-guard.ts +1 -0
  329. package/src/server/management-api.ts +9 -1
  330. package/src/server/messages-antigravity-direct.ts +195 -0
  331. package/src/server/messages-native.ts +73 -43
  332. package/src/server/relay-eager.ts +2 -0
  333. package/src/server/request-log-filter.ts +6 -1
  334. package/src/server/request-log-terminal-status.ts +63 -6
  335. package/src/server/request-log.ts +7 -0
  336. package/src/server/responses/adapter-continuation.ts +35 -18
  337. package/src/server/responses/adapter-delivery.ts +24 -2
  338. package/src/server/responses/adapter-dispatch.ts +261 -81
  339. package/src/server/responses/advisor-plan-slot.ts +27 -0
  340. package/src/server/responses/advisor-slot.ts +336 -0
  341. package/src/server/responses/code-mode-collab-surface.ts +313 -0
  342. package/src/server/responses/codex-ws-exchange.ts +147 -6
  343. package/src/server/responses/codex-ws-wire.ts +203 -0
  344. package/src/server/responses/collaboration.ts +36 -7
  345. package/src/server/responses/compact.ts +126 -48
  346. package/src/server/responses/context-overflow.ts +13 -1
  347. package/src/server/responses/core-codex-account.ts +105 -1
  348. package/src/server/responses/core-combo-failure.ts +24 -1
  349. package/src/server/responses/core-combo.ts +28 -2
  350. package/src/server/responses/core-errors.ts +29 -1
  351. package/src/server/responses/core-lifetime.ts +2 -0
  352. package/src/server/responses/core-normalize.ts +11 -0
  353. package/src/server/responses/core-options.ts +48 -0
  354. package/src/server/responses/core-replay.ts +7 -4
  355. package/src/server/responses/core.ts +1 -1
  356. package/src/server/responses/deferred-collab-surface.ts +158 -0
  357. package/src/server/responses/deferred-send-reporter.ts +25 -0
  358. package/src/server/responses/fetch-helpers.ts +12 -1
  359. package/src/server/responses/passthrough-delivery.ts +20 -11
  360. package/src/server/responses/passthrough-dispatch.ts +29 -7
  361. package/src/server/responses/request-prepare.ts +74 -9
  362. package/src/server/responses/request-transport.ts +22 -8
  363. package/src/server/responses/run-turn-execution.ts +70 -8
  364. package/src/server/responses/sidecar-execution.ts +89 -5
  365. package/src/server/responses/terminal-guard.ts +2 -1
  366. package/src/server/responses/ws-upstream.ts +9 -1
  367. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  368. package/src/server/startup-action-control.ts +38 -4
  369. package/src/server/startup-health-cache.ts +5 -0
  370. package/src/server/system-env-shell.ts +7 -2
  371. package/src/server/system-env.ts +11 -1
  372. package/src/service/cli.ts +5 -1
  373. package/src/service/desktop-startup.ts +161 -34
  374. package/src/service/desktop-supervision.d.mts +27 -1
  375. package/src/service/desktop-supervision.mjs +111 -5
  376. package/src/service/health-sentinel-launch.ts +176 -0
  377. package/src/service/health-sentinel-main.ts +466 -0
  378. package/src/service/health-sentinel-policy.ts +241 -0
  379. package/src/service/health-sentinel-probe.ts +71 -0
  380. package/src/service/health-sentinel-windows.ts +87 -0
  381. package/src/service/linux-linger.ts +20 -0
  382. package/src/service/logging.ts +72 -0
  383. package/src/tray/windows-tray.ps1 +43 -24
  384. package/src/types/config.ts +68 -6
  385. package/src/types/provider.ts +7 -0
  386. package/src/types/request.ts +15 -0
  387. package/src/types/tools.ts +2 -0
  388. package/src/update/async-check.ts +1 -1
  389. package/src/update/badge.ts +2 -2
  390. package/src/update/index.ts +88 -15
  391. package/src/update/job-supervised-restart.ts +107 -0
  392. package/src/update/job.ts +32 -32
  393. package/src/update/notify.ts +1 -1
  394. package/src/update/refresh-scheduler.ts +2 -2
  395. package/src/update/supervised-recovery.d.mts +27 -0
  396. package/src/update/supervised-recovery.mjs +157 -0
  397. package/src/usage/cache-diagnostic.ts +2 -0
  398. package/src/usage/expected-prices.ts +16 -0
  399. package/src/usage/log.ts +19 -1
  400. package/src/web-search/gemini-executor.ts +2 -2
  401. package/assets/sponsors/packycode.png +0 -0
  402. package/gui/dist/assets/App-Ba8uG696.js +0 -54
  403. package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
  404. package/gui/dist/assets/index-BHRZdJuv.js +0 -94
  405. package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
@@ -1,3 +1,4 @@
1
+ import { deferPhysicalSendReporter } from "./deferred-send-reporter";
1
2
  import { spendLedgerStorageErrorResponse } from "./spend-storage-error";
2
3
  import { classifyAnthropic429 } from "../../oauth/anthropic-rate-limit-policy";
3
4
  import { rotateAnthropicAccountOnResponseForInstance } from "../../oauth/anthropic-account-refusal";
@@ -23,11 +24,13 @@ import {
23
24
  sealRequestAttemptIdentity,
24
25
  recordAttemptCredentialSource,
25
26
  } from "../request-log";
26
- import { clientCancelledResponse, readDisplaySafeErrorText, normalizeUpstreamErrorText } from "./core-errors";
27
+ import { anthropicPoolCooldownResponse, clientCancelledResponse, readDisplaySafeErrorText, normalizeUpstreamErrorText } from "./core-errors";
28
+ import { isTranslatorBudgetExceededError } from "../../lib/translator-budget";
27
29
  import { redactSecretString } from "../../lib/redact";
28
30
  import { rewriteUpstreamPolicyRefusal } from "./policy-refusal";
29
31
  import { withProviderRequestSlot } from "../../providers/request-pacing";
30
32
  import { providerFetch, fetchWithHeaderTimeout, safeHostLabel } from "./fetch-helpers";
33
+ import { isAnthropicBillingKeyPool, rotateAnthropicBillingKeyOnResponse } from "../../providers/anthropic-billing-recovery";
31
34
  import {
32
35
  transientRetryPolicyFor,
33
36
  resetReplayPolicyFor,
@@ -91,7 +94,8 @@ import {
91
94
  } from "../../providers/opencode-zen-rate-limit";
92
95
  import { planReasoningEffortDowngrade } from "../../providers/reasoning-metadata";
93
96
  import { consumeComboFailure } from "./core-combo-failure";
94
- import { streamingContextOverflowResponse, jsonContextOverflowResponse } from "./context-overflow";
97
+ import { streamingContextOverflowResponse, jsonContextOverflowResponse, lateAttributionLimitResponse } from "./context-overflow";
98
+ import { OllamaLateAttributionBudgetError } from "../../adapters/ollama-native";
95
99
  import { isFixedCodexAccount } from "./core-codex-account";
96
100
  import { recordSubagentQuotaFailureForThreadSpawn } from "../../codex/subagent-model-fallback";
97
101
  import {
@@ -108,6 +112,7 @@ import { mapCodexAuthContextErrorToResponse } from "./codex-auth-error";
108
112
  import { chargeWorkflowSends } from "../../lib/workflow-budget";
109
113
  import { isAntigravityValidationRefusal } from "./antigravity-validation-refusal";
110
114
  import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
115
+ import { AdmissionModelDeniedError, admissionModelDeniedResponse } from "../admission-model-scope";
111
116
 
112
117
  /** One responsibility of the Responses request pipeline; state owners are explicit. */
113
118
  export async function prepareAdapterExchange(
@@ -221,8 +226,13 @@ export async function prepareAdapterExchange(
221
226
 
222
227
 
223
228
  let selfContainedJudgment: boolean | undefined;
229
+ // On the Antigravity direct lane the raw body carries only settings; judge the body the bridge
230
+ // would have sent, built only if an ambiguous reset actually asks.
231
+ const directRequest = options.directRequest;
224
232
  const requestIsSelfContained = (): boolean =>
225
- selfContainedJudgment ??= selfContainedResponsesBody(parsed._rawBody);
233
+ selfContainedJudgment ??= selfContainedResponsesBody(directRequest
234
+ ? { ...(parsed._rawBody as Record<string, unknown>), ...directRequest.bridgeConversation() }
235
+ : parsed._rawBody);
226
236
  const claimPreHeaderResend = (): boolean => authorizeResendForRecovery(
227
237
  "pre-header", "connection-reset",
228
238
  ambiguousResendAllowanceFor(route.provider, requestIsSelfContained, claimAmbiguousResend),
@@ -341,6 +351,14 @@ export async function prepareAdapterExchange(
341
351
  cleanupUpstreamAbort();
342
352
  upstream.abort();
343
353
  if (options.abortSignal?.aborted) return clientCancelledResponse();
354
+ if (err instanceof OllamaLateAttributionBudgetError) return lateAttributionLimitResponse();
355
+ // An adapter can also throw the shared translator budget while sizing generated
356
+ // request content (e.g. Ollama late-tool attribution); keep its established 413.
357
+ if (isTranslatorBudgetExceededError(err)) {
358
+ return formatErrorResponse(413, "request_too_large", "request translation buffer exceeded the safe limit", {
359
+ code: "translation_buffer_limit",
360
+ });
361
+ }
344
362
  const msg = err instanceof Error ? err.message : String(err);
345
363
  return formatErrorResponse(400, "invalid_request_error", redactSecretString(msg));
346
364
  }
@@ -362,6 +380,23 @@ export async function prepareAdapterExchange(
362
380
  : transportState.oauth401ReplayAttempted ? "oauth-401" : undefined
363
381
  : undefined;
364
382
  let upstreamResponse: Response;
383
+ // Preserve bounded terminal bytes without retaining the refused send's pacing lease.
384
+ // Reading the owned body to EOF (or cancelling on its bound) frees capacity BEFORE
385
+ // replacement admission; keeping that live body would wait on its own concurrency slot.
386
+ const retainFailureResponse = async (): Promise<Response> => {
387
+ const original = upstreamResponse;
388
+ const text = await readDisplaySafeErrorText(original, upstream.signal, "");
389
+ const headers = new Headers(original.headers);
390
+ headers.delete("content-length");
391
+ headers.delete("content-encoding");
392
+ upstreamResponse = new Response(text, { status: original.status, statusText: original.statusText, headers });
393
+ return upstreamResponse;
394
+ };
395
+ const billingPoolRequest = isAnthropicBillingKeyPool(route.provider);
396
+ let billingRecoveryActive = false;
397
+ const genericRefetchCountsSends = () => sendBudgetState.adapterSendBudget?.spendEnforced === true
398
+ || transientRetryPolicyFor(route.provider) !== null || resetReplayPolicyFor(route.provider) !== null
399
+ || billingPoolRequest || billingRecoveryActive;
365
400
  let handoffPermit: ResponsesSendBudget["pendingHopPermit"];
366
401
  try {
367
402
  if (transportState.activeAdapter.fetchResponse) {
@@ -434,6 +469,8 @@ export async function prepareAdapterExchange(
434
469
  const fetchWithRetryPolicy = (route.provider.adapter === "google" || transientPolicy)
435
470
  ? fetchWithTransientRetry
436
471
  : fetchWithResetRetry;
472
+ const initialReporter = transientSendReporter(compactPrepaid ?? options.comboDispatchPermit);
473
+ const deferredInitial = billingPoolRequest && !receiptMode ? deferPhysicalSendReporter(initialReporter) : undefined;
437
474
  try { upstreamResponse = await fetchWithRetryPolicy(
438
475
  recovery => {
439
476
  // Unconfigured generic sends may omit the counting reporter. Capture policy
@@ -442,10 +479,11 @@ export async function prepareAdapterExchange(
442
479
  poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
443
480
  });
444
481
  // Receipt mode books the physical send at executor admission instead of here.
445
- if (!receiptMode && compactPrepaid && !compactPrepaidUsed) {
482
+ if (!deferredInitial && !receiptMode && compactPrepaid && !compactPrepaidUsed) {
446
483
  if (!compactPrepaid.use()) throw new SendBudgetExhaustedError(safeHostLabel(builtInitialRequest.url));
447
484
  compactPrepaidUsed = true;
448
485
  }
486
+ // Key transport keeps this metadata pending until it binds the actual selection.
449
487
  transportState.noteRoutedAttemptSend(inputTokenEstimate, recovery ?? initialRecovery);
450
488
  return fetchWithHeaderTimeout(builtInitialRequest.url, applyUpstreamRecoveryInit({
451
489
  method: builtInitialRequest.method,
@@ -457,6 +495,13 @@ export async function prepareAdapterExchange(
457
495
  providerName: route.providerName,
458
496
  modelId: route.modelId,
459
497
  onPhysicalDispatch: () => {
498
+ if (deferredInitial) {
499
+ if (!deferredInitial.beforeDispatch()) throw new SendBudgetExhaustedError(safeHostLabel(builtInitialRequest.url));
500
+ if (compactPrepaid && !compactPrepaidUsed) {
501
+ if (!compactPrepaid.use()) throw new SendBudgetExhaustedError(safeHostLabel(builtInitialRequest.url));
502
+ compactPrepaidUsed = true;
503
+ }
504
+ }
460
505
  // Preserve the unconfigured direct-adapter accounting contract; Combo admission
461
506
  // and explicit retry/compaction budgets require a receipt for their booked send.
462
507
  if (receiptMode) {
@@ -473,6 +518,7 @@ export async function prepareAdapterExchange(
473
518
  label: safeHostLabel(builtInitialRequest.url),
474
519
  claimAmbiguousResend: claimPreHeaderResend,
475
520
  ...(sendBudgetState.adapterSendBudget?.spendEnforced || options.comboDispatchPermit || transientPolicy || resetPolicy || compactPrepaid || options.comboAttempt
521
+ || billingPoolRequest
476
522
  ? {
477
523
  // A pending compaction permit already booked this leg's first physical send.
478
524
  ...(transientPolicy || resetPolicy || compactPrepaid || options.comboAttempt
@@ -485,7 +531,7 @@ export async function prepareAdapterExchange(
485
531
  // settled by `onPhysicalDispatch`, so a reporter here would count them twice.
486
532
  ...(receiptMode
487
533
  ? {}
488
- : { onSendsConsumed: transientSendReporter(compactPrepaid ?? options.comboDispatchPermit) }),
534
+ : { onSendsConsumed: deferredInitial?.report ?? initialReporter }),
489
535
  }
490
536
  : {}),
491
537
  },
@@ -501,6 +547,10 @@ export async function prepareAdapterExchange(
501
547
  return storageRefusal;
502
548
  }
503
549
  const refusal = err instanceof UpstreamRetryEvidenceError ? err.cause : err;
550
+ if (refusal instanceof AdmissionModelDeniedError) {
551
+ releaseCodexAuthContextProbeLease(admissionState.authCtx);
552
+ return admissionModelDeniedResponse(refusal);
553
+ }
504
554
  const codexRefusal = mapCodexAuthContextErrorToResponse(unwrapUpstreamRetryEvidenceError(err), {
505
555
  now: Date.now(), accountSelector: route.codexAccountNamespace,
506
556
  });
@@ -516,8 +566,7 @@ export async function prepareAdapterExchange(
516
566
  return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(refusal));
517
567
  }
518
568
  if (refusal instanceof AnthropicAccountCooldownError) {
519
- return formatErrorResponse(429, "rate_limit_error", refusal.message,
520
- refusal.retryAfterSeconds === null ? undefined : { retryAfter: String(refusal.retryAfterSeconds) });
569
+ return anthropicPoolCooldownResponse(refusal, { comboAttempt: options.comboAttempt, logCtx });
521
570
  }
522
571
  // A budget refusal is a decision this process made, not an upstream fault. Reporting it as
523
572
  // 502 does more than mislabel it: the Codex client retries 5xx and does not retry a 429, so
@@ -604,13 +653,26 @@ export async function prepareAdapterExchange(
604
653
  recordAdapterReasoning(logCtx, retryRequest);
605
654
  recordAdapterTier(logCtx, retryRequest);
606
655
  } catch (err) {
607
- if (preserveFailureResponse && !options.abortSignal?.aborted) return { failed: preserveFailureResponse };
656
+ // A rotated credential that cannot be shaped keeps the refusal it was meant to
657
+ // replace. The image-tier rebuild is different: the request itself failed to shape,
658
+ // and its established 413/400 build-error contract stays authoritative.
659
+ if (preserveFailureResponse && recovery !== "image-413" && !options.abortSignal?.aborted) {
660
+ return { failed: preserveFailureResponse };
661
+ }
608
662
  // A rotated/rebuilt adapter build failure is a request-shaping error, not an
609
- // upstream connect failure: tear the abort link down and map it as 400 (no 413
610
- // translator-budget mapping here — that stays with parseRequest/buildToolBridgeMaps).
663
+ // upstream connect failure: tear the abort link down and map it as 400. A
664
+ // translator-budget refusal keeps its established 413 here as well — adapters
665
+ // can throw it while sizing generated content (e.g. Ollama late-tool
666
+ // attribution), not only parseRequest/buildToolBridgeMaps.
611
667
  cleanupUpstreamAbort();
612
668
  upstream.abort();
613
669
  if (options.abortSignal?.aborted) return { failed: clientCancelledResponse() };
670
+ if (err instanceof OllamaLateAttributionBudgetError) return { failed: lateAttributionLimitResponse() };
671
+ if (isTranslatorBudgetExceededError(err)) {
672
+ return { failed: formatErrorResponse(413, "request_too_large", "request translation buffer exceeded the safe limit", {
673
+ code: "translation_buffer_limit",
674
+ }) };
675
+ }
614
676
  const msg = err instanceof Error ? err.message : String(err);
615
677
  return { failed: formatErrorResponse(400, "invalid_request_error", redactSecretString(msg)) };
616
678
  }
@@ -673,7 +735,7 @@ export async function prepareAdapterExchange(
673
735
  const refetchWithPolicy = (route.provider.adapter === "google" || refetchTransientPolicy)
674
736
  ? fetchWithTransientRetry
675
737
  : fetchWithResetRetry;
676
- const helperCountsSends = sendBudgetState.adapterSendBudget?.spendEnforced === true || refetchTransientPolicy !== null || resetReplayPolicyFor(route.provider) !== null;
738
+ const helperCountsSends = genericRefetchCountsSends();
677
739
  const prepaid = sendBudgetState.pendingHopPermit;
678
740
  const configuredTotal = refetchTransientPolicy?.attempts;
679
741
  const refetchCap = transientSendCapFor(configuredTotal, sendBudgetState.targetSendsUsed);
@@ -689,27 +751,41 @@ export async function prepareAdapterExchange(
689
751
  { allowFinalRecoveryReserve: configuredTotal === undefined },
690
752
  )
691
753
  : undefined;
754
+ let refetchPermitUsed = false;
755
+ const refetchReporter = refetchAllowance ? transientSendReporter(refetchAllowance.permit) : undefined;
756
+ const deferredRefetch = billingPoolRequest && refetchReporter ? deferPhysicalSendReporter(refetchReporter) : undefined;
692
757
  try {
693
758
  return await refetchWithPolicy(
694
759
  recoveryKind => {
695
760
  sendBudgetState.adapterSendBudget?.startRequest?.({
696
761
  poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
697
762
  });
698
- if (refetchAllowance?.permit && !refetchAllowance.permit.use()) {
699
- throw new SendBudgetExhaustedError(safeHostLabel(retryRequest.url));
700
- }
701
- transportState.noteRoutedAttemptSend(retryEstimate, recoveryKind ?? recovery);
702
- // Same boundary on the helper path: the thunk is what reaches the wire, and it
703
- // can be refused above before it does. use() past the first attempt is a no-op.
704
- onDispatch?.();
705
- if (preserveFailureResponse) replacementAdmitted = true;
706
- if (!refetchAllowance) chargeFastDowngradeWorkflowSend();
763
+ // Prepare key metadata before the guarded transport commits its attempt;
764
+ // counters and permits still wait for the physical executor after pacing.
765
+ if (deferredRefetch) transportState.noteRoutedAttemptSend(retryEstimate, recoveryKind ?? recovery);
766
+ const confirmDispatch = () => {
767
+ if (refetchAllowance?.permit && !refetchPermitUsed) {
768
+ if (!refetchAllowance.permit.use()) throw new SendBudgetExhaustedError(safeHostLabel(retryRequest.url));
769
+ refetchPermitUsed = true;
770
+ }
771
+ if (!deferredRefetch) transportState.noteRoutedAttemptSend(retryEstimate, recoveryKind ?? recovery);
772
+ // Eligible pools confirm after pacing at the physical executor; legacy
773
+ // helper paths retain their existing thunk admission boundary.
774
+ onDispatch?.();
775
+ if (preserveFailureResponse) replacementAdmitted = true;
776
+ if (!refetchAllowance) chargeFastDowngradeWorkflowSend();
777
+ };
778
+ if (!deferredRefetch) confirmDispatch();
707
779
  return fetchWithHeaderTimeout(retryRequest.url,
708
780
  applyUpstreamRecoveryInit({
709
781
  method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
710
782
  }, recoveryKind), upstream.signal, connectMs, parsed.stream,
711
783
  providerFetch(route.provider, options.codexWsRuntimeIdentity, {
712
784
  dispatchOverride: oauthDispatch(retryRequest),
785
+ onPhysicalDispatch: deferredRefetch ? () => {
786
+ if (!deferredRefetch.beforeDispatch()) throw new SendBudgetExhaustedError(safeHostLabel(retryRequest.url));
787
+ confirmDispatch();
788
+ } : undefined,
713
789
  providerName: route.providerName,
714
790
  modelId: route.modelId,
715
791
  beforeDispatch: isCanonicalOpenAiForwardProvider(route.provider)
@@ -723,14 +799,14 @@ export async function prepareAdapterExchange(
723
799
  ...(refetchAllowance
724
800
  ? {
725
801
  attempts: refetchAllowance.attempts,
726
- onSendsConsumed: transientSendReporter(refetchAllowance.permit),
802
+ onSendsConsumed: deferredRefetch?.report ?? refetchReporter,
727
803
  }
728
804
  : {}),
729
805
  },
730
806
  );
731
807
  } finally {
732
- // Refunds only a reservation whose send never happened -- an abort settled before
733
- // the thunk ran. A used or externally settled permit ignores this.
808
+ // Refund only a reservation whose send never reached its admission boundary.
809
+ // A used or externally settled permit ignores this.
734
810
  refetchAllowance?.permit?.release();
735
811
  }
736
812
  } finally {
@@ -744,6 +820,13 @@ export async function prepareAdapterExchange(
744
820
  releaseCodexAuthContextProbeLease(admissionState.authCtx);
745
821
  return { failed: storageRefusal };
746
822
  }
823
+ const scopeRefusal = unwrapUpstreamRetryEvidenceError(err);
824
+ if (!options.abortSignal?.aborted && scopeRefusal instanceof AdmissionModelDeniedError) {
825
+ cleanupUpstreamAbort();
826
+ upstream.abort();
827
+ releaseCodexAuthContextProbeLease(admissionState.authCtx);
828
+ return { failed: admissionModelDeniedResponse(scopeRefusal) };
829
+ }
747
830
  const codexRefusal = !options.abortSignal?.aborted && mapCodexAuthContextErrorToResponse(unwrapUpstreamRetryEvidenceError(err), {
748
831
  now: Date.now(), accountSelector: route.codexAccountNamespace,
749
832
  });
@@ -768,8 +851,7 @@ export async function prepareAdapterExchange(
768
851
  return { failed: formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(refusal)) };
769
852
  }
770
853
  if (refusal instanceof AnthropicAccountCooldownError) {
771
- return { failed: formatErrorResponse(429, "rate_limit_error", refusal.message,
772
- refusal.retryAfterSeconds === null ? undefined : { retryAfter: String(refusal.retryAfterSeconds) }) };
854
+ return { failed: anthropicPoolCooldownResponse(refusal, { comboAttempt: options.comboAttempt, logCtx }) };
773
855
  }
774
856
  // Same rule on the recovery leg: the ladder refused to send again, so the answer names
775
857
  // this proxy rather than the provider it never reached.
@@ -980,32 +1062,83 @@ export async function prepareAdapterExchange(
980
1062
  promptCacheKey: parsed.options.promptCacheKey,
981
1063
  });
982
1064
  if (!rotated) break;
983
- // Release the failed response's socket before retrying; unread bodies otherwise linger
984
- // until runtime cleanup (one per rotated key).
985
- try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
986
- route.provider = rotated;
987
- const selectionFailure = await resolveRotatedCopilotSelection();
988
- if (selectionFailure) return selectionFailure;
989
- invalidateSameTargetRequest();
990
- transportState.activeAdapter = resolveSelectionAdapter(
991
- resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
992
- config.cacheRetention,
993
- );
994
- bindRouteReasoningReplayScope({
995
- parsed,
996
- providerName: route.providerName,
997
- provider: route.provider,
998
- adapterName: transportState.activeAdapter.name,
999
- });
1000
- const result = await rebuildAndRefetch("key-401");
1001
- if ("failed" in result) return result.failed;
1002
- upstreamResponse = result;
1003
- // A recovery refetch can itself die on an ambiguous pre-header reset, and the refusal
1004
- // that answers it is a 429. Every arm below keys on 429, so letting it fall through
1005
- // hands the marked refusal to the next waiting arm and replays the send it exists to
1006
- // stop. Re-enter the loop guard instead, which returns it unchanged.
1007
- if (isNonReplayableResponse(upstreamResponse)) continue recovery;
1008
- }
1065
+ if (billingRecoveryActive && (options.abortSignal?.aborted || upstream.signal.aborted)) {
1066
+ cleanupUpstreamAbort(); upstream.abort(); return clientCancelledResponse();
1067
+ }
1068
+ const billingHop = (billingPoolRequest || billingRecoveryActive)
1069
+ ? reserveCredentialHop("auth-recovery", `${route.providerName}|${route.modelId}|key-401`,
1070
+ transportState.activeAdapter.fetchResponse === undefined)
1071
+ : undefined;
1072
+ if (billingHop && !billingHop.allowed) break;
1073
+ const failedResponse = billingPoolRequest || billingRecoveryActive ? await retainFailureResponse() : undefined;
1074
+ try {
1075
+ // Release the failed response's socket before retrying; unread bodies otherwise linger
1076
+ // until runtime cleanup (one per rotated key).
1077
+ if (!failedResponse) try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
1078
+ route.provider = rotated;
1079
+ const selectionFailure = await resolveRotatedCopilotSelection();
1080
+ if (selectionFailure) return selectionFailure;
1081
+ invalidateSameTargetRequest();
1082
+ transportState.activeAdapter = resolveSelectionAdapter(
1083
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
1084
+ config.cacheRetention,
1085
+ );
1086
+ bindRouteReasoningReplayScope({
1087
+ parsed,
1088
+ providerName: route.providerName,
1089
+ provider: route.provider,
1090
+ adapterName: transportState.activeAdapter.name,
1091
+ });
1092
+ if (billingHop) sendBudgetState.pendingHopPermit = billingHop.permit;
1093
+ const result = await rebuildAndRefetch("key-401",
1094
+ billingHop ? () => { billingHop.permit?.use(); } : undefined, failedResponse);
1095
+ if ("failed" in result) {
1096
+ if (result.failed !== failedResponse) return result.failed;
1097
+ break;
1098
+ }
1099
+ if (failedResponse) {
1100
+ try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
1101
+ }
1102
+ upstreamResponse = result;
1103
+ // A recovery refetch can itself die on an ambiguous pre-header reset, and the refusal
1104
+ // that answers it is a 429. Every arm below keys on 429, so letting it fall through
1105
+ // hands the marked refusal to the next waiting arm and replays the send it exists to
1106
+ // stop. Re-enter the loop guard instead, which returns it unchanged.
1107
+ if (isNonReplayableResponse(upstreamResponse)) continue recovery;
1108
+ } finally {
1109
+ if (billingHop) { sendBudgetState.pendingHopPermit = undefined; billingHop.permit?.release(); }
1110
+ }
1111
+ }
1112
+
1113
+ while ((upstreamResponse.status === 400 || upstreamResponse.status === 402) && hasKeyPoolFailover(route.provider)) {
1114
+ const rotated = await rotateAnthropicBillingKeyOnResponse(upstreamResponse, config, route.providerName, route.provider,
1115
+ { signal: upstream.signal, promptCacheKey: parsed.options.promptCacheKey });
1116
+ if (upstream.signal.aborted || options.abortSignal?.aborted) {
1117
+ cleanupUpstreamAbort(); upstream.abort(); return clientCancelledResponse();
1118
+ }
1119
+ if (!rotated) break;
1120
+ billingRecoveryActive = true;
1121
+ const hop = reserveCredentialHop("auth-recovery", `${route.providerName}|${route.modelId}|key-401`,
1122
+ transportState.activeAdapter.fetchResponse === undefined);
1123
+ if (!hop.allowed) break;
1124
+ const failedResponse = await retainFailureResponse();
1125
+ try {
1126
+ route.provider = rotated;
1127
+ invalidateSameTargetRequest();
1128
+ transportState.activeAdapter = resolveSelectionAdapter(
1129
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy), config.cacheRetention);
1130
+ bindRouteReasoningReplayScope({ parsed, providerName: route.providerName, provider: route.provider, adapterName: transportState.activeAdapter.name });
1131
+ sendBudgetState.pendingHopPermit = hop.permit;
1132
+ const result = await rebuildAndRefetch("key-401", () => { hop.permit?.use(); }, failedResponse);
1133
+ if ("failed" in result) {
1134
+ if (result.failed !== failedResponse) return result.failed;
1135
+ break;
1136
+ }
1137
+ try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
1138
+ upstreamResponse = result;
1139
+ continue recovery;
1140
+ } finally { sendBudgetState.pendingHopPermit = undefined; hop.permit?.release(); }
1141
+ }
1009
1142
 
1010
1143
  // Anthropic fast mode refused (no usage credits, organization not enabled, model outside
1011
1144
  // the lane, fast pool empty): resend once at standard speed, as Claude Code does. This
@@ -1024,7 +1157,7 @@ export async function prepareAdapterExchange(
1024
1157
  const hop = reserveCredentialHop(
1025
1158
  "repair",
1026
1159
  `${route.providerName}|${route.modelId}|anthropic-fast-downgrade`,
1027
- !adapterOwnsDispatch && (transientRetryPolicyFor(route.provider) !== null || resetReplayPolicyFor(route.provider) !== null),
1160
+ !adapterOwnsDispatch && genericRefetchCountsSends(),
1028
1161
  );
1029
1162
  if (hop.allowed) {
1030
1163
  anthropicFastDowngradeGuard.attempted = true;
@@ -1093,7 +1226,9 @@ export async function prepareAdapterExchange(
1093
1226
  upstreamResponse = result;
1094
1227
  // The refusal is a 429 too: without this the while condition is still true and the
1095
1228
  // next configured attempt replays it on the same target.
1096
- if (isNonReplayableResponse(upstreamResponse)) continue recovery;
1229
+ if (isNonReplayableResponse(upstreamResponse)
1230
+ || (isAnthropicBillingKeyPool(route.provider)
1231
+ && [400, 402].includes(upstreamResponse.status))) continue recovery;
1097
1232
  }
1098
1233
 
1099
1234
  // Multi-key 429 failover: rotate to the next pool key (cooldown-aware) and retry the
@@ -1128,30 +1263,53 @@ export async function prepareAdapterExchange(
1128
1263
  quotaResetAt,
1129
1264
  });
1130
1265
  if (!rotated) break;
1131
- // Release the failed response's socket before retrying; unread bodies otherwise linger
1132
- // until runtime cleanup (one per rotated key under a rate-limit storm).
1133
- try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
1134
- route.provider = rotated;
1135
- const selectionFailure = await resolveRotatedCopilotSelection();
1136
- if (selectionFailure) return selectionFailure;
1137
- invalidateSameTargetRequest();
1138
- transportState.activeAdapter = resolveSelectionAdapter(
1139
- resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
1140
- config.cacheRetention,
1141
- );
1142
- bindRouteReasoningReplayScope({
1143
- parsed,
1144
- providerName: route.providerName,
1145
- provider: route.provider,
1146
- adapterName: transportState.activeAdapter.name,
1147
- });
1148
- const result = await rebuildAndRefetch("key-429");
1149
- if ("failed" in result) return result.failed;
1150
- upstreamResponse = result;
1151
- // Rotating on the refusal would also write a cooldown against a key that rate-limited
1152
- // nothing, which outlives the request.
1153
- if (isNonReplayableResponse(upstreamResponse)) continue recovery;
1154
- }
1266
+ if (billingRecoveryActive && (options.abortSignal?.aborted || upstream.signal.aborted)) {
1267
+ cleanupUpstreamAbort(); upstream.abort(); return clientCancelledResponse();
1268
+ }
1269
+ const billingHop = (billingPoolRequest || billingRecoveryActive)
1270
+ ? reserveCredentialHop("auth-recovery", `${route.providerName}|${route.modelId}|key-429`,
1271
+ transportState.activeAdapter.fetchResponse === undefined)
1272
+ : undefined;
1273
+ if (billingHop && !billingHop.allowed) break;
1274
+ const failedResponse = billingPoolRequest || billingRecoveryActive ? await retainFailureResponse() : undefined;
1275
+ try {
1276
+ // Release the failed response's socket before retrying; unread bodies otherwise linger
1277
+ // until runtime cleanup (one per rotated key under a rate-limit storm).
1278
+ if (!failedResponse) try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
1279
+ route.provider = rotated;
1280
+ const selectionFailure = await resolveRotatedCopilotSelection();
1281
+ if (selectionFailure) return selectionFailure;
1282
+ invalidateSameTargetRequest();
1283
+ transportState.activeAdapter = resolveSelectionAdapter(
1284
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
1285
+ config.cacheRetention,
1286
+ );
1287
+ bindRouteReasoningReplayScope({
1288
+ parsed,
1289
+ providerName: route.providerName,
1290
+ provider: route.provider,
1291
+ adapterName: transportState.activeAdapter.name,
1292
+ });
1293
+ if (billingHop) sendBudgetState.pendingHopPermit = billingHop.permit;
1294
+ const result = await rebuildAndRefetch("key-429",
1295
+ billingHop ? () => { billingHop.permit?.use(); } : undefined, failedResponse);
1296
+ if ("failed" in result) {
1297
+ if (result.failed !== failedResponse) return result.failed;
1298
+ break;
1299
+ }
1300
+ if (failedResponse) {
1301
+ try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
1302
+ }
1303
+ upstreamResponse = result;
1304
+ // Rotating on the refusal would also write a cooldown against a key that rate-limited
1305
+ // nothing, which outlives the request.
1306
+ if (isNonReplayableResponse(upstreamResponse)
1307
+ || (billingRecoveryActive && upstreamResponse.status === 401)
1308
+ || (route.provider.adapter === "anthropic" && [400, 402].includes(upstreamResponse.status))) continue recovery;
1309
+ } finally {
1310
+ if (billingHop) { sendBudgetState.pendingHopPermit = undefined; billingHop.permit?.release(); }
1311
+ }
1312
+ }
1155
1313
 
1156
1314
  // Anthropic OAuth: recover a rate limit or proven account entitlement refusal
1157
1315
  // before output, within the shared request and account rotation limits.
@@ -1398,7 +1556,17 @@ export async function prepareAdapterExchange(
1398
1556
  // The hop's permit is already settled by the dispatch boundary above; continuing
1399
1557
  // only skips the remaining arms, it does not abandon a reservation.
1400
1558
  if (isNonReplayableResponse(upstreamResponse)) continue recovery;
1401
- } catch {
1559
+ } catch (err) {
1560
+ // A local admission denial is terminal for the whole request: rotating to another
1561
+ // account cannot re-scope the key, so the refusal answers in place of the 429.
1562
+ const scopeRefusal = unwrapUpstreamRetryEvidenceError(err);
1563
+ if (!options.abortSignal?.aborted && scopeRefusal instanceof AdmissionModelDeniedError) {
1564
+ hop.permit?.release();
1565
+ cleanupUpstreamAbort();
1566
+ upstream.abort();
1567
+ releaseCodexAuthContextProbeLease(admissionState.authCtx);
1568
+ return admissionModelDeniedResponse(scopeRefusal);
1569
+ }
1402
1570
  // A throw before the send — snapshot fetch, credential application, adapter
1403
1571
  // resolution — must hand the reservation back. Without this the ladder charges the
1404
1572
  // request for a send it never made, and a later recovery in the same request is
@@ -1539,9 +1707,13 @@ export async function prepareAdapterExchange(
1539
1707
  imageRetryAttempted = true;
1540
1708
  transportState.imageTierBias = 1;
1541
1709
  invalidateSameTargetRequest();
1542
- try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
1543
- const result = await rebuildAndRefetch("image-413");
1544
- if ("failed" in result) return result.failed;
1710
+ const failedResponse = await retainFailureResponse();
1711
+ const result = await rebuildAndRefetch("image-413", undefined, failedResponse);
1712
+ if ("failed" in result) {
1713
+ if (result.failed !== failedResponse) return result.failed;
1714
+ break recovery;
1715
+ }
1716
+ try { void failedResponse.body?.cancel().catch(() => {}); } catch { /* closed */ }
1545
1717
  upstreamResponse = result;
1546
1718
  continue recovery;
1547
1719
  }
@@ -1658,6 +1830,14 @@ export async function prepareAdapterExchange(
1658
1830
  requestState.subagentFallbackAccountId,
1659
1831
  );
1660
1832
  }
1833
+ // The last pool account just refused on spent quota and recovery found no replacement:
1834
+ // answer the usage limit now (#6991), so Codex stops instead of retrying into the local
1835
+ // refusal that every later attempt meets until the first window reopens.
1836
+ const poolUsageLimit = upstreamResponse.status === 429 && anthropicInstance && route.provider.authMode === "oauth"
1837
+ ? anthropicRoutingFor(anthropicInstance).anthropicPoolUsageLimitAfterRefusal(Date.now(), transportState.anthropicRouteDecision, route.modelId)
1838
+ : null;
1839
+ // Combo children returned above; the flag is passed anyway so this site states the same rule.
1840
+ if (poolUsageLimit) return anthropicPoolCooldownResponse(poolUsageLimit, { comboAttempt: options.comboAttempt, logCtx });
1661
1841
  // Upstreams occasionally echo request details in error bodies — scrub token-shaped
1662
1842
  // material before it reaches the client-facing error surface.
1663
1843
  const upstreamRetryAfter = upstreamResponse.headers.get("retry-after");
@@ -0,0 +1,27 @@
1
+ /** Core-owned, config-scoped registration. An inactive install has no Advisor factory. */
2
+ import type { OcxConfig, OcxParsedRequest, OcxTool } from "../../types";
3
+
4
+ export interface AdvisorPlanInput {
5
+ config: OcxConfig;
6
+ workerIdentity: string;
7
+ workerModelId: string;
8
+ abortSignal?: AbortSignal;
9
+ }
10
+
11
+ export interface RegisteredAdvisorPlan {
12
+ tool: OcxTool;
13
+ preflightInject(parsed: OcxParsedRequest): Promise<boolean>;
14
+ attachGuard(parsed: OcxParsedRequest): void;
15
+ }
16
+
17
+ type AdvisorPlanFactory = (input: AdvisorPlanInput) => Promise<RegisteredAdvisorPlan | null>;
18
+ const factories = new WeakMap<OcxConfig, AdvisorPlanFactory>();
19
+
20
+ export function setAdvisorPlanFactory(config: OcxConfig, factory: AdvisorPlanFactory | null): void {
21
+ if (factory) factories.set(config, factory);
22
+ else factories.delete(config);
23
+ }
24
+
25
+ export function createRegisteredAdvisorPlan(input: AdvisorPlanInput): Promise<RegisteredAdvisorPlan | null> | null {
26
+ return factories.get(input.config)?.(input) ?? null;
27
+ }