@bitkyc08/opencodex 2.86.0 → 2.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  2. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  3. package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-DCd-A1az.js} +1 -1
  4. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  5. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  6. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  7. package/gui/dist/index.html +2 -2
  8. package/package.json +1 -1
  9. package/src/adapters/anthropic/beta-allowlist.ts +9 -8
  10. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  11. package/src/adapters/anthropic/passthrough.ts +7 -23
  12. package/src/adapters/anthropic-progress.ts +84 -0
  13. package/src/adapters/anthropic.ts +108 -21
  14. package/src/adapters/base.ts +2 -0
  15. package/src/adapters/claude-cli/adapter.ts +111 -23
  16. package/src/adapters/claude-cli/profiles.ts +2 -1
  17. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  18. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  19. package/src/adapters/coding-agent/protocol.ts +1 -1
  20. package/src/adapters/coding-agent/turn.ts +18 -2
  21. package/src/adapters/dsh-account.ts +97 -0
  22. package/src/adapters/google.ts +35 -8
  23. package/src/adapters/openai-chat/messages.ts +4 -3
  24. package/src/adapters/openai-chat/passthrough.ts +8 -2
  25. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  26. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  27. package/src/adapters/openai-responses/passthrough.ts +57 -125
  28. package/src/adapters/openai-responses/request-strips.ts +25 -3
  29. package/src/adapters/openai-responses/transport.ts +81 -0
  30. package/src/adapters/qoder/adapter.ts +4 -2
  31. package/src/adapters/registry.ts +5 -0
  32. package/src/adapters/zed.ts +26 -1
  33. package/src/advisor/activity.ts +55 -0
  34. package/src/advisor/budget.ts +62 -0
  35. package/src/advisor/consult.ts +4 -1
  36. package/src/advisor/context.ts +16 -4
  37. package/src/advisor/evidence-tool.ts +21 -0
  38. package/src/advisor/evidence.ts +117 -0
  39. package/src/advisor/runtime.ts +79 -31
  40. package/src/advisor/settings.ts +11 -2
  41. package/src/bridge/errors.ts +19 -1
  42. package/src/bridge/internal.ts +3 -1
  43. package/src/bridge/response-json.ts +18 -3
  44. package/src/bridge/sse.ts +58 -8
  45. package/src/bridge/terminal-refusal.ts +59 -0
  46. package/src/claude/desktop-3p-library.ts +32 -7
  47. package/src/claude/desktop-3p.ts +55 -13
  48. package/src/claude/desktop-first-party.ts +1 -1
  49. package/src/claude/desktop-picker-platform.ts +4 -0
  50. package/src/claude/desktop-picker.ts +15 -8
  51. package/src/claude/desktop-system-proxy.ts +50 -4
  52. package/src/claude/inbound-content-options.ts +11 -0
  53. package/src/claude/inbound.ts +33 -0
  54. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  55. package/src/claude/intercept/picker-runtime.ts +6 -5
  56. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  57. package/src/claude/intercept/picker-trust.ts +19 -0
  58. package/src/claude/intercept/port-policy.ts +30 -0
  59. package/src/claude/intercept/runtime.ts +97 -94
  60. package/src/claude/outbound.ts +38 -13
  61. package/src/cli/account-api.ts +1 -0
  62. package/src/cli/account-policy-dto.ts +6 -0
  63. package/src/cli/account-policy.ts +34 -5
  64. package/src/cli/account-preservation.ts +44 -0
  65. package/src/cli/account.ts +8 -2
  66. package/src/cli/advisor.ts +17 -3
  67. package/src/cli/capabilities-accounts.ts +20 -1
  68. package/src/cli/capabilities-agents-routing.ts +5 -2
  69. package/src/cli/capabilities-base.ts +38 -10
  70. package/src/cli/capabilities-observe-system.ts +63 -3
  71. package/src/cli/capabilities-provider-models.ts +11 -0
  72. package/src/cli/codex-cli-update.ts +151 -41
  73. package/src/cli/codex-provider.ts +44 -0
  74. package/src/cli/combo-input.ts +10 -1
  75. package/src/cli/combo-result.ts +1 -1
  76. package/src/cli/combo.ts +2 -1
  77. package/src/cli/cross-home-owner.ts +17 -14
  78. package/src/cli/dispatch.ts +25 -4
  79. package/src/cli/doctor.ts +6 -1
  80. package/src/cli/ensure-desktop-cold-start.ts +60 -7
  81. package/src/cli/help.ts +3 -2
  82. package/src/cli/index.ts +1 -1
  83. package/src/cli/integration-plan-dto.ts +1 -0
  84. package/src/cli/log-history.ts +172 -0
  85. package/src/cli/models-runtime-subcommands.ts +1 -0
  86. package/src/cli/models-runtime.ts +72 -8
  87. package/src/cli/observe-stream.ts +2 -0
  88. package/src/cli/observe.ts +16 -4
  89. package/src/cli/provider.ts +27 -5
  90. package/src/cli/registry.ts +28 -8
  91. package/src/cli/runtime-api.ts +5 -3
  92. package/src/cli/system-command.ts +40 -8
  93. package/src/cli/uninstall-client-state.ts +32 -0
  94. package/src/cli/usage-compare.ts +309 -0
  95. package/src/cli/usage-model-search.ts +4 -4
  96. package/src/cli/usage-report.ts +2 -2
  97. package/src/cli/v2.ts +1 -1
  98. package/src/client/machine-listener.ts +1 -3
  99. package/src/client/runtime.ts +1 -0
  100. package/src/clients/client-path.ts +51 -0
  101. package/src/clients/config-export/contracts.ts +2 -1
  102. package/src/clients/config-export.ts +18 -44
  103. package/src/clients/gentle-shell-path.ts +87 -0
  104. package/src/codex/account-auto-switch.ts +2 -0
  105. package/src/codex/account-identity.ts +29 -0
  106. package/src/codex/account-label.ts +1 -1
  107. package/src/codex/account-lifecycle.ts +21 -5
  108. package/src/codex/account-preservation.ts +28 -0
  109. package/src/codex/account-use-remaining.ts +23 -0
  110. package/src/codex/app-server-processes.ts +97 -7
  111. package/src/codex/app-server-restart-service.ts +55 -4
  112. package/src/codex/auth-api/account-list.ts +4 -0
  113. package/src/codex/auth-api/account-pause-group.ts +6 -50
  114. package/src/codex/auth-api/account-preservation.ts +57 -0
  115. package/src/codex/auth-api/routes.ts +8 -0
  116. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  117. package/src/codex/auth-api.ts +0 -1
  118. package/src/codex/auth-context.ts +2 -2
  119. package/src/codex/catalog/auto-review.ts +20 -3
  120. package/src/codex/catalog/build-entries.ts +9 -1
  121. package/src/codex/catalog/combo-member.ts +14 -5
  122. package/src/codex/catalog/configured-tiers.ts +1 -1
  123. package/src/codex/catalog/gather-capture.ts +2 -0
  124. package/src/codex/catalog/metadata.ts +11 -9
  125. package/src/codex/catalog/model-hints.ts +21 -16
  126. package/src/codex/catalog/model-visibility.ts +3 -1
  127. package/src/codex/catalog/parsing.ts +30 -86
  128. package/src/codex/catalog/provider-models.ts +36 -34
  129. package/src/codex/catalog/routed-gather.ts +12 -13
  130. package/src/codex/cli-install-provenance.ts +105 -17
  131. package/src/codex/cli-update-plan.ts +545 -0
  132. package/src/codex/config-write-lock.ts +23 -2
  133. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  134. package/src/codex/desktop-app-restart.ts +21 -0
  135. package/src/codex/desktop-compatibility/installed-build.ts +24 -6
  136. package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
  137. package/src/codex/desktop-compatibility/runtime.ts +12 -3
  138. package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
  139. package/src/codex/features.ts +85 -45
  140. package/src/codex/inject/history-failure.ts +27 -0
  141. package/src/codex/inject/restore.ts +24 -2
  142. package/src/codex/inject/routing-classify.ts +4 -3
  143. package/src/codex/inject.ts +18 -27
  144. package/src/codex/main-account-hard-lock.ts +30 -1
  145. package/src/codex/native-profile-startup.ts +1 -1
  146. package/src/codex/ocx-compaction-history.ts +12 -10
  147. package/src/codex/paths.ts +86 -1
  148. package/src/codex/provider-routing.ts +174 -0
  149. package/src/codex/refresh.ts +2 -2
  150. package/src/codex/routing/health-store.ts +1 -1
  151. package/src/codex/routing/selection.ts +4 -3
  152. package/src/codex/routing/thread-affinity.ts +1 -1
  153. package/src/codex/signed-in-identity.ts +43 -0
  154. package/src/codex/subagent-model-fallback.ts +1 -1
  155. package/src/codex/sync.ts +6 -1
  156. package/src/codex/user-identity.ts +16 -5
  157. package/src/codex/windows-local-appdata.ts +84 -0
  158. package/src/combos/failover.ts +6 -1
  159. package/src/combos/index.ts +1 -0
  160. package/src/combos/resolve.ts +46 -10
  161. package/src/combos/types.ts +15 -0
  162. package/src/config/diagnostics.ts +33 -6
  163. package/src/config/live-reconcile.ts +38 -2
  164. package/src/config/load-degrade.ts +17 -0
  165. package/src/config/opencode-wire-upgrade.ts +54 -0
  166. package/src/config/opencode-zen-upgrade.ts +52 -0
  167. package/src/config/owner-registry.ts +22 -17
  168. package/src/config/provider-validation.ts +45 -2
  169. package/src/config/schema/config-schema.ts +22 -0
  170. package/src/config/schema/leaf-validators.ts +38 -9
  171. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  172. package/src/config/schema/native-reasoning-retention.ts +26 -0
  173. package/src/config.ts +2 -0
  174. package/src/generated/compatibility-version.json +608 -284
  175. package/src/grok/sync.ts +1 -1
  176. package/src/images/loop.ts +46 -8
  177. package/src/integrations/catalog-refresh.ts +1 -1
  178. package/src/integrations/mutation-plan.ts +1 -0
  179. package/src/integrations/registry.ts +8 -0
  180. package/src/lib/codex-restart-contract.ts +15 -0
  181. package/src/lib/desktop-upstream-tunnel.ts +18 -53
  182. package/src/lib/empty-tool-catalog.ts +62 -0
  183. package/src/lib/errors.ts +19 -3
  184. package/src/lib/gcp-adc.ts +96 -9
  185. package/src/lib/gcp-credential-input.ts +12 -0
  186. package/src/lib/lab-live-route-production.ts +5 -0
  187. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  188. package/src/lib/redact.ts +2 -2
  189. package/src/oauth/account-quota-rank.ts +69 -2
  190. package/src/oauth/anthropic-account-refusal.ts +20 -2
  191. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  192. package/src/oauth/anthropic-routing.ts +1 -21
  193. package/src/oauth/anthropic.ts +38 -7
  194. package/src/oauth/callback-server.ts +27 -1
  195. package/src/oauth/chatgpt-device.ts +1 -1
  196. package/src/oauth/chatgpt-shared.ts +83 -0
  197. package/src/oauth/chatgpt.ts +8 -72
  198. package/src/oauth/dsh.ts +203 -0
  199. package/src/oauth/generic-account-failover.ts +33 -7
  200. package/src/oauth/generic-session-affinity.ts +125 -0
  201. package/src/oauth/index.ts +184 -109
  202. package/src/oauth/local-token-detect.ts +21 -1
  203. package/src/oauth/login-cli-errors.ts +44 -0
  204. package/src/oauth/login-cli.ts +69 -7
  205. package/src/oauth/login-flow-state.ts +371 -23
  206. package/src/oauth/meta-muse-errors.ts +41 -0
  207. package/src/oauth/meta-muse-keychain.ts +52 -0
  208. package/src/oauth/meta-muse.ts +51 -84
  209. package/src/oauth/model-discovery-auth.ts +3 -0
  210. package/src/oauth/pool-settings-capability.ts +4 -2
  211. package/src/oauth/refresh-flight-retirement.ts +48 -0
  212. package/src/oauth/session-affinity-key.ts +23 -0
  213. package/src/oauth/store-anthropic-instance.ts +9 -2
  214. package/src/oauth/store.ts +12 -5
  215. package/src/protocols/encoders/adapter-events.ts +5 -4
  216. package/src/protocols/encoders/messages.ts +9 -6
  217. package/src/protocols/provider-summary.ts +2 -1
  218. package/src/providers/api-key-cooldowns.ts +49 -0
  219. package/src/providers/api-key-resolve.ts +11 -6
  220. package/src/providers/api-key-response-ownership.ts +57 -0
  221. package/src/providers/api-key-selection.ts +31 -6
  222. package/src/providers/api-keys.ts +214 -3
  223. package/src/providers/claude-cli-usage.ts +96 -0
  224. package/src/providers/dsh-account-destination.ts +31 -0
  225. package/src/providers/dsh-account-models.ts +41 -0
  226. package/src/providers/gcp-credential-cleanup.ts +59 -0
  227. package/src/providers/key-failover.ts +54 -50
  228. package/src/providers/key-store.ts +7 -2
  229. package/src/providers/model-rename-fields.ts +3 -0
  230. package/src/providers/model-rename-migration.ts +2 -2
  231. package/src/providers/openai-tiers-destination.ts +11 -2
  232. package/src/providers/openai-tiers.ts +5 -12
  233. package/src/providers/opencode-go-transport.ts +3 -1
  234. package/src/providers/quota/account-cache.ts +6 -5
  235. package/src/providers/quota/account-row.ts +34 -0
  236. package/src/providers/quota/antigravity.ts +28 -7
  237. package/src/providers/quota/dsh-account.ts +70 -0
  238. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  239. package/src/providers/quota-types.ts +2 -0
  240. package/src/providers/quota.ts +14 -20
  241. package/src/providers/registry/entries-core.ts +37 -8
  242. package/src/providers/registry/types.ts +1 -1
  243. package/src/providers/request-pacing.ts +77 -19
  244. package/src/providers/routed-config.ts +339 -0
  245. package/src/providers/service-tier.ts +2 -1
  246. package/src/quota/window-mapping.ts +5 -2
  247. package/src/responses/anthropic-progress.ts +34 -0
  248. package/src/responses/assistant-replay.ts +35 -0
  249. package/src/responses/compaction-history-framing.ts +65 -0
  250. package/src/responses/compaction.ts +6 -1
  251. package/src/responses/incomplete-reason.ts +31 -0
  252. package/src/responses/parser.ts +50 -5
  253. package/src/responses/reasoning-envelope.ts +4 -0
  254. package/src/responses/reasoning-replay-cache.ts +24 -2
  255. package/src/responses/reasoning-retention.ts +173 -0
  256. package/src/responses/retained-compaction.ts +46 -0
  257. package/src/responses/schema.ts +23 -0
  258. package/src/router.ts +8 -321
  259. package/src/routing/health.ts +37 -22
  260. package/src/routing/history/indexer.ts +6 -5
  261. package/src/routing/history/ingest.ts +6 -2
  262. package/src/routing/profile.ts +1 -1
  263. package/src/server/admission-model-scope.ts +17 -0
  264. package/src/server/auth-cors.ts +11 -0
  265. package/src/server/chat-completions.ts +7 -1
  266. package/src/server/chat-native-reasoning.ts +123 -0
  267. package/src/server/chat-native-sse.ts +110 -7
  268. package/src/server/chat-native.ts +76 -17
  269. package/src/server/claude-messages.ts +55 -414
  270. package/src/server/effort-policy.ts +1 -1
  271. package/src/server/images.ts +15 -2
  272. package/src/server/index/model-alias-id.ts +36 -0
  273. package/src/server/index/optional-listeners.ts +2 -0
  274. package/src/server/index/serve-options.ts +9 -1
  275. package/src/server/index/startup-warnings.ts +2 -0
  276. package/src/server/index.ts +1 -1
  277. package/src/server/inference/advisor-diagnostics.ts +31 -0
  278. package/src/server/management/advisor-routes.ts +15 -4
  279. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  280. package/src/server/management/combo-routes.ts +176 -50
  281. package/src/server/management/companion-routes.ts +1 -1
  282. package/src/server/management/config-routes.ts +75 -4
  283. package/src/server/management/cursor-integration-routes.ts +1 -1
  284. package/src/server/management/model-routes.ts +11 -6
  285. package/src/server/management/model-rows.ts +14 -0
  286. package/src/server/management/native-integration-routes.ts +2 -7
  287. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  288. package/src/server/management/oauth-account-routes.ts +87 -14
  289. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  290. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  291. package/src/server/management/provider-overwrite-carry.ts +7 -0
  292. package/src/server/management/provider-routes.ts +25 -30
  293. package/src/server/management/route-registry.ts +10 -1
  294. package/src/server/management-api.ts +3 -1
  295. package/src/server/messages-native-tool-guard.ts +185 -0
  296. package/src/server/messages-native.ts +101 -21
  297. package/src/server/messages-shared.ts +431 -0
  298. package/src/server/relay.ts +1 -1
  299. package/src/server/request-log-terminal-status.ts +1 -1
  300. package/src/server/request-log.ts +6 -1
  301. package/src/server/responses/account-change-state.ts +8 -2
  302. package/src/server/responses/adapter-continuation.ts +13 -1
  303. package/src/server/responses/adapter-delivery.ts +19 -6
  304. package/src/server/responses/adapter-dispatch.ts +19 -2
  305. package/src/server/responses/advisor-retention.ts +117 -0
  306. package/src/server/responses/advisor-slot.ts +73 -41
  307. package/src/server/responses/codex-ws-wire.ts +15 -8
  308. package/src/server/responses/compact.ts +303 -125
  309. package/src/server/responses/compaction-recovery.ts +18 -10
  310. package/src/server/responses/compaction-retention.ts +61 -0
  311. package/src/server/responses/context-overflow.ts +11 -6
  312. package/src/server/responses/core-auth.ts +6 -0
  313. package/src/server/responses/core-codex-account.ts +11 -1
  314. package/src/server/responses/core-combo-failure.ts +2 -0
  315. package/src/server/responses/core-combo.ts +120 -36
  316. package/src/server/responses/core-errors.ts +3 -3
  317. package/src/server/responses/core-lifetime.ts +5 -2
  318. package/src/server/responses/core-opaque-recovery.ts +35 -0
  319. package/src/server/responses/core-options.ts +5 -0
  320. package/src/server/responses/core-pipeline.ts +141 -0
  321. package/src/server/responses/core-replay.ts +18 -2
  322. package/src/server/responses/core.ts +2 -115
  323. package/src/server/responses/fetch-helpers.ts +73 -19
  324. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  325. package/src/server/responses/native-response-control.ts +4 -2
  326. package/src/server/responses/passthrough-delivery.ts +3 -1
  327. package/src/server/responses/passthrough-dispatch.ts +15 -13
  328. package/src/server/responses/request-prepare.ts +76 -12
  329. package/src/server/responses/request-sidecar-auth.ts +2 -2
  330. package/src/server/responses/request-transport.ts +104 -27
  331. package/src/server/responses/response-effects.ts +1 -0
  332. package/src/server/responses/run-turn-execution.ts +96 -30
  333. package/src/server/responses/sidecar-execution.ts +20 -2
  334. package/src/server/responses/terminal-guard.ts +2 -16
  335. package/src/service/auth-preflight.ts +71 -0
  336. package/src/service/cli.ts +4 -2
  337. package/src/service/definition.ts +97 -0
  338. package/src/service/guards.ts +1 -61
  339. package/src/service/health-sentinel-linux.ts +112 -0
  340. package/src/service/health-sentinel-main.ts +76 -57
  341. package/src/service/health.ts +5 -89
  342. package/src/service/launchd.ts +27 -7
  343. package/src/service/logging.ts +72 -9
  344. package/src/service/repair.ts +2 -1
  345. package/src/service/state.ts +7 -8
  346. package/src/service/systemd.ts +1 -1
  347. package/src/service/windows-guarded-manager.ts +2 -1
  348. package/src/service/windows-ops.ts +1 -1
  349. package/src/service/windows-scheduler.ts +2 -7
  350. package/src/service/windows-taskxml.ts +2 -2
  351. package/src/service/windows-wscript.ts +14 -0
  352. package/src/service.ts +4 -2
  353. package/src/sidecar/auth.ts +2 -0
  354. package/src/types/config.ts +64 -3
  355. package/src/types/opencode-go.ts +61 -0
  356. package/src/types/opencode-zen.ts +29 -0
  357. package/src/types/provider.ts +32 -6
  358. package/src/types/request.ts +23 -2
  359. package/src/types/wire.ts +29 -37
  360. package/src/types.ts +1 -0
  361. package/src/update/npm-invocation.mjs +12 -2
  362. package/src/usage/cost.ts +48 -12
  363. package/src/usage/expected-prices.ts +3 -0
  364. package/src/usage/ledger-retention.ts +2 -1
  365. package/src/usage/ledger-scanner.ts +6 -5
  366. package/src/usage/log.ts +17 -8
  367. package/src/usage/summary.ts +22 -9
  368. package/src/usage/user-cost-overlays.ts +14 -19
  369. package/src/usage/user-cost-validation.ts +38 -0
  370. package/src/vision/eligibility.ts +13 -0
  371. package/src/web-search/loop.ts +55 -5
  372. package/src/web-search/progress-stream.ts +4 -1
  373. package/src/web-search/run-turn-loop.ts +9 -1
  374. package/gui/dist/assets/App-BjlttVLy.js +0 -58
  375. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  376. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  377. package/gui/dist/assets/index-BMssP4k3.js +0 -94
  378. package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
@@ -276,6 +276,39 @@ export function isSelfIdentifiedOpaqueBlobRejection(bodyText: string): boolean {
276
276
  }
277
277
 
278
278
 
279
+ /**
280
+ * Opted-in native reasoning retention forwards ciphertext another model or account minted. ChatGPT
281
+ * can refuse it inside a 200 stream -- a Codex WebSocket `error` frame or `response.failed` --
282
+ * rather than as an HTTP 4xx, so the passthrough inspects the zero-output prelude of exactly these
283
+ * requests. Ordinary turns keep the function-output-only inspection.
284
+ */
285
+ export function retainedReasoningStreamCandidate(parsed: OcxParsedRequest, outboundBody: string | undefined): boolean {
286
+ return parsed._nativeReasoningRetainedAcrossRoute === true
287
+ && parsed._stripReasoningEncryptedContent !== true
288
+ && outboundResponsesBodyCarriesOpaqueBlob(outboundBody);
289
+ }
290
+
291
+ /**
292
+ * Whether a stream-prelude payload is a self-identified refusal of retained ciphertext. A match marks
293
+ * the projected failure as the 4xx it is, so the shared single-shot trigger accepts it.
294
+ */
295
+ export function retainedReasoningStreamRejection(
296
+ payload: unknown,
297
+ parsed: OcxParsedRequest,
298
+ outboundBody: string | undefined,
299
+ preflightLog: RequestLogContext,
300
+ ): boolean {
301
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
302
+ if (!retainedReasoningStreamCandidate(parsed, outboundBody)) return false;
303
+ const { type, response } = payload as { type?: unknown; response?: { error?: unknown } };
304
+ if (type !== "error" && type !== "response.failed") return false;
305
+ const envelope = type === "error" ? payload : { error: response?.error };
306
+ if (!isSelfIdentifiedOpaqueBlobRejection(JSON.stringify(envelope))) return false;
307
+ preflightLog.terminalHttpStatus = 400;
308
+ return true;
309
+ }
310
+
311
+
279
312
  /**
280
313
  * Whether an upstream Responses 4xx authoritatively rejected opaque replay state.
281
314
  *
@@ -411,6 +444,8 @@ export async function consoleGoUploadRejectionBody(
411
444
 
412
445
  export function prepareOpaqueBlobRecovery(parsed: OcxParsedRequest): void {
413
446
  parsed._stripReasoningEncryptedContent = true;
447
+ delete parsed._nativeReasoningRetainedAcrossRoute;
448
+ parsed._stripNativeCompactionEncryptedContent = true;
414
449
  // The destination rejected state another serving identity minted. A reasoning item's id names
415
450
  // an item in that identity's store, so it goes with the blob.
416
451
  parsed._dropForeignReasoningItemIds = true;
@@ -15,6 +15,7 @@ import type { InboundWire } from "../../providers/registry";
15
15
  import type { ExplicitOpenAiCallerAuth } from "../../providers/openai-sidecar";
16
16
  import type { CallerDirectAuth } from "../../providers/caller-authorization";
17
17
  import type { CompactionRoutingOverride } from "./compaction-routing";
18
+ import type { MainHardLockFallbackOverride } from "./main-hard-lock-routing";
18
19
  import type { TranslatorBudget } from "../../lib/translator-budget";
19
20
  import type { NativeReasoningOwner } from "../../responses/reasoning-replay-cache";
20
21
  import type { TransientSendBudget } from "../../lib/upstream-retry";
@@ -174,6 +175,8 @@ export interface HandleResponsesOptions {
174
175
  clientDeclaredEmptyToolCatalog?: boolean;
175
176
  nativeReasoningReplay?: ReadonlyMap<string, string>;
176
177
  nativeReasoningMint?: { owner?: NativeReasoningOwner };
178
+ /** The Claude client's typed `web_search_*` / `web_fetch_*` declarations; kept only for `inboundWire: "anthropic"`. */
179
+ anthropicServerTools?: readonly Record<string, unknown>[];
177
180
  /** Droid's per-request effort default; each concrete combo or policy target applies it only if its ladder allows it. */
178
181
  droidDefaultEffort?: string;
179
182
  /** PF-07: the Chat source a combo child may send natively; set only by the Chat ingress. */
@@ -207,6 +210,8 @@ export interface HandleResponsesOptions {
207
210
  /** Internal handoff: the memory phase this turn belongs to, so combo children keep its routing. */
208
211
  memoryModelPhase?: "extract" | "consolidation";
209
212
  compactionRoutingOverride?: CompactionRoutingOverride | null;
213
+ /** Internal handoff: the hard-lock fallback rewrote this turn; the log shows `sourceModel`. */
214
+ mainHardLockFallbackOverride?: MainHardLockFallbackOverride | null;
210
215
  /** Internal combo handoff for one parent-validated continuation snapshot. */
211
216
  comboReplaySnapshot?: {
212
217
  sourceBody: unknown;
@@ -0,0 +1,141 @@
1
+ import { supportsNativeResponsesCompactionTrigger } from "../../providers/openai-tiers";
2
+ import type { OcxConfig } from "../../types";
3
+ import type { RequestLogContext } from "../request-log";
4
+ import type {
5
+ HandleResponsesOptions,
6
+ ResponsesRequestContext,
7
+ ResponsesAdmissionState,
8
+ ResponsesDispatchers,
9
+ } from "./core-options";
10
+ import type { TranslatorBudget } from "../../lib/translator-budget";
11
+ import { prepareResponsesRequest } from "./request-prepare";
12
+ import { prepareResponsesTransport } from "./request-transport";
13
+ import { prepareResponsesSidecarAuth } from "./request-sidecar-auth";
14
+ import { createResponsesEffects } from "./response-effects";
15
+ import { createResponsesSendBudget } from "./request-send-budget";
16
+ import { executePassthroughResponse } from "./passthrough-execution";
17
+ import { executeResponsesSidecars } from "./sidecar-execution";
18
+ import { createResponsesCompletionPolicy } from "./completion-policy";
19
+ import { executeResponsesRunTurn } from "./run-turn-execution";
20
+ import { prepareAdapterExchange } from "./adapter-dispatch";
21
+ import { createAdapterContinuations } from "./adapter-continuation";
22
+ import { deliverAdapterResponse } from "./adapter-delivery";
23
+ import { releaseUpstreamHostAdmission } from "../../codex/upstream-host-health";
24
+ import { releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
25
+ import { formatErrorResponse } from "../../bridge";
26
+ import { InvalidRetainedCompactionSummary, prepareCompactionRetention } from "./compaction-retention";
27
+
28
+ /** Compose request phases while retaining the original admission-finally ownership. */
29
+ export async function handleResponsesInner(
30
+ req: Request,
31
+ config: OcxConfig,
32
+ logCtx: RequestLogContext,
33
+ options: HandleResponsesOptions & { translatorBudget: TranslatorBudget },
34
+ requestDispatchers: ResponsesDispatchers,
35
+ ): Promise<Response> {
36
+ const requestContext: ResponsesRequestContext = { req, config, logCtx, options };
37
+ const admissionState: ResponsesAdmissionState = {
38
+ pendingHostAdmissionLease: null,
39
+ authCtx: { kind: "main", accountId: null },
40
+ };
41
+ let retention: ReturnType<typeof prepareCompactionRetention>;
42
+ let releasePendingSend = () => {};
43
+ try {
44
+ const requestState = await prepareResponsesRequest(requestContext, admissionState, requestDispatchers);
45
+ if (requestState instanceof Response) return requestState;
46
+ const transportState = await prepareResponsesTransport(requestContext, admissionState, requestState);
47
+ if (transportState instanceof Response) return transportState;
48
+ options.onCompactionRecoveryRoute?.(requestState.route);
49
+ if (requestState.parsed._compactionRequest && options.compactionRecoveryKind !== "compaction-v1"
50
+ && (requestState.parsed._portableCompaction || !supportsNativeResponsesCompactionTrigger(requestState.route.provider))) {
51
+ retention = prepareCompactionRetention(requestState.parsed, config,
52
+ requestState.route.staticPolicy.model.contextWindow ?? requestState.route.staticPolicy.model.maxInputTokens,
53
+ options.translatorBudget, options.abortSignal ?? req.signal);
54
+ }
55
+ const execute = async (): Promise<Response> => {
56
+ const sidecarState = await prepareResponsesSidecarAuth(requestContext, requestState, transportState);
57
+ if (sidecarState instanceof Response) return sidecarState;
58
+ const responseEffects = createResponsesEffects(
59
+ requestContext,
60
+ admissionState,
61
+ requestState,
62
+ sidecarState,
63
+ );
64
+ const sendBudgetState = createResponsesSendBudget(requestContext);
65
+ if (sendBudgetState instanceof Response) return sendBudgetState;
66
+ if ("passthrough" in transportState.adapter && transportState.adapter.passthrough && !sidecarState.routedCompaction) {
67
+ const passthroughResult = await executePassthroughResponse(
68
+ requestContext,
69
+ admissionState,
70
+ requestState,
71
+ transportState,
72
+ sidecarState,
73
+ responseEffects,
74
+ sendBudgetState,
75
+ );
76
+ if (passthroughResult instanceof Response) return passthroughResult;
77
+ const unclaimedHop = sendBudgetState.pendingHopPermit;
78
+ releasePendingSend = () => { if (sendBudgetState.pendingHopPermit === unclaimedHop) { unclaimedHop?.release(); sendBudgetState.pendingHopPermit = undefined; } };
79
+ }
80
+ const sidecarPlans = await executeResponsesSidecars(
81
+ requestContext,
82
+ requestState,
83
+ transportState,
84
+ sidecarState,
85
+ responseEffects,
86
+ sendBudgetState,
87
+ );
88
+ if (sidecarPlans instanceof Response) return sidecarPlans;
89
+ const completionPolicy = createResponsesCompletionPolicy(requestContext, sidecarState);
90
+ if (transportState.adapter.runTurn) return await executeResponsesRunTurn(
91
+ requestContext,
92
+ admissionState,
93
+ requestState,
94
+ transportState,
95
+ sidecarState,
96
+ responseEffects,
97
+ sendBudgetState,
98
+ completionPolicy,
99
+ );
100
+ const adapterExchange = await prepareAdapterExchange(
101
+ requestContext,
102
+ admissionState,
103
+ requestState,
104
+ transportState,
105
+ responseEffects,
106
+ sendBudgetState,
107
+ );
108
+ if (adapterExchange instanceof Response) return adapterExchange;
109
+ const continuationState = createAdapterContinuations(
110
+ requestContext,
111
+ requestState,
112
+ transportState,
113
+ sidecarState,
114
+ sendBudgetState,
115
+ adapterExchange,
116
+ );
117
+ return await deliverAdapterResponse(
118
+ requestContext,
119
+ requestState,
120
+ transportState,
121
+ sidecarState,
122
+ responseEffects,
123
+ completionPolicy,
124
+ adapterExchange,
125
+ continuationState,
126
+ );
127
+ };
128
+ const response = await execute();
129
+ return retention ? retention.deliver(response) : response;
130
+ } catch (error) {
131
+ retention?.dispose();
132
+ if (error instanceof InvalidRetainedCompactionSummary) return formatErrorResponse(502, "invalid_compaction_summary", error.message);
133
+ throw error;
134
+ } finally {
135
+ releasePendingSend();
136
+ if (admissionState.pendingHostAdmissionLease) {
137
+ releaseUpstreamHostAdmission(admissionState.pendingHostAdmissionLease);
138
+ releaseCodexAuthContextProbeLease(admissionState.authCtx);
139
+ }
140
+ }
141
+ }
@@ -24,6 +24,7 @@ import {
24
24
  type NativeReasoningOwner,
25
25
  bindReasoningReplayScope,
26
26
  reasoningReplayServingIdentityChanged,
27
+ reasoningReplayServingIdentityChange,
27
28
  reasoningReplayItemStoreChanged,
28
29
  reasoningReplayOpaqueBlobRejectionMemoized,
29
30
  } from "../../responses/reasoning-replay-cache";
@@ -32,6 +33,7 @@ import type { CodexAuthContext } from "../../codex/auth-context";
32
33
  import { thoughtSignatureReplaySalt } from "../../responses/thought-signature-replay";
33
34
  import { randomUUID } from "node:crypto";
34
35
  import { requiresPlaintextReasoningReplay } from "../../adapters/openai-responses/passthrough";
36
+ import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
35
37
 
36
38
  /**
37
39
  * Adapters whose continuation state must survive Codex's store:false requests.
@@ -156,8 +158,19 @@ export function bindRouteReasoningReplayScope(args: {
156
158
  );
157
159
  // Keep this sticky for the whole outbound request: a later auth/key rebind may compare equal
158
160
  // after the first mismatch, but it cannot make history minted by the prior route decodable.
159
- if (reasoningReplayServingIdentityChanged(parsed._reasoningReplayScope)) {
160
- parsed._stripReasoningEncryptedContent = true;
161
+ const identityChange = reasoningReplayServingIdentityChange(parsed._reasoningReplayScope);
162
+ if (identityChange) {
163
+ // Reasoning retention does not establish portability of native compacted history.
164
+ parsed._stripNativeCompactionEncryptedContent = true;
165
+ const retention = parsed._nativeReasoningRetention;
166
+ const mayRetainReasoning = isCanonicalOpenAiForwardProvider(provider)
167
+ && !identityChange.providerChanged
168
+ && !identityChange.destinationChanged
169
+ && !identityChange.adapterChanged
170
+ && (!identityChange.modelChanged || retention?.modelSwitch === true)
171
+ && (!identityChange.credentialChanged || retention?.accountSwitch === true);
172
+ if (!mayRetainReasoning) parsed._stripReasoningEncryptedContent = true;
173
+ else if (parsed._stripReasoningEncryptedContent !== true) parsed._nativeReasoningRetainedAcrossRoute = true;
161
174
  // Only a different destination or credential makes the replayed item ids unresolvable; a
162
175
  // model change on the same store keeps them.
163
176
  if (reasoningReplayItemStoreChanged(parsed._reasoningReplayScope)) {
@@ -166,8 +179,11 @@ export function bindRouteReasoningReplayScope(args: {
166
179
  }
167
180
  if (reasoningReplayOpaqueBlobRejectionMemoized(parsed._reasoningReplayScope)) {
168
181
  parsed._stripReasoningEncryptedContent = true;
182
+ parsed._stripNativeCompactionEncryptedContent = true;
169
183
  parsed._dropForeignReasoningItemIds = true;
170
184
  }
185
+ // Stripping is authoritative: nothing retained remains to be refused.
186
+ if (parsed._stripReasoningEncryptedContent === true) delete parsed._nativeReasoningRetainedAcrossRoute;
171
187
  bindProviderContinuationForRoute(parsed, continuationOwner);
172
188
  }
173
189
 
@@ -1,9 +1,8 @@
1
+ import { handleResponsesInner } from "./core-pipeline";
1
2
  import type { OcxConfig } from "../../types";
2
3
  import type { RequestLogContext } from "../request-log";
3
4
  import type {
4
5
  HandleResponsesOptions,
5
- ResponsesRequestContext,
6
- ResponsesAdmissionState,
7
6
  ResponsesDispatchers,
8
7
  } from "./core-options";
9
8
  import { createTranslatorBudget } from "../../lib/translator-budget";
@@ -13,20 +12,6 @@ import { createInferenceSendBudget } from "../inference/context";
13
12
  import { finalizeOwnedTranslatorBudget, finalizeAccountLease } from "./core-lifetime";
14
13
  import type { TranslatorBudget } from "../../lib/translator-budget";
15
14
  import { executeComboResponses } from "./core-combo";
16
- import { prepareResponsesRequest } from "./request-prepare";
17
- import { prepareResponsesTransport } from "./request-transport";
18
- import { prepareResponsesSidecarAuth } from "./request-sidecar-auth";
19
- import { createResponsesEffects } from "./response-effects";
20
- import { createResponsesSendBudget } from "./request-send-budget";
21
- import { executePassthroughResponse } from "./passthrough-execution";
22
- import { executeResponsesSidecars } from "./sidecar-execution";
23
- import { createResponsesCompletionPolicy } from "./completion-policy";
24
- import { executeResponsesRunTurn } from "./run-turn-execution";
25
- import { prepareAdapterExchange } from "./adapter-dispatch";
26
- import { createAdapterContinuations } from "./adapter-continuation";
27
- import { deliverAdapterResponse } from "./adapter-delivery";
28
- import { releaseUpstreamHostAdmission } from "../../codex/upstream-host-health";
29
- import { releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
30
15
  import { runWithCompactionRecovery } from "./compaction-recovery";
31
16
  /**
32
17
  * Route one `/v1/responses` request through the adapter pipeline: recovery loop, passthrough
@@ -62,7 +47,7 @@ export async function handleResponses(
62
47
  accountLoad,
63
48
  // Once at ingress, spend observer included: a combo child inherits the parent's holder.
64
49
  sendBudget: options.sendBudget ?? createInferenceSendBudget(req, logCtx),
65
- }, handleResponsesInner);
50
+ }, (req, config, logCtx, options) => handleResponsesInner(req, config, logCtx, options, requestDispatchers));
66
51
  const finalResponse = ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget, abortSignal) : response;
67
52
  if (!accountLoad.lease) { release(); return finalResponse; }
68
53
  return finalizeAccountLease(finalResponse, release);
@@ -92,104 +77,6 @@ export async function handleComboResponses(
92
77
  requestDispatchers,
93
78
  );
94
79
  }
95
- /** Compose request phases while retaining the original admission-finally ownership. */
96
- async function handleResponsesInner(
97
- req: Request,
98
- config: OcxConfig,
99
- logCtx: RequestLogContext,
100
- options: HandleResponsesOptions & { translatorBudget: TranslatorBudget },
101
- ): Promise<Response> {
102
- const requestContext: ResponsesRequestContext = { req, config, logCtx, options };
103
- const admissionState: ResponsesAdmissionState = {
104
- pendingHostAdmissionLease: null,
105
- authCtx: { kind: "main", accountId: null },
106
- };
107
- let releasePendingSend = () => {};
108
- try {
109
- const requestState = await prepareResponsesRequest(requestContext, admissionState, requestDispatchers);
110
- if (requestState instanceof Response) return requestState;
111
- const transportState = await prepareResponsesTransport(requestContext, admissionState, requestState);
112
- if (transportState instanceof Response) return transportState;
113
- options.onCompactionRecoveryRoute?.(requestState.route);
114
- const sidecarState = await prepareResponsesSidecarAuth(requestContext, requestState, transportState);
115
- if (sidecarState instanceof Response) return sidecarState;
116
- const responseEffects = createResponsesEffects(
117
- requestContext,
118
- admissionState,
119
- requestState,
120
- sidecarState,
121
- );
122
- const sendBudgetState = createResponsesSendBudget(requestContext);
123
- if (sendBudgetState instanceof Response) return sendBudgetState;
124
- if ("passthrough" in transportState.adapter && transportState.adapter.passthrough && !sidecarState.routedCompaction) {
125
- const passthroughResult = await executePassthroughResponse(
126
- requestContext,
127
- admissionState,
128
- requestState,
129
- transportState,
130
- sidecarState,
131
- responseEffects,
132
- sendBudgetState,
133
- );
134
- if (passthroughResult instanceof Response) return passthroughResult;
135
- const unclaimedHop = sendBudgetState.pendingHopPermit;
136
- releasePendingSend = () => { if (sendBudgetState.pendingHopPermit === unclaimedHop) { unclaimedHop?.release(); sendBudgetState.pendingHopPermit = undefined; } };
137
- }
138
- const sidecarPlans = await executeResponsesSidecars(
139
- requestContext,
140
- requestState,
141
- transportState,
142
- sidecarState,
143
- responseEffects,
144
- sendBudgetState,
145
- );
146
- if (sidecarPlans instanceof Response) return sidecarPlans;
147
- const completionPolicy = createResponsesCompletionPolicy(requestContext, sidecarState);
148
- if (transportState.adapter.runTurn) return await executeResponsesRunTurn(
149
- requestContext,
150
- admissionState,
151
- requestState,
152
- transportState,
153
- sidecarState,
154
- responseEffects,
155
- sendBudgetState,
156
- completionPolicy,
157
- );
158
- const adapterExchange = await prepareAdapterExchange(
159
- requestContext,
160
- admissionState,
161
- requestState,
162
- transportState,
163
- responseEffects,
164
- sendBudgetState,
165
- );
166
- if (adapterExchange instanceof Response) return adapterExchange;
167
- const continuationState = createAdapterContinuations(
168
- requestContext,
169
- requestState,
170
- transportState,
171
- sidecarState,
172
- sendBudgetState,
173
- adapterExchange,
174
- );
175
- return await deliverAdapterResponse(
176
- requestContext,
177
- requestState,
178
- transportState,
179
- sidecarState,
180
- responseEffects,
181
- completionPolicy,
182
- adapterExchange,
183
- continuationState,
184
- );
185
- } finally {
186
- releasePendingSend();
187
- if (admissionState.pendingHostAdmissionLease) {
188
- releaseUpstreamHostAdmission(admissionState.pendingHostAdmissionLease);
189
- releaseCodexAuthContextProbeLease(admissionState.authCtx);
190
- }
191
- }
192
- }
193
80
  const requestDispatchers: ResponsesDispatchers = { handleResponses, handleComboResponses };
194
81
  export { adapterNeedsForcedContinuation } from "./core-replay";
195
82
  export {
@@ -12,6 +12,7 @@ import type { WsData } from "../ws-bridge";
12
12
  import {
13
13
  requestPacingMaxConcurrentRequests,
14
14
  sendTrackingRequestSlot,
15
+ trackProviderRequestSlotBody,
15
16
  waitForProviderRequestSlot,
16
17
  type ProviderRequestSlot,
17
18
  } from "../../providers/request-pacing";
@@ -49,6 +50,9 @@ const UPSTREAM_REWRITTEN = Symbol.for("opencodex.plugins.upstream-rewritten");
49
50
  const PHYSICAL_DISPATCH = Symbol.for("opencodex.physical-dispatch");
50
51
  // One WS attempt and its HTTP fallback are one logical physical-send receipt.
51
52
  const WS_FALLBACK_RECEIPT = Symbol.for("opencodex.ws-fallback-receipt");
53
+ // Replacement admission waits on the client signal, outside the response-header deadline.
54
+ const HEADER_DEADLINE = Symbol("header-deadline");
55
+ type HeaderDeadline = { clientSignal: AbortSignal; pause(): void; resume(): void };
52
56
 
53
57
  /**
54
58
  * Announce once, per provider, that an explicit egress route moved this provider off the
@@ -139,6 +143,7 @@ export function wantsFreshConnection(
139
143
  export interface PaceAwareFetch {
140
144
  waitForPacing?: (signal?: AbortSignal) => Promise<ProviderRequestSlot | undefined>;
141
145
  unpacedFetch?: typeof globalThis.fetch;
146
+ fetchWithPacingSlot?: (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => Promise<Response>;
142
147
  }
143
148
 
144
149
  export type ProviderFetch = typeof globalThis.fetch & PaceAwareFetch;
@@ -252,7 +257,8 @@ export interface ProviderFetchOptions {
252
257
  */
253
258
  preOutputRefusalHold?: CodexWsPreOutputRefusalHold;
254
259
  /** Revalidate/rebuild a queued request at its physical send boundary, after pacing. */
255
- dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch) => Promise<Response>;
260
+ dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch,
261
+ reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => Promise<Response>;
256
262
  }
257
263
 
258
264
  /** Compose provider pacing and transport admission; receipts run only at executor entry. */
@@ -301,7 +307,8 @@ export function providerFetch(
301
307
  { preconnect },
302
308
  ) as typeof globalThis.fetch);
303
309
  const httpFetch = Object.assign(
304
- async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
310
+ async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit,
311
+ reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => {
305
312
  // Refuse before any dispatch side effect where that is sound. `beforeDispatch` commits
306
313
  // attempt accounting and consumes admission state, so a refusal firing after it would
307
314
  // charge an attempt for a send that never happens, and a throwing hook would mask the
@@ -329,30 +336,33 @@ export function providerFetch(
329
336
  } } : {}),
330
337
  };
331
338
  return options.dispatchOverride
332
- ? options.dispatchOverride(input, dispatchInit, dispatch)
339
+ ? options.dispatchOverride(input, dispatchInit, dispatch, reacquirePacing)
333
340
  : dispatch(input, dispatchInit);
334
341
  },
335
342
  { preconnect },
336
- ) as typeof globalThis.fetch;
343
+ );
337
344
  // ChatGPT Codex backend: streaming turns ride the responses_websockets
338
345
  // transport (measured ~3s faster TTFT than the SSE POST queue); everything
339
346
  // else keeps the provider's HTTP fetch. See ws-upstream.ts for the details.
340
- const unpaced = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
347
+ const unpaced = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit,
348
+ reacquirePacing?: (provider: OcxProviderConfig, modelId?: string) => Promise<void>) => {
341
349
  const upstreamWebsocket = provider.upstreamWebsocket;
342
350
  if (!options.httpOnly && typeof input === "string" && init
343
351
  && shouldUseCodexWsUpstream(input, init, runtime, upstreamWebsocket)) {
344
- if (options.providerName && requestPacingMaxConcurrentRequests(provider, options.modelId) > 0) {
352
+ const keyedPacing = provider.requestPacing?.enabled
353
+ && (provider.requestPacing.keys || provider.requestPacing.keySelection === "round-robin");
354
+ if (keyedPacing || (options.providerName && requestPacingMaxConcurrentRequests(provider, options.modelId) > 0)) {
345
355
  if (!pacingWebsocketDowngradeWarned.has(providerName)
346
356
  && pacingWebsocketDowngradeWarned.size < EGRESS_DOWNGRADE_NOTICE_LIMIT) {
347
357
  pacingWebsocketDowngradeWarned.add(providerName);
348
- console.warn(`[opencodex] provider ${JSON.stringify(redactSecretString(providerName))} has a request concurrency cap; serving over HTTP/SSE.`);
358
+ console.warn(`[opencodex] provider ${JSON.stringify(redactSecretString(providerName))} requires guarded request pacing; serving over HTTP/SSE.`);
349
359
  }
350
- return httpFetch(input, init);
360
+ return httpFetch(input, init, reacquirePacing);
351
361
  }
352
362
  const egress = egressFor(input);
353
363
  if (providerEgressIsExplicit(egress)) {
354
364
  warnEgressWebsocketDowngradeOnce(providerName, describeProviderEgressForLog(egress));
355
- return httpFetch(input, init);
365
+ return httpFetch(input, init, reacquirePacing);
356
366
  }
357
367
  // The fallback has to be the same HTTP fetch the non-WS branch would have
358
368
  // used, protocol pin included: a WS turn that falls back is serving the
@@ -365,11 +375,11 @@ export function providerFetch(
365
375
  options.onPhysicalDispatch?.();
366
376
  };
367
377
  const fallback = ((url: Parameters<typeof globalThis.fetch>[0], fallbackInit?: RequestInit) =>
368
- httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit)) as typeof globalThis.fetch;
378
+ httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit, reacquirePacing)) as typeof globalThis.fetch;
369
379
  return codexWsUpstreamFetch(input, init, fallback, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
370
380
  async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce, options.preOutputRefusalHold);
371
381
  }
372
- return httpFetch(input, init);
382
+ return httpFetch(input, init, reacquirePacing);
373
383
  };
374
384
  let pacingSlotAcquired = options.pacingSlotAcquired === true;
375
385
  const waitForPacing = async (signal?: AbortSignal): Promise<ProviderRequestSlot | undefined> => {
@@ -392,15 +402,35 @@ export function providerFetch(
392
402
  throw new Error("A provider name is required for request concurrency pacing");
393
403
  }
394
404
  };
395
- const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
396
- return sendTrackingRequestSlot(await waitForPacing(init?.signal ?? undefined), () => unpaced(input, init));
405
+ const fetchWithPacingSlot = async (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
406
+ const reacquirePacing = async (refreshed: OcxProviderConfig, modelId?: string) => {
407
+ if (options.turnScopedPacing) throw new Error("Cannot replace a turn-scoped pacing lease during dispatch");
408
+ if (!options.providerName) throw new Error("Refreshed dispatch requires a named pacing owner");
409
+ slot?.release();
410
+ slot = undefined;
411
+ const deadline = (init as Record<symbol, HeaderDeadline> | undefined)?.[HEADER_DEADLINE];
412
+ deadline?.pause();
413
+ try {
414
+ slot = await waitForProviderRequestSlot(providerName, refreshed, modelId, deadline?.clientSignal ?? init?.signal ?? undefined);
415
+ } finally { deadline?.resume(); }
416
+ };
417
+ try {
418
+ const response = await unpaced(input, init, reacquirePacing);
419
+ return trackProviderRequestSlotBody(slot, response);
420
+ } catch (error) {
421
+ slot?.release();
422
+ throw error;
423
+ }
397
424
  };
425
+ const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) =>
426
+ fetchWithPacingSlot(await waitForPacing(init?.signal ?? undefined), input, init);
398
427
  // The returned wrapper forwards its init down to `dispatch`, which applies the route at the
399
428
  // physical send. Adapters that hand this executor back as `provider.fetch` (Cursor does)
400
429
  // therefore still carry a per-provider route instead of being refused as opaque.
401
430
  const paceAware = Object.assign(wrapped, {
402
431
  preconnect,
403
432
  waitForPacing,
433
+ fetchWithPacingSlot,
404
434
  unpacedFetch: Object.assign(unpaced, { preconnect }),
405
435
  });
406
436
  markEgressTransparentExecutor(paceAware as unknown as typeof globalThis.fetch);
@@ -432,6 +462,9 @@ export function storedPoolReplayDispatchNotifier(
432
462
  notified = true;
433
463
  onDispatch();
434
464
  };
465
+ const notifyWithSlot = (slot: ProviderRequestSlot | undefined): void => {
466
+ try { notifyOnce(); } catch (error) { slot?.release(); throw error; }
467
+ };
435
468
  const unpacedSource = executor.unpacedFetch ?? executor;
436
469
  const unpaced = Object.assign(
437
470
  (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
@@ -441,12 +474,21 @@ export function storedPoolReplayDispatchNotifier(
441
474
  { preconnect: unpacedSource.preconnect },
442
475
  ) as ProviderFetch["unpacedFetch"];
443
476
  const wrapped = async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
477
+ if (executor.fetchWithPacingSlot) {
478
+ const slot = await executor.waitForPacing?.(init?.signal ?? undefined);
479
+ notifyWithSlot(slot);
480
+ return executor.fetchWithPacingSlot(slot, input, init);
481
+ }
444
482
  return sendTrackingRequestSlot(await executor.waitForPacing?.(init?.signal ?? undefined), () => unpaced!(input, init));
445
483
  };
446
484
  return Object.assign(wrapped, {
447
485
  preconnect: executor.preconnect,
448
486
  waitForPacing: executor.waitForPacing,
449
487
  unpacedFetch: unpaced,
488
+ ...(executor.fetchWithPacingSlot ? { fetchWithPacingSlot: (slot: ProviderRequestSlot | undefined, input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
489
+ notifyWithSlot(slot);
490
+ return executor.fetchWithPacingSlot!(slot, input, init);
491
+ } } : {}),
450
492
  }) as ProviderFetch;
451
493
  }
452
494
 
@@ -470,24 +512,36 @@ export async function fetchWithHeaderTimeout(
470
512
  let timer: ReturnType<typeof setTimeout> | undefined;
471
513
  try {
472
514
  const timeout = new AbortController();
473
- timer = setTimeout(() => {
474
- if (!timeout.signal.aborted) timeout.abort(new DOMException("Timeout elapsed", "TimeoutError"));
475
- }, timeoutMs);
515
+ const deadline: HeaderDeadline = {
516
+ clientSignal: abortSignal,
517
+ pause() { if (timer) clearTimeout(timer); timer = undefined; },
518
+ resume() {
519
+ this.pause();
520
+ timer = setTimeout(() => {
521
+ if (!timeout.signal.aborted) timeout.abort(new DOMException("Timeout elapsed", "TimeoutError"));
522
+ }, timeoutMs);
523
+ },
524
+ };
525
+ deadline.resume();
476
526
  const headers = new Headers(init.headers);
477
527
  // Compressed SSE can be held until the decompressor has a complete block. Streaming calls
478
528
  // default to identity for low-latency frame delivery, while an explicit caller choice wins.
479
529
  if (preferIdentityEncoding && !headers.has("accept-encoding")) {
480
530
  headers.set("accept-encoding", "identity");
481
531
  }
482
- return await sendTrackingRequestSlot(slot, () => fetchExecutor(url, {
532
+ const dispatchInit = {
483
533
  ...init,
484
534
  headers,
485
535
  // Never replay provider credentials or request bodies to a redirect destination.
486
536
  // Preserve the 3xx for the owner's existing response/health policy (#914, #1471).
487
- redirect: "manual",
537
+ redirect: "manual" as const,
488
538
  signal: AbortSignal.any([abortSignal, timeout.signal]),
539
+ [HEADER_DEADLINE]: deadline,
489
540
  timeout: 0,
490
- }));
541
+ };
542
+ return pacing.fetchWithPacingSlot
543
+ ? await pacing.fetchWithPacingSlot(slot, url, dispatchInit)
544
+ : await sendTrackingRequestSlot(slot, () => fetchExecutor(url, dispatchInit));
491
545
  } finally {
492
546
  if (timer) clearTimeout(timer);
493
547
  if (!slot?.bodyTracked) slot?.release();