@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
@@ -54,7 +54,7 @@ import { describeUpstreamConnectFailure } from "./upstream-error";
54
54
  import type { OpaqueBlobRecoveryGuard } from "./core-opaque-recovery";
55
55
  import type { AttemptRecoveryKind } from "../../usage/log";
56
56
  import type { OAuthAccessSnapshot } from "../../oauth";
57
- import { OAuthAccountPausedError, OAuthLoginRequiredError, publicOAuthAuthenticationErrorMessage } from "../../oauth";
57
+ import { OAuthAccountPausedError, OAuthLoginRequiredError, OAuthTokenRefreshBusyError, publicOAuthAuthenticationErrorMessage } from "../../oauth";
58
58
  import { getAccountSet, markAccountNeedsReauthIfGeneration } from "../../oauth/store";
59
59
  import { tryKiroAlternateAfterTerminalRefresh } from "../../oauth/kiro-terminal-failover";
60
60
  import { classifyKiroRefusal } from "../../adapters/kiro-refusal";
@@ -932,6 +932,15 @@ export async function prepareAdapterExchange(
932
932
  try {
933
933
  refreshed = await refreshResolvedOAuthSelection(transportState.sentOAuthSnapshot);
934
934
  } catch (err) {
935
+ const cancelled = options.abortSignal?.aborted || req.signal.aborted;
936
+ if (cancelled || err instanceof OAuthTokenRefreshBusyError) {
937
+ cleanupUpstreamAbort();
938
+ upstream.abort();
939
+ try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already closed */ }
940
+ releaseCodexAuthContextProbeLease(admissionState.authCtx);
941
+ return cancelled ? clientCancelledResponse()
942
+ : formatErrorResponse(503, "server_error", publicOAuthAuthenticationErrorMessage(err), { retryAfter: "1" });
943
+ }
935
944
  const failed = transportState.sentOAuthSnapshot;
936
945
  if (route.providerName === "google-antigravity" && !antigravityAuthRotationAttempted
937
946
  && err instanceof OAuthLoginRequiredError && failed
@@ -1337,9 +1346,17 @@ export async function prepareAdapterExchange(
1337
1346
  resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
1338
1347
  config.cacheRetention,
1339
1348
  );
1349
+ bindRouteReasoningReplayScope({
1350
+ parsed,
1351
+ providerName: route.providerName,
1352
+ provider: route.provider,
1353
+ adapterName: transportState.activeAdapter.name,
1354
+ oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot,
1355
+ });
1340
1356
  sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, transportState.activeAdapter.name, logCtx.accountLogLabel);
1341
1357
  recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, transportState.activeAdapter.name);
1342
- const result = await rebuildAndRefetch(upstreamResponse.status === 401 ? "oauth-401" : "anthropic-oauth-429");
1358
+ const result = await rebuildAndRefetch(upstreamResponse.status === 401 ? "oauth-401"
1359
+ : upstreamResponse.status === 403 ? "oauth-account-403" : "anthropic-oauth-429");
1343
1360
  if ("failed" in result) return result.failed;
1344
1361
  upstreamResponse = result;
1345
1362
  if (isNonReplayableResponse(upstreamResponse)) continue recovery;
@@ -0,0 +1,117 @@
1
+ import { jsonUtf8Bytes } from "../../lib/json-byte-size";
2
+ import {
3
+ TRANSLATOR_MAX_TURN_BYTES,
4
+ TranslatorBudgetExceededError,
5
+ type TranslatorBudget,
6
+ } from "../../lib/translator-budget";
7
+ import type { AdapterEvent, OcxMessage, OcxUsage } from "../../types";
8
+ import { mergeUsage } from "./terminal-guard";
9
+
10
+ export const MAX_ADVISOR_RETAINED_EVENTS = 4_096;
11
+ export const MAX_ADVISOR_RETAINED_CALLS = 1_024;
12
+
13
+ /** Independent ownership: never borrow the bridge's event or tool-call leases. */
14
+ export class AdvisorRetention {
15
+ private legBytes = 0;
16
+ private historyBytes = 0;
17
+ private copyBytes = 0;
18
+ private usageBytes = 0;
19
+ private events = 0;
20
+ private calls = 0;
21
+
22
+ constructor(private readonly shared?: TranslatorBudget) {}
23
+
24
+ private charge(bytes: number, kind: "retained_collectors" | "request_copies"): void {
25
+ if (bytes > TRANSLATOR_MAX_TURN_BYTES - this.legBytes - this.historyBytes - this.copyBytes - this.usageBytes) {
26
+ throw new TranslatorBudgetExceededError(kind, TRANSLATOR_MAX_TURN_BYTES);
27
+ }
28
+ this.shared?.chargeRetained(bytes, { kind });
29
+ }
30
+
31
+ /** Usage can alias a terminal payload. Keep its own owner after the raw leg ends. */
32
+ usage(first: OcxUsage | undefined, second: OcxUsage | undefined): OcxUsage | undefined {
33
+ if (!second) return first;
34
+ // Two-input merge materializes a new object. Reserve before merging, including
35
+ // a conservative bound for concatenation if a provider supplied malformed scalars.
36
+ const bytes = first
37
+ ? (jsonUtf8Bytes(first) + jsonUtf8Bytes(second)) * 2 + 256
38
+ : jsonUtf8Bytes(second) + 1;
39
+ this.charge(bytes, "request_copies");
40
+ this.copyBytes += bytes;
41
+ let retained = 0;
42
+ try {
43
+ const merged = mergeUsage(first, second);
44
+ const measured = jsonUtf8Bytes(merged, bytes) + 1;
45
+ if (measured > bytes) throw new TranslatorBudgetExceededError("request_copies", TRANSLATOR_MAX_TURN_BYTES);
46
+ retained = measured;
47
+ const previous = this.usageBytes;
48
+ this.usageBytes = retained;
49
+ this.shared?.releaseRetained(previous, { kind: "request_copies" });
50
+ return merged;
51
+ } finally {
52
+ this.copyBytes -= bytes;
53
+ this.shared?.releaseRetained(bytes - retained, { kind: "request_copies" });
54
+ }
55
+ }
56
+
57
+ /**
58
+ * `counted: false` is only for non-empty text/thinking deltas: the leg's 16 KiB character
59
+ * caps already bound how many of those can be held, and an ordinary long answer streams
60
+ * thousands of them. They still consume bytes.
61
+ */
62
+ retain(event: AdapterEvent, call = false, counted = true): void {
63
+ if ((counted && this.events >= MAX_ADVISOR_RETAINED_EVENTS) || (call && this.calls >= MAX_ADVISOR_RETAINED_CALLS)) {
64
+ throw new TranslatorBudgetExceededError("retained_collectors", TRANSLATOR_MAX_TURN_BYTES);
65
+ }
66
+ const bytes = jsonUtf8Bytes(event) + 1;
67
+ this.charge(bytes, "retained_collectors");
68
+ this.legBytes += bytes;
69
+ if (counted) this.events++;
70
+ if (call) this.calls++;
71
+ }
72
+
73
+ /** Hold a bounded materialization while its raw sources are still retained. */
74
+ copy<T>(bytes: number, build: () => T): T {
75
+ this.charge(bytes, "request_copies");
76
+ this.copyBytes += bytes;
77
+ try { return build(); }
78
+ finally {
79
+ this.copyBytes -= bytes;
80
+ this.shared?.releaseRetained(bytes, { kind: "request_copies" });
81
+ }
82
+ }
83
+
84
+ /** Reserve before constructing replay JSON; transfer the reservation to history. */
85
+ history<T extends OcxMessage | undefined>(bytes: number, build: () => T): T {
86
+ this.charge(bytes, "request_copies");
87
+ this.copyBytes += bytes;
88
+ let retained = 0;
89
+ try {
90
+ const message = build();
91
+ const measured = message ? jsonUtf8Bytes(message, bytes) + 1 : 0;
92
+ if (measured > bytes) throw new TranslatorBudgetExceededError("request_copies", TRANSLATOR_MAX_TURN_BYTES);
93
+ retained = measured;
94
+ this.historyBytes += retained;
95
+ return message;
96
+ } finally {
97
+ this.copyBytes -= bytes;
98
+ this.shared?.releaseRetained(bytes - retained, { kind: "request_copies" });
99
+ }
100
+ }
101
+
102
+ /** Event envelopes dominate the replay content; allow new message/call framing too. */
103
+ replayReservation(): number { return this.legBytes + (this.calls + 1) * 256; }
104
+
105
+ releaseLeg(): void {
106
+ this.shared?.releaseRetained(this.legBytes, { kind: "retained_collectors" });
107
+ this.legBytes = this.events = this.calls = 0;
108
+ }
109
+
110
+ release(): void {
111
+ this.releaseLeg();
112
+ this.shared?.releaseRetained(this.historyBytes, { kind: "request_copies" });
113
+ this.historyBytes = 0;
114
+ this.shared?.releaseRetained(this.usageBytes, { kind: "request_copies" });
115
+ this.usageBytes = 0;
116
+ }
117
+ }
@@ -1,3 +1,4 @@
1
+ import { assistantReplayContent } from "../../responses/assistant-replay";
1
2
  /**
2
3
  * Core-owned registration slot for the optional advisor subsystem (src/advisor).
3
4
  *
@@ -28,7 +29,9 @@ import type {
28
29
  OcxToolResultMessage,
29
30
  OcxUsage,
30
31
  } from "../../types";
31
- import { mergeUsage } from "./terminal-guard";
32
+ import { jsonStringPartsUtf8Bytes, jsonUtf8Bytes } from "../../lib/json-byte-size";
33
+ import { isTranslatorBudgetExceededError, TRANSLATOR_MAX_CALL_ARGUMENT_BYTES, TranslatorBudgetExceededError, type TranslatorBudget } from "../../lib/translator-budget";
34
+ import { AdvisorRetention } from "./advisor-retention";
32
35
 
33
36
  export const ADVISOR_TOOL_NAME = "advisor";
34
37
 
@@ -53,7 +56,7 @@ export interface AdvisorConsultOutcome {
53
56
  * Operator config blocked the consultation before any outbound call. Not a provider
54
57
  * failure: preflight must release a claim instead of recording a cooldown.
55
58
  */
56
- blocked?: "consent" | "settings";
59
+ blocked?: "consent" | "settings" | "budget" | "limit";
57
60
  }
58
61
 
59
62
  /** The structural plan the optional advisor subsystem registers per request. */
@@ -78,12 +81,19 @@ export interface AdvisorPlan {
78
81
  interface HeldAdvisorCall {
79
82
  id: string;
80
83
  name: string;
81
- argsBuf: string;
84
+ args: string[];
85
+ argumentBytes: number;
86
+ question?: string;
82
87
  closed: boolean;
83
88
  providerMetadata?: import("../../types").OcxProviderOpaqueToolCallMetadata;
84
89
  }
85
90
 
86
- function parseAdvisorArgs(argsBuf: string): { question?: string } {
91
+ function parseAdvisorArgs(call: HeldAdvisorCall, retention: AdvisorRetention): { question?: string } {
92
+ // Reserve both the joined input and parsed JSON before either is materialized.
93
+ return retention.copy(call.argumentBytes * 2 + 4, () => parseArgs(call.args.join("")));
94
+ }
95
+
96
+ function parseArgs(argsBuf: string): { question?: string } {
87
97
  if (argsBuf.trim() === "") return {};
88
98
  try {
89
99
  const parsed: unknown = JSON.parse(argsBuf);
@@ -99,31 +109,18 @@ function assistantMessageFromLeg(
99
109
  events: readonly AdapterEvent[],
100
110
  held: readonly HeldAdvisorCall[],
101
111
  timestamp: number,
112
+ retention: AdvisorRetention,
102
113
  ): OcxAssistantMessage | undefined {
103
- let text = "";
104
- let thinking = "";
105
- let signature: string | undefined;
106
- const redacted: string[] = [];
107
- for (const event of events) {
108
- if (event.type === "text_delta") text += event.text;
109
- else if (event.type === "thinking_delta") thinking += event.thinking;
110
- else if (event.type === "thinking_signature") signature = event.signature;
111
- else if (event.type === "redacted_thinking") redacted.push(event.data);
112
- }
113
- if (text.length > MAX_LEG_TEXT_CHARS) text = text.slice(0, MAX_LEG_TEXT_CHARS);
114
- if (thinking.length > MAX_LEG_THINKING_CHARS) thinking = thinking.slice(0, MAX_LEG_THINKING_CHARS);
115
- const content: OcxAssistantContentPart[] = [];
116
- if (thinking || signature || redacted.length > 0) {
117
- content.push({ type: "thinking", thinking, ...(signature ? { signature } : {}), ...(redacted.length > 0 ? { redacted } : {}) });
118
- }
119
- if (text) content.push({ type: "text", text });
114
+ const content = assistantReplayContent(events);
120
115
  for (const call of held) {
121
116
  if (!call.closed) continue;
117
+ const args = parseAdvisorArgs(call, retention);
118
+ call.question = args.question;
122
119
  content.push({
123
120
  type: "toolCall",
124
121
  id: call.id,
125
122
  name: call.name,
126
- arguments: parseAdvisorArgs(call.argsBuf),
123
+ arguments: args,
127
124
  ...(call.providerMetadata ? { providerMetadata: call.providerMetadata } : {}),
128
125
  });
129
126
  }
@@ -150,6 +147,8 @@ export interface AdvisorGuardOptions {
150
147
  parsed: OcxParsedRequest;
151
148
  plan: AdvisorPlan;
152
149
  firstEvents: AsyncIterable<AdapterEvent>;
150
+ /** Independent guard ownership within the optional shared request budget. */
151
+ translatorBudget?: TranslatorBudget;
153
152
  /** One bounded worker continuation re-dispatch (same machinery as the terminal guard). */
154
153
  continuation: (parsed: OcxParsedRequest) => AsyncIterable<AdapterEvent> | Promise<AsyncIterable<AdapterEvent>>;
155
154
  }
@@ -172,7 +171,8 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
172
171
  let parsed = options.parsed;
173
172
  let accumulatedUsage: OcxUsage | undefined;
174
173
  let source: AsyncIterable<AdapterEvent> = options.firstEvents;
175
-
174
+ const retention = new AdvisorRetention(options.translatorBudget);
175
+ try {
176
176
  while (true) {
177
177
  const held: HeldAdvisorCall[] = [];
178
178
  const legEvents: AdapterEvent[] = [];
@@ -189,15 +189,33 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
189
189
  for await (const event of source) {
190
190
  if (event.type === "tool_call_start") {
191
191
  if (pending) { held.push(pending); pending = null; }
192
- pending = { id: event.id, name: event.name, argsBuf: "", closed: false, ...(event.providerMetadata ? { providerMetadata: event.providerMetadata } : {}) };
193
192
  holdingCurrent = event.name === ADVISOR_TOOL_NAME;
194
- if (!holdingCurrent) hasRealToolCall = true;
195
- else continue;
193
+ if (!holdingCurrent) {
194
+ hasRealToolCall = true;
195
+ // No continuation can use this leg. Real-tool arguments are never copied.
196
+ held.length = legEvents.length = 0;
197
+ retention.releaseLeg();
198
+ } else {
199
+ if (!hasRealToolCall) {
200
+ retention.retain(event, true);
201
+ pending = { id: event.id, name: event.name, args: [], argumentBytes: 0, closed: false, ...(event.providerMetadata ? { providerMetadata: event.providerMetadata } : {}) };
202
+ }
203
+ continue;
204
+ }
196
205
  } else if (event.type === "tool_call_delta") {
197
- if (pending) pending.argsBuf += event.arguments;
206
+ if (pending) {
207
+ const bytes = jsonStringPartsUtf8Bytes([event.arguments], TRANSLATOR_MAX_CALL_ARGUMENT_BYTES + 2) - 2;
208
+ if (bytes > TRANSLATOR_MAX_CALL_ARGUMENT_BYTES - pending.argumentBytes) {
209
+ throw new TranslatorBudgetExceededError("tool_args", TRANSLATOR_MAX_CALL_ARGUMENT_BYTES);
210
+ }
211
+ retention.retain(event);
212
+ pending.argumentBytes += bytes;
213
+ pending.args.push(event.arguments);
214
+ }
198
215
  if (holdingCurrent) continue;
199
216
  } else if (event.type === "tool_call_end") {
200
217
  if (pending) {
218
+ retention.retain(event);
201
219
  pending.closed = true;
202
220
  if (pending.name === ADVISOR_TOOL_NAME) held.push(pending);
203
221
  pending = null;
@@ -207,6 +225,7 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
207
225
  continue;
208
226
  }
209
227
  } else if (event.type === "done") {
228
+ if (!hasRealToolCall) retention.retain(event);
210
229
  if (pending) { held.push(pending); pending = null; }
211
230
  terminalEvent = event;
212
231
  terminalConsumed = true;
@@ -214,22 +233,23 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
214
233
  } else if (event.type === "incomplete" || event.type === "error") {
215
234
  // A broken leg cannot safely anchor a continuation: surface the terminal as-is.
216
235
  if (pending) { pending = null; }
217
- const usage = mergeUsage(accumulatedUsage, event.usage);
218
- yield usage ? { ...event, usage } : event;
236
+ accumulatedUsage = retention.usage(accumulatedUsage, event.usage);
237
+ yield accumulatedUsage ? { ...event, usage: accumulatedUsage } : event;
219
238
  return;
220
239
  } else {
221
240
  // Retain (bounded) the leg's visible content so the rebuilt assistant message is complete.
222
241
  if (event.type === "text_delta") {
223
242
  legTextChars += event.text.length;
224
- if (legTextChars <= MAX_LEG_TEXT_CHARS) legEvents.push(event);
243
+ // Empty deltas add nothing to the rebuilt message; non-empty ones are bounded by the cap.
244
+ if (!hasRealToolCall && event.text !== "" && legTextChars <= MAX_LEG_TEXT_CHARS) { retention.retain(event, false, false); legEvents.push(event); }
225
245
  } else if (event.type === "thinking_delta") {
226
246
  legThinkingChars += event.thinking.length;
227
- if (legThinkingChars <= MAX_LEG_THINKING_CHARS) legEvents.push(event);
247
+ if (!hasRealToolCall && event.thinking !== "" && legThinkingChars <= MAX_LEG_THINKING_CHARS) { retention.retain(event, false, false); legEvents.push(event); }
228
248
  } else if (
229
249
  event.type === "thinking_signature"
230
250
  || event.type === "redacted_thinking"
231
251
  ) {
232
- legEvents.push(event);
252
+ if (!hasRealToolCall) { retention.retain(event); legEvents.push(event); }
233
253
  }
234
254
  }
235
255
  yield event;
@@ -245,14 +265,14 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
245
265
  if (!shouldIntercept) {
246
266
  // Plain leg: surface the terminal with merged usage from any earlier intercepted legs.
247
267
  if (terminalEvent) {
248
- const usage = mergeUsage(accumulatedUsage, terminalEvent.usage);
249
- yield usage ? { ...terminalEvent, usage } : terminalEvent;
268
+ accumulatedUsage = retention.usage(accumulatedUsage, terminalEvent.usage);
269
+ yield accumulatedUsage ? { ...terminalEvent, usage: accumulatedUsage } : terminalEvent;
250
270
  }
251
271
  return;
252
272
  }
253
273
 
254
274
  // Consume this leg's done — the continuation replaces it.
255
- accumulatedUsage = mergeUsage(accumulatedUsage, terminalEvent?.usage);
275
+ accumulatedUsage = retention.usage(accumulatedUsage, terminalEvent?.usage);
256
276
  if (continuations >= MAX_ADVISOR_CONTINUATIONS_PER_REQUEST || finalContinuationUsed) {
257
277
  yield {
258
278
  type: "error",
@@ -269,22 +289,22 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
269
289
  continuations += 1;
270
290
  finalContinuationUsed ||= isFinalContinuation;
271
291
  const timestamp = Date.now();
272
- const assistant = assistantMessageFromLeg(legEvents, advisorCalls, timestamp);
292
+ const assistant = retention.history(retention.replayReservation(), () => assistantMessageFromLeg(legEvents, advisorCalls, timestamp, retention));
273
293
  const messages: OcxMessage[] = [...parsed.context.messages];
274
294
  if (assistant) messages.push(assistant);
275
295
 
276
296
  for (const call of advisorCalls) {
277
297
  if (consultations >= maxConsultations) {
278
298
  // Runtime-owned limit text: not advice, and never a genuine advice wrapper.
279
- messages.push(advisorToolResult(call, {
299
+ const outcome = {
280
300
  ok: false,
281
301
  isError: true,
282
302
  content: plan.formatUnavailable("limit", "consultation limit reached for this request"),
283
- }, timestamp));
303
+ };
304
+ messages.push(retention.history(jsonUtf8Bytes(outcome) + jsonUtf8Bytes(call.id) + 256, () => advisorToolResult(call, outcome, timestamp))!);
284
305
  continue;
285
306
  }
286
307
  consultations += 1;
287
- const args = parseAdvisorArgs(call.argsBuf);
288
308
  // Later calls in the same leg must see what came before them: the rebuilt worker turn
289
309
  // and every earlier advisor result in this leg's accumulated messages.
290
310
  const consultParsed: OcxParsedRequest = {
@@ -293,8 +313,9 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
293
313
  };
294
314
  let outcome: AdvisorConsultOutcome;
295
315
  try {
296
- outcome = await plan.consult(consultParsed, "manual", args.question);
316
+ outcome = await plan.consult(consultParsed, "manual", call.question);
297
317
  } catch (error) {
318
+ if (isTranslatorBudgetExceededError(error)) throw error;
298
319
  // Runtime-owned failure text only: the implementation neutralizes untrusted exception
299
320
  // text so it can never forge a genuine advice wrapper.
300
321
  outcome = {
@@ -303,7 +324,7 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
303
324
  content: plan.formatUnavailable("manual", error instanceof Error ? error.message : String(error)),
304
325
  };
305
326
  }
306
- messages.push(advisorToolResult(call, outcome, timestamp));
327
+ messages.push(retention.history(jsonUtf8Bytes(outcome) + jsonUtf8Bytes(call.id) + 256, () => advisorToolResult(call, outcome, timestamp))!);
307
328
  }
308
329
 
309
330
  const exhausted = consultations >= maxConsultations;
@@ -320,10 +341,16 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
320
341
  : parsed.options,
321
342
  };
322
343
  parsed = nextParsed;
344
+ // All replay owners were admitted while the raw leg was still live.
345
+ held.length = legEvents.length = advisorCalls.length = 0;
346
+ pending = null;
347
+ terminalEvent = undefined;
348
+ retention.releaseLeg();
323
349
  yield { type: "assistant_boundary" };
324
350
  try {
325
351
  source = await options.continuation(parsed);
326
352
  } catch (error) {
353
+ if (isTranslatorBudgetExceededError(error)) throw error;
327
354
  yield {
328
355
  type: "error",
329
356
  message: error instanceof Error ? error.message : String(error),
@@ -332,5 +359,10 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
332
359
  return;
333
360
  }
334
361
  }
362
+ } catch (error) {
363
+ if (!isTranslatorBudgetExceededError(error)) throw error;
364
+ yield { type: "error", status: 502, errorType: "upstream_error", code: "translation_buffer_limit",
365
+ message: "upstream translation buffer exceeded the safe limit", ...(accumulatedUsage ? { usage: accumulatedUsage } : {}) };
366
+ } finally { retention.release(); }
335
367
  };
336
368
  }
@@ -1,3 +1,4 @@
1
+ import { normalizeResponsesIncompleteReason } from "../../responses/incomplete-reason";
1
2
  import { MAX_CLIENT_SSE_FRAME_BYTES } from "../sse-frame-buffer";
2
3
  import {
3
4
  isTransientUpstreamStatus,
@@ -8,6 +9,7 @@ import {
8
9
  import { isRateLimitOrQuotaFailureMessage, recognizedUpstreamError } from "../../lib/errors";
9
10
  import type { RequestFailureCause, RequestFailureStage } from "../../lib/request-failure-model";
10
11
  import { packageVersion } from "../../lib/package-version";
12
+ import { registerProviderResponseProvenance } from "../../providers/request-pacing";
11
13
  // If the 101 never arrives (network black hole), give SSE a chance well before
12
14
  // the caller's connect timeout (default 200s) would fire.
13
15
  export const UPGRADE_DEADLINE_MS = 10_000;
@@ -49,9 +51,12 @@ export const CODEX_WS_CREATE_FRAME_LIMIT_BYTES =
49
51
  /** Close code the backend uses for an oversized message (RFC 6455 "message too big"). */
50
52
  const WS_CLOSE_MESSAGE_TOO_BIG = 1009;
51
53
 
52
- const codexWsUpstreamResponses = new WeakSet<Response>();
53
- const quotaObservedResponses = new WeakSet<Response>();
54
- const codexWsPreludeProjections = new WeakSet<Response>();
54
+ const codexWsUpstreamResponses = new WeakMap<Response, true>();
55
+ const quotaObservedResponses = new WeakMap<Response, true>();
56
+ const codexWsPreludeProjections = new WeakMap<Response, true>();
57
+ registerProviderResponseProvenance(codexWsUpstreamResponses);
58
+ registerProviderResponseProvenance(quotaObservedResponses);
59
+ registerProviderResponseProvenance(codexWsPreludeProjections);
55
60
 
56
61
  /** Quota arrived directly at its captured account; do not replay old HTTP prelude headers. */
57
62
  export function isCodexWsQuotaObservedResponse(response: Response): boolean {
@@ -65,7 +70,7 @@ export function isCodexWsUpstreamResponse(response: Response): boolean {
65
70
 
66
71
  /** Pre-response projection; its headers retain the exchange's prelude snapshot. */
67
72
  export function markCodexWsPreludeProjection(response: Response): void {
68
- codexWsPreludeProjections.add(response);
73
+ codexWsPreludeProjections.set(response, true);
69
74
  }
70
75
 
71
76
  export function isCodexWsPreludeProjection(response: Response): boolean {
@@ -73,8 +78,8 @@ export function isCodexWsPreludeProjection(response: Response): boolean {
73
78
  }
74
79
 
75
80
  export function markCodexWsResponse(response: Response, observed: boolean): void {
76
- codexWsUpstreamResponses.add(response);
77
- if (observed) quotaObservedResponses.add(response);
81
+ codexWsUpstreamResponses.set(response, true);
82
+ if (observed) quotaObservedResponses.set(response, true);
78
83
  }
79
84
 
80
85
  /**
@@ -313,6 +318,7 @@ export function restoreCodexWsPreOutputRefusal(response: Response): Response {
313
318
  }
314
319
 
315
320
  const codexWsStageByResponse = new WeakMap<Response, CodexWsStageRecord>();
321
+ registerProviderResponseProvenance(codexWsStageByResponse);
316
322
 
317
323
  export function markCodexWsStage(response: Response, record: CodexWsStageRecord): void {
318
324
  const current = codexWsStageByResponse.get(response);
@@ -457,6 +463,7 @@ export function projectCodexWsFailure(
457
463
  }
458
464
 
459
465
  const socketDeathStages = new WeakMap<Response, RequestFailureStage>();
466
+ registerProviderResponseProvenance(socketDeathStages);
460
467
 
461
468
  /**
462
469
  * Record that a pre-response settle came from the socket closing or failing under the send (#4191),
@@ -517,12 +524,12 @@ export function normalizeResponsesWsRelayEvent(text: string): ResponsesWsRelayEv
517
524
  return null;
518
525
  }
519
526
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
520
- const record = payload as Record<string, unknown>;
527
+ const record = normalizeResponsesIncompleteReason(payload as Record<string, unknown>);
521
528
  if (typeof record.type !== "string") return null;
522
529
  // A native error may be pretty-printed JSON. SSE prefixes one data line;
523
530
  // embedded physical newlines would otherwise truncate the JSON for readers.
524
531
  if (record.type !== "response.done") return {
525
- type: record.type, text: /[\r\n]/.test(text) ? JSON.stringify(record) : text, payload: record,
532
+ type: record.type, text: record !== payload || /[\r\n]/.test(text) ? JSON.stringify(record) : text, payload: record,
526
533
  };
527
534
 
528
535
  const response = record.response;