@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
@@ -1,8 +1,10 @@
1
1
  import { spendLedgerStorageErrorResponse } from "./spend-storage-error";
2
- import { createPhysicalSendReporter } from "../../lib/request-execution-budget";
2
+ import { createPhysicalSendReporter, rebindPhysicalSend } from "../../lib/request-execution-budget";
3
3
  import { createInferenceSendBudget } from "../inference/context";
4
4
  import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
5
5
  import { capturePoolQuotaWriter } from "../../codex/account-store";
6
+ import { join } from "node:path";
7
+ import { rmSync } from "node:fs";
6
8
  import { previewXaiOauthWireModel } from "./core-normalize";
7
9
  import {
8
10
  admissionModelDeniedResponse,
@@ -14,12 +16,15 @@ import type { Server } from "bun";
14
16
  import { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type ResponsesTerminalStatus } from "../../bridge";
15
17
  import {
16
18
  getConfigPath,
19
+ getConfigDir,
17
20
  multiAgentGuidanceEnabled,
18
21
  } from "../../config";
19
22
  import { resolveProviderApiKey } from "../../providers/key-store";
20
23
  import { parseRequest } from "../../responses/parser";
21
24
  import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
25
+ import { applyReasoningRetention, appendRetentionNotice, type ReasoningRetentionResult } from "../../responses/reasoning-retention";
22
26
  import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
27
+ import { stripProviderInputItemMetadata } from "../../adapters/openai-responses/request-strips";
23
28
  import { expandPreviousResponseInput, previousResponseProviderState, rememberResponseState } from "../../responses/state";
24
29
  import { repairLegacyDottedToolCallNames } from "../../responses/legacy-dotted-tool-name-repair";
25
30
  import { NoEligiblePolicyCandidateError, routeCompactionModel } from "../../router";
@@ -127,6 +132,14 @@ import {
127
132
  import type { DataPlaneAdmission } from "../auth-cors";
128
133
  import { listOpenAiForwardSidecarCandidates, resolveFirstUsableOpenAiSidecar, type ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar";
129
134
  import { CODEX_FORWARD_BASE_URL, isCanonicalOpenAiForwardProvider, supportsNativeResponsesCompactEndpoint } from "../../providers/openai-tiers";
135
+ import { buildResponsesTransport, copyResponsesClientMetadata } from "../../adapters/openai-responses/transport";
136
+ import { scrubOcxCompactionItems, stripCanonicalOnlyTopLevelFields, stripInternalChatMessageMetadataPassthrough } from "../../adapters/openai-responses/request-strips";
137
+ import { dropResponsesReasoningInputItems } from "../../adapters/openai-responses/reasoning";
138
+ import { requiresPlaintextReasoningReplay } from "../../adapters/openai-responses/passthrough";
139
+ import { destinationDecodesNativeCompactionBlob, isOpenAiOperatedResponsesDestination } from "../../providers/openai-tiers-destination";
140
+ import { bindRouteReasoningReplayScope } from "./core-replay";
141
+ import { commitReasoningReplayServingIdentity } from "../../responses/reasoning-replay-cache";
142
+ import { nativeCompactResponseSchema } from "../../responses/schema";
130
143
  import { NATIVE_RESERVE_MODEL } from "../../codex/catalog/native-models";
131
144
  import {
132
145
  isCodexReserveOptInMissing,
@@ -151,7 +164,11 @@ import {
151
164
  UnsupportedContentEncodingError,
152
165
  } from "../request-decompress";
153
166
  import { resolveAdapter, resolveWireProtocolOverride } from "../adapter-resolve";
154
- import { hasKeyPoolFailover, rotateProviderTransportOn429, selectProactiveApiKeyTransport } from "../../providers/key-failover";
167
+ import { hasKeyPoolFailover, rotateProviderTransportOn429, selectProactiveApiKeyTransport, transientRetryPolicyFor } from "../../providers/key-failover";
168
+ import { transientSendCapFor } from "./request-send-budget";
169
+ import { distributedKeyPhysicalFetch, recordDistributedResponseFailure } from "../../providers/api-key-response-ownership";
170
+ import { providerApiKeySelectionIsCurrent, resolveCurrentProviderApiKeyTransport } from "../../providers/api-key-selection";
171
+ import { stampApiKeyAccountLabel } from "../../providers/label";
155
172
  import { shouldAttemptImageTierRetry } from "../image-retry";
156
173
  import { resolveProviderTransport } from "../../providers/xai-transport";
157
174
  import type { WsData } from "../ws-bridge";
@@ -168,6 +185,7 @@ import {
168
185
  catalogModelSupportsServiceTier,
169
186
  finishRequestAttempt,
170
187
  inspectResponseLogJson,
188
+ noteProviderAttemptSend,
171
189
  readConfiguredCodexServiceTier,
172
190
  requestLogSpeedLabel,
173
191
  sealRequestAttemptIdentity,
@@ -199,10 +217,11 @@ import {
199
217
  upstreamHostCircuitOpenResponse,
200
218
  usesCodexForwardPoolAuth,
201
219
  } from "./core";
202
- import { fetchWithHeaderTimeout, providerFetch, safeHostLabel, safeOriginLabel } from "./fetch-helpers";
220
+ import { fetchWithHeaderTimeout, providerFetch, safeHostLabel, safeOriginLabel, sendWithConnectionPolicy } from "./fetch-helpers";
221
+ import { requestPacingOverloadResponse } from "./pacing-overload";
203
222
  import { shouldRetryCodexPoolAccountQuota, shouldRetryCodexPoolAccountTransient } from "./core-codex-account";
204
223
  import { mapCodexAuthContextErrorToResponse, nativeMainRefreshFailureResponse } from "./codex-auth-error";
205
- import { linkRequestSessionLane, sessionLaneIdFromRequest } from "../request-log-conversation";
224
+ import { linkRequestSessionLane, sessionLaneIdFromRequest, reasoningReplayConversationIdFromResponsesRequest, sessionIdHeaderFromRequest } from "../request-log-conversation";
206
225
  import { recallComboForLane } from "./combo-session-recall";
207
226
  import { redactHostedImageDisplayPaths } from "../responses-hosted-image-display";
208
227
 
@@ -719,7 +738,8 @@ export async function handleResponsesCompact(
719
738
  // reads the account namespace as a routed provider prefix — so keying on `raw.model` sent
720
739
  // exactly the selector form back down the native compact endpoint this guard exists to avoid.
721
740
  // `route.modelId` is the same value `applyCodexAccountGatedWireNormalization` uses in core.ts.
722
- const accountGatedCompactWireModel = codexAccountGatedCanonicalWireModel(selectedModelId);
741
+ const accountGatedCompactWireModel = isCanonicalOpenAiForwardProvider(route.provider)
742
+ ? codexAccountGatedCanonicalWireModel(selectedModelId) : undefined;
723
743
  logCtx.requestedModel = compactRequestedModel;
724
744
  logCtx.model = selectedModelId;
725
745
  logCtx.routeDecision = route.routeDecision;
@@ -799,8 +819,8 @@ export async function handleResponsesCompact(
799
819
  }
800
820
 
801
821
  // Native /responses/compact exists on the canonical ChatGPT backend and on the
802
- // official OpenAI API. Any other Responses-shaped gateway must take the routed
803
- // summarizer path below, or compaction fails against an endpoint it never had (#422).
822
+ // official OpenAI API, or a gateway explicitly declaring the native endpoint. Other
823
+ // Responses-shaped gateways still take the routed summarizer path (#422).
804
824
  // Combo-resolved targets skip native compact so failover can advance through the
805
825
  // combo target list when the picked model returns 429/5xx — the routed path below
806
826
  // dispatches through handleResponses → handleComboResponses with full failover.
@@ -839,7 +859,7 @@ export async function handleResponsesCompact(
839
859
  compactHostAdmissionLease = admission.lease;
840
860
  }
841
861
  try {
842
- // Native ChatGPT/OpenAI model: forward the compact request verbatim to the real backend.
862
+ // Native-capable destination: forward the compact request to its configured backend.
843
863
  // Resolve the SAME pool/thread auth context as /v1/responses — forwarding the caller's raw
844
864
  // headers would run compaction on the wrong account (or 401) whenever a pool account is
845
865
  // active for this thread while normal turns succeed.
@@ -906,19 +926,35 @@ export async function handleResponsesCompact(
906
926
  // codexAccountMode cases, and the picker returns null for them anyway.
907
927
  // Transport variant, for the same reason as the Responses core: the persisted row has no
908
928
  // registry backfills, and compactProvider is read for its transport fields below.
909
- const warmKeyProvider = selectProactiveApiKeyTransport(config, route.providerName, compactProvider);
929
+ let warmKeyProvider;
930
+ try { warmKeyProvider = selectProactiveApiKeyTransport(config, route.providerName, compactProvider); }
931
+ catch (error) {
932
+ const refusal = requestPacingOverloadResponse(error);
933
+ if (refusal) return refusal;
934
+ throw error;
935
+ }
910
936
  if (warmKeyProvider?.apiKey) compactProvider = warmKeyProvider;
911
937
  headers.set("authorization", `Bearer ${resolveProviderApiKey(compactProvider.apiKey)}`);
938
+ stampApiKeyAccountLabel(logCtx, route.providerName, compactProvider);
912
939
  }
913
940
  const { reasoning: _reasoning, ...compactBodyRaw } = raw as typeof raw & { reasoning?: unknown };
941
+ const requireCompactPlaintextReasoning = !isOpenAiOperatedResponsesDestination(compactProvider)
942
+ && requiresPlaintextReasoningReplay(compactProvider);
914
943
  // The regular /v1/responses path applies sanitizeReasoningInputContent via the adapter's
915
944
  // buildRequest, but the compact endpoint forwards directly. Apply the same sanitizer here
916
- // so routed-model reasoning items (reasoning_text content) don't 400 the ChatGPT backend.
945
+ // so canonical reasoning stays blank and custom gateways honor their explicit plaintext policy.
917
946
  // #5095: the compact endpoint forwards `raw` directly, so it needs the same legacy dotted
918
947
  // call-name repair the adapter applies. A damaged item here refuses the compaction itself,
919
948
  // which is the request a long task depends on to keep going.
949
+ // This branch is OpenAI-only and bypasses the adapter's Google replay projection (#7111).
920
950
  const compactBody = repairLegacyDottedToolCallNames(
921
- sanitizeReasoningInputContent(compactBodyRaw),
951
+ stripProviderInputItemMetadata(sanitizeReasoningInputContent(compactBodyRaw, {
952
+ preserveRawReasoningContent: !isOpenAiOperatedResponsesDestination(compactProvider)
953
+ && compactProvider.preserveResponsesReasoningContent === true,
954
+ dropNullContentChannel: !isOpenAiOperatedResponsesDestination(compactProvider),
955
+ stripEncryptedContent: requireCompactPlaintextReasoning,
956
+ requirePlaintextReasoning: requireCompactPlaintextReasoning,
957
+ })),
922
958
  ) as typeof compactBodyRaw;
923
959
  {
924
960
  const binding = conversationStateBindingFromAuth(authCtx, codexPoolAffinityKey(req.headers));
@@ -945,9 +981,30 @@ export async function handleResponsesCompact(
945
981
  });
946
982
  }
947
983
  }
948
- const compactUrl = `${base}/responses/compact`;
984
+ // Gateway protocol capability never grants official credential authority. Reuse the normal
985
+ // Responses transport for custom paths, static auth and explicitly forwarded client metadata.
986
+ const gatewayTransport = !isCanonicalOpenAiForwardProvider(compactProvider)
987
+ && compactProvider.supportsNativeCompactEndpoint === true
988
+ ? buildResponsesTransport({ ...compactProvider, apiKey: compactProvider.apiKey ? resolveProviderApiKey(compactProvider.apiKey) : undefined }, { headers: req.headers })
989
+ : undefined;
990
+ if (gatewayTransport) headers = new Headers(gatewayTransport.headers);
991
+ // Native v1 bypasses the adapter serializer. Bind the same replay provenance as ordinary
992
+ // turns, but leave canonical compact's established account-state handling unchanged.
993
+ const gatewayReplayParsed = gatewayTransport ? parseRequest(raw) : undefined;
994
+ if (gatewayReplayParsed) {
995
+ gatewayReplayParsed.modelId = route.modelId;
996
+ gatewayReplayParsed._clientThreadId = req.headers.get("x-codex-parent-thread-id")?.trim() || undefined;
997
+ const clientThreadId = reasoningReplayConversationIdFromResponsesRequest({
998
+ clientThreadId: gatewayReplayParsed._clientThreadId,
999
+ threadIdHeader: req.headers.get("thread-id"),
1000
+ cursorConversationId: gatewayReplayParsed._cursorConversationId,
1001
+ sessionIdHeader: sessionIdHeaderFromRequest(req.headers),
1002
+ });
1003
+ if (clientThreadId) gatewayReplayParsed._reasoningReplayScope = { clientThreadId };
1004
+ }
1005
+ let compactUrl = gatewayTransport ? `${gatewayTransport.url.replace(/\/+$/, "")}/compact` : `${base}/responses/compact`;
949
1006
  const compactTargetKey = `${route.providerName}|${route.modelId}|compact`;
950
- const actualCompactHostKey = upstreamHostHealthKey(
1007
+ let actualCompactHostKey = upstreamHostHealthKey(
951
1008
  route.providerName,
952
1009
  safeOriginLabel(compactUrl),
953
1010
  );
@@ -999,7 +1056,8 @@ export async function handleResponsesCompact(
999
1056
  promoteAccountId?: string;
1000
1057
  } = {},
1001
1058
  ) => {
1002
- if (!usesCodexForwardPoolAuth(ctx, route.provider)) return;
1059
+ // A gateway response is not evidence about any Codex account.
1060
+ if (gatewayTransport || !usesCodexForwardPoolAuth(ctx, route.provider)) return;
1003
1061
  recordCodexUpstreamOutcome(config, ctx.accountId, outcome, {
1004
1062
  ...meta,
1005
1063
  threadId: ctx.kind === "pool" || ctx.kind === "main-pool" ? ctx.affinityKey : undefined,
@@ -1038,7 +1096,7 @@ export async function handleResponsesCompact(
1038
1096
  try {
1039
1097
  if (report.beforeSend?.() === false) throw new SendBudgetExhaustedError(safeHostLabel(compactUrl));
1040
1098
  started = true;
1041
- return await doFetch();
1099
+ return await (report.execute ? report.execute(doFetch) : doFetch());
1042
1100
  } finally {
1043
1101
  try { if (started) report(1); }
1044
1102
  finally { report.close?.(); }
@@ -1049,12 +1107,14 @@ export async function handleResponsesCompact(
1049
1107
  targetKey: compactTargetKey,
1050
1108
  });
1051
1109
  if (!decision.allowed) return Promise.reject(new SendBudgetExhaustedError(safeHostLabel(compactUrl)));
1052
- if (!decision.permit.use()) {
1053
- // Nothing was sent, so the reservation goes back rather than holding a send against the request.
1054
- decision.permit.release();
1055
- return Promise.reject(new SendBudgetExhaustedError(safeHostLabel(compactUrl)));
1056
- }
1057
- return doFetch();
1110
+ const dispatch = () => {
1111
+ if (!decision.permit.use()) {
1112
+ decision.permit.release();
1113
+ throw new SendBudgetExhaustedError(safeHostLabel(compactUrl));
1114
+ }
1115
+ return doFetch();
1116
+ };
1117
+ return decision.permit.execute ? decision.permit.execute(dispatch) : dispatch();
1058
1118
  };
1059
1119
  const sendCompactAttempt = (
1060
1120
  sendProvider: OcxProviderConfig,
@@ -1063,12 +1123,42 @@ export async function handleResponsesCompact(
1063
1123
  sendAuthCtx: CodexAuthContext,
1064
1124
  onPhysicalDispatch?: () => void,
1065
1125
  ): Promise<Response> => {
1126
+ // A gateway send carries only its own transport headers, whatever recovery handed in.
1127
+ let wireHeaders = gatewayTransport ? new Headers(gatewayTransport.headers) : sendHeaders;
1128
+ sendHeaders = wireHeaders;
1129
+ const buildCompactSendBody = () => {
1130
+ if (gatewayReplayParsed) bindRouteReasoningReplayScope({
1131
+ parsed: gatewayReplayParsed, providerName: route.providerName, provider: sendProvider,
1132
+ adapterName: sendProvider.adapter, codexAuthContext: sendAuthCtx, forwardHeaders: wireHeaders,
1133
+ });
1134
+ const requireSendPlaintextReasoning = !isOpenAiOperatedResponsesDestination(sendProvider)
1135
+ && requiresPlaintextReasoningReplay(sendProvider);
1136
+ let replayBody = gatewayReplayParsed ? sanitizeReasoningInputContent(scrubOcxCompactionItems(
1137
+ compactBody, destinationDecodesNativeCompactionBlob(sendProvider),
1138
+ gatewayReplayParsed._stripReasoningEncryptedContent === true,
1139
+ ), {
1140
+ preserveRawReasoningContent: !isOpenAiOperatedResponsesDestination(sendProvider)
1141
+ && sendProvider.preserveResponsesReasoningContent === true,
1142
+ dropNullContentChannel: !isOpenAiOperatedResponsesDestination(sendProvider),
1143
+ stripEncryptedContent: gatewayReplayParsed._stripReasoningEncryptedContent === true || requireSendPlaintextReasoning,
1144
+ dropForeignItemId: gatewayReplayParsed._dropForeignReasoningItemIds === true,
1145
+ requirePlaintextReasoning: requireSendPlaintextReasoning,
1146
+ }) : compactBody;
1147
+ if (!isOpenAiOperatedResponsesDestination(sendProvider) && sendProvider.dropResponsesReasoningItems === true) {
1148
+ replayBody = dropResponsesReasoningInputItems(replayBody) as typeof replayBody;
1149
+ }
1150
+ if (!isOpenAiOperatedResponsesDestination(sendProvider) && sendProvider.preserveResponsesMessageMetadata !== true) {
1151
+ replayBody = stripInternalChatMessageMetadataPassthrough(replayBody) as typeof replayBody;
1152
+ }
1153
+ return isOpenAiOperatedResponsesDestination(sendProvider)
1154
+ ? replayBody : stripCanonicalOnlyTopLevelFields(replayBody);
1155
+ };
1066
1156
  const doFetch = (upstreamRecovery?: UpstreamSendRecovery) => fetchWithHeaderTimeout(
1067
1157
  compactUrl,
1068
1158
  applyUpstreamRecoveryInit({
1069
1159
  method: "POST",
1070
- headers: sendHeaders,
1071
- body: JSON.stringify({ ...compactBody, model: route.modelId }),
1160
+ headers: wireHeaders,
1161
+ body: JSON.stringify({ ...buildCompactSendBody() as typeof compactBody, model: route.modelId }),
1072
1162
  }, upstreamRecovery),
1073
1163
  req.signal,
1074
1164
  connectMs,
@@ -1078,6 +1168,47 @@ export async function handleResponsesCompact(
1078
1168
  modelId: route.modelId,
1079
1169
  beforeDispatch: isCanonicalOpenAiForwardProvider(sendProvider)
1080
1170
  ? createCodexAuthDispatchGuard(sendAuthCtx, config, selectedModelId, admission) : undefined,
1171
+ dispatchOverride: sendProvider.authMode !== "forward" && sendProvider.apiKey
1172
+ ? async (_input, init, execute, reacquirePacing) => {
1173
+ for (let attempt = 0; !providerApiKeySelectionIsCurrent(config, route.providerName, sendProvider); attempt++) {
1174
+ if (attempt >= 3) throw new Error("Provider key selection changed repeatedly before compact dispatch");
1175
+ let current = resolveCurrentProviderApiKeyTransport(config, route.providerName, sendProvider);
1176
+ if (!current && sendProvider.requestPacing?.enabled
1177
+ && (sendProvider.requestPacing.keys || sendProvider.requestPacing.keySelection === "round-robin")) {
1178
+ const fresh = resolveCurrentProviderApiKeyTransport(config, route.providerName, { ...sendProvider, _apiKeyAttempt: undefined });
1179
+ if (fresh) current = selectProactiveApiKeyTransport(config, route.providerName, fresh) ?? fresh;
1180
+ }
1181
+ if (!current?.apiKey || !supportsNativeResponsesCompactEndpoint(route.providerName, current)) {
1182
+ throw new Error("Provider key selection is no longer available for compact");
1183
+ }
1184
+ const rebuilt = new Headers(sendHeaders);
1185
+ for (const name of Object.keys(sendProvider.headers ?? {})) rebuilt.delete(name);
1186
+ for (const [name, value] of Object.entries(current.headers ?? {})) rebuilt.set(name, value);
1187
+ rebuilt.set("authorization", `Bearer ${resolveProviderApiKey(current.apiKey)}`);
1188
+ const currentGateway = gatewayTransport
1189
+ ? buildResponsesTransport({ ...current, apiKey: resolveProviderApiKey(current.apiKey) }, { headers: req.headers })
1190
+ : undefined;
1191
+ sendHeaders = wireHeaders = headers = currentGateway ? new Headers(currentGateway.headers) : rebuilt;
1192
+ sendProvider = compactProvider = current;
1193
+ compactUrl = currentGateway ? `${currentGateway.url.replace(/\/+$/, "")}/compact`
1194
+ : `${(current.baseUrl ?? "").replace(/\/+$/, "")}/responses/compact`;
1195
+ actualCompactHostKey = upstreamHostHealthKey(route.providerName, safeOriginLabel(compactUrl));
1196
+ if (current.requestPacing?.enabled
1197
+ && (current.requestPacing.keys || current.requestPacing.keySelection === "round-robin")) {
1198
+ if (!reacquirePacing) throw new Error("Refreshed compact dispatch requires pacing readmission");
1199
+ await reacquirePacing(current, route.modelId);
1200
+ }
1201
+ }
1202
+ if (req.signal.aborted) throw req.signal.reason;
1203
+ stampApiKeyAccountLabel(logCtx, route.providerName, sendProvider);
1204
+ if (!rebindPhysicalSend(sendBudget, { poolId: logCtx.spendPoolId ?? route.providerName,
1205
+ identityId: logCtx.accountLogLabel })) throw new SendBudgetExhaustedError(safeHostLabel(compactUrl));
1206
+ noteProviderAttemptSend(logCtx, route.providerName, sendProvider, logCtx.usageLogInputTokens, upstreamRecovery);
1207
+ return sendWithConnectionPolicy(
1208
+ distributedKeyPhysicalFetch((sendProvider as { fetch?: typeof fetch }).fetch ?? execute, route.providerName, sendProvider, compactUrl), compactUrl,
1209
+ { ...init, headers: sendHeaders, body: JSON.stringify({ ...buildCompactSendBody() as typeof compactBody, model: route.modelId }) },
1210
+ { providerName: route.providerName, provider: sendProvider });
1211
+ } : undefined,
1081
1212
  ...(onPhysicalDispatch ? { onPhysicalDispatch } : {}),
1082
1213
  }),
1083
1214
  // Every credential-bearing forward send gets manual redirects, not only
@@ -1094,9 +1225,11 @@ export async function handleResponsesCompact(
1094
1225
  : fetchWithTransientRetry(doFetch, {
1095
1226
  abortSignal: req.signal,
1096
1227
  label: safeHostLabel(compactUrl),
1097
- // Draws the shared remainder instead of a fresh three. Compact is a native endpoint
1098
- // of the same logical turn, so its sends belong to the same cap.
1099
- attempts: sendBudget.remainingBaseSends(TRANSIENT_RETRY_MAX_ATTEMPTS),
1228
+ // Key-auth policy is a logical-request total; canonical forward keeps its default
1229
+ // ladder. Both intersect the same shared base remainder as ordinary Responses.
1230
+ attempts: sendBudget.remainingBaseSends(transientSendCapFor(
1231
+ transientRetryPolicyFor(sendProvider)?.attempts, sendBudget.used,
1232
+ )),
1100
1233
  onSendsConsumed: createPhysicalSendReporter(sendBudget, () => ({ poolId: logCtx.spendPoolId ?? route.providerName,
1101
1234
  identityId: logCtx.accountLogLabel })),
1102
1235
  });
@@ -1107,7 +1240,7 @@ export async function handleResponsesCompact(
1107
1240
  let outcomeCtx = authCtx;
1108
1241
  const localDispatchRefusal = (error: unknown): Response | undefined => {
1109
1242
  const cause = unwrapUpstreamRetryEvidenceError(error);
1110
- const response = spendLedgerStorageErrorResponse(error, logCtx) ?? (cause instanceof SendBudgetExhaustedError
1243
+ const response = requestPacingOverloadResponse(cause) ?? spendLedgerStorageErrorResponse(error, logCtx) ?? (cause instanceof SendBudgetExhaustedError
1111
1244
  ? unboundPoolSpendRefusalResponse(logCtx) ?? formatErrorResponse(429, "request_send_budget_exhausted", cause.message)
1112
1245
  : mapCodexAuthContextErrorToResponse(cause, {
1113
1246
  now: Date.now(), accountSelector: route.codexAccountNamespace,
@@ -1152,9 +1285,11 @@ export async function handleResponsesCompact(
1152
1285
  return formatErrorResponse(502, "upstream_error", "Failed to connect to compact upstream");
1153
1286
  }
1154
1287
 
1288
+ recordDistributedResponseFailure(upstream, config, route.providerName, req.signal);
1155
1289
  if (
1156
1290
  upstream.status === 401
1157
1291
  && (authCtx.kind === "main-pool" || authCtx.kind === "pool")
1292
+ && !gatewayTransport
1158
1293
  && usesCodexForwardPoolAuth(authCtx, compactProvider)
1159
1294
  && !req.signal.aborted
1160
1295
  ) {
@@ -1282,6 +1417,7 @@ export async function handleResponsesCompact(
1282
1417
  // compact turn that may already have been processed.
1283
1418
  && !isNonReplayableResponse(upstream)
1284
1419
  && !storedPool401ReplayAttempted
1420
+ && !gatewayTransport
1285
1421
  && usesCodexForwardPoolAuth(authCtx, route.provider)
1286
1422
  && !authCtx.fixedAccount
1287
1423
  && route.codexAccountMode
@@ -1386,6 +1522,7 @@ export async function handleResponsesCompact(
1386
1522
  if (
1387
1523
  shouldRetryCodexPoolAccountTransient(upstream)
1388
1524
  && !storedPool401ReplayAttempted
1525
+ && !gatewayTransport
1389
1526
  && usesCodexForwardPoolAuth(authCtx, route.provider)
1390
1527
  && !authCtx.fixedAccount
1391
1528
  && route.codexAccountMode
@@ -1494,7 +1631,15 @@ export async function handleResponsesCompact(
1494
1631
  // request log; the routed branch gets the same through handleResponses. The
1495
1632
  // synthetic buffer errors are not upstream bodies and stay uninspected.
1496
1633
  if (buffered.ok) {
1497
- inspectResponseLogJson(logCtx, await buffered.clone().text());
1634
+ const compactText = await buffered.clone().text();
1635
+ if (gatewayReplayParsed) {
1636
+ try {
1637
+ if (nativeCompactResponseSchema.safeParse(JSON.parse(compactText)).success) {
1638
+ commitReasoningReplayServingIdentity(gatewayReplayParsed._reasoningReplayScope);
1639
+ }
1640
+ } catch { /* A malformed body cannot establish serving provenance. */ }
1641
+ }
1642
+ inspectResponseLogJson(logCtx, compactText);
1498
1643
  if (!options.compactionRoutingOverride) forgetCompactHandoffRoute(req, admission);
1499
1644
  rememberServingConversationStateIssuer(outcomeCtx, codexPoolAffinityKey(req.headers));
1500
1645
  } else if (!options.compactionRoutingOverride && quotaFailure && !storedPool401ReplayAttempted) {
@@ -1535,112 +1680,145 @@ export async function handleResponsesCompact(
1535
1680
 
1536
1681
  // ROUTED model: run the v2 synthetic-compaction turn internally (appends COMPACT_PROMPT, no
1537
1682
  // tools) and decode the resulting ocx1 envelope into plain v1 replacement-history items.
1538
- const inputItems = Array.isArray(raw.input) ? (raw.input as unknown[]) : [];
1539
- const internalBody = {
1540
- ...raw,
1541
- // Canonical ChatGPT Responses rejects non-streaming turns. Daybreak cannot use the
1542
- // native compact endpoint either, so run its synthetic compaction as SSE and collapse
1543
- // the completed event back into the v1 compact JSON contract below. Combo-dispatched
1544
- // turns also go out as SSE: failover can land on a canonical child that rejects a
1545
- // non-streaming turn, and every combo-capable provider already serves streaming traffic.
1546
- stream: isCanonicalOpenAiForwardProvider(route.provider) || accountGatedCompactWireModel || route.combo ? true : false,
1547
- input: [...inputItems, { type: "compaction_trigger" }],
1548
- };
1549
- const internalHeaders = new Headers({ "content-type": "application/json" });
1550
- for (const name of FORWARD_HEADERS) {
1551
- const value = req.headers.get(name);
1552
- if (value) internalHeaders.set(name, value);
1553
- }
1554
- const internalReq = new Request("http://localhost/v1/responses", {
1555
- method: "POST",
1556
- headers: internalHeaders,
1557
- body: JSON.stringify(internalBody),
1558
- });
1559
- linkRequestSessionLane(req, internalReq);
1560
- // The routed compaction turn is a handoff inside the same logical request, so it draws the
1561
- // REMAINDER. Minting here is what let a native attempt spend three sends and the routed
1562
- // fallback spend four more.
1563
- const response = await handleResponses(internalReq, config, logCtx, { abortSignal: req.signal, turnAdmissionLease, sendBudget, compactionRecoveryKind: "compaction-v1", compactionRoutingOverride: options.compactionRoutingOverride, ...(admission ? { admission } : {}) });
1564
- if (!response.ok) return response;
1565
- let json: { output?: unknown[]; status?: unknown; error?: unknown };
1566
- if (response.headers.get("content-type")?.includes("text/event-stream")) {
1567
- if (!response.body) {
1568
- return formatErrorResponse(502, "server_error", "compaction turn returned an empty event stream");
1683
+ const rawInputItems = Array.isArray(raw.input) ? (raw.input as unknown[]) : [];
1684
+ // Keep readable reasoning local until success, outside the summarizer request.
1685
+ // Opaque compaction responses below keep their original protocol and ciphertext.
1686
+ const retention: ReasoningRetentionResult = config.reasoningRetention?.enabled === true
1687
+ && !isCanonicalOpenAiForwardProvider(route.provider) ? applyReasoningRetention(rawInputItems, {
1688
+ archiveDir: join(getConfigDir(), "reasoning-archive"),
1689
+ contextWindow: route.staticPolicy.model.contextWindow ?? route.staticPolicy.model.maxInputTokens,
1690
+ maxContextPercent: config.reasoningRetention?.maxContextPercent,
1691
+ maxTokens: config.reasoningRetention?.maxTokens,
1692
+ }) : { input: rawInputItems };
1693
+ let keepArchive = false;
1694
+ try {
1695
+ const inputItems = retention.input;
1696
+ const internalBody = {
1697
+ ...raw,
1698
+ // Canonical ChatGPT Responses rejects non-streaming turns. Daybreak cannot use the
1699
+ // native compact endpoint either, so run its synthetic compaction as SSE and collapse
1700
+ // the completed event back into the v1 compact JSON contract below. Combo-dispatched
1701
+ // turns also go out as SSE: failover can land on a canonical child that rejects a
1702
+ // non-streaming turn, and every combo-capable provider already serves streaming traffic.
1703
+ stream: isCanonicalOpenAiForwardProvider(route.provider) || accountGatedCompactWireModel || route.combo ? true : false,
1704
+ input: [...inputItems, { type: "compaction_trigger" }],
1705
+ };
1706
+ const internalHeaders = new Headers({ "content-type": "application/json" });
1707
+ for (const name of FORWARD_HEADERS) {
1708
+ const value = req.headers.get(name);
1709
+ if (value) internalHeaders.set(name, value);
1569
1710
  }
1570
- const terminal = { status: "incomplete" as "completed" | "failed" | "incomplete" };
1571
- let completed: { id?: unknown; output?: unknown; status?: unknown } | undefined;
1572
- await new Promise<void>(resolve => {
1573
- consumeForInspection(
1574
- response.body!,
1575
- status => { terminal.status = status; },
1576
- req.signal,
1577
- resolve,
1578
- undefined,
1579
- undefined,
1580
- value => { completed = value; },
1581
- );
1711
+ copyResponsesClientMetadata(internalHeaders, req.headers, route.provider);
1712
+ const internalReq = new Request("http://localhost/v1/responses", {
1713
+ method: "POST",
1714
+ headers: internalHeaders,
1715
+ body: JSON.stringify(internalBody),
1582
1716
  });
1717
+ linkRequestSessionLane(req, internalReq);
1718
+ // The routed compaction turn is a handoff inside the same logical request, so it draws the
1719
+ // REMAINDER. Minting here is what let a native attempt spend three sends and the routed
1720
+ // fallback spend four more.
1721
+ const response = await handleResponses(internalReq, config, logCtx, { abortSignal: req.signal, turnAdmissionLease, sendBudget, compactionRecoveryKind: "compaction-v1", compactionRoutingOverride: options.compactionRoutingOverride, ...(admission ? { admission } : {}) });
1722
+ if (req.signal.aborted) {
1723
+ void response.body?.cancel(req.signal.reason).catch(() => undefined);
1724
+ return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
1725
+ }
1726
+ if (!response.ok) return response;
1727
+ let json: { output?: unknown[]; status?: unknown; error?: unknown };
1728
+ if (response.headers.get("content-type")?.includes("text/event-stream")) {
1729
+ if (!response.body) {
1730
+ return formatErrorResponse(502, "server_error", "compaction turn returned an empty event stream");
1731
+ }
1732
+ const terminal = { status: "incomplete" as "completed" | "failed" | "incomplete" };
1733
+ let completed: { id?: unknown; output?: unknown; status?: unknown } | undefined;
1734
+ await new Promise<void>(resolve => {
1735
+ consumeForInspection(
1736
+ response.body!,
1737
+ status => { terminal.status = status; },
1738
+ req.signal,
1739
+ resolve,
1740
+ undefined,
1741
+ undefined,
1742
+ value => { completed = value; },
1743
+ );
1744
+ });
1745
+ if (req.signal.aborted) {
1746
+ return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
1747
+ }
1748
+ if (terminal.status !== "completed" || !completed) {
1749
+ return formatErrorResponse(502, "upstream_error", `compaction turn did not complete (status: ${terminal.status})`);
1750
+ }
1751
+ json = completed as { output?: unknown[]; status?: unknown; error?: unknown };
1752
+ } else {
1753
+ try {
1754
+ json = await response.json() as { output?: unknown[]; status?: unknown; error?: unknown };
1755
+ } catch {
1756
+ return formatErrorResponse(502, "server_error", "compaction turn returned a non-JSON response");
1757
+ }
1758
+ }
1583
1759
  if (req.signal.aborted) {
1584
1760
  return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
1585
1761
  }
1586
- if (terminal.status !== "completed" || !completed) {
1587
- return formatErrorResponse(502, "upstream_error", `compaction turn did not complete (status: ${terminal.status})`);
1762
+ // The internal turn answers 200 even when it failed or was truncated, so the body
1763
+ // has to be inspected. Reporting a failure beats installing "(no summary
1764
+ // available)" as replacement history and silently losing the conversation (#422).
1765
+ if (json.error) {
1766
+ const message = typeof json.error === "string"
1767
+ ? json.error
1768
+ : (json.error as { message?: unknown })?.message;
1769
+ return formatErrorResponse(502, "upstream_error", typeof message === "string" ? message : "compaction turn failed");
1588
1770
  }
1589
- json = completed as { output?: unknown[]; status?: unknown; error?: unknown };
1590
- } else {
1591
- try {
1592
- json = await response.json() as { output?: unknown[]; status?: unknown; error?: unknown };
1593
- } catch {
1594
- return formatErrorResponse(502, "server_error", "compaction turn returned a non-JSON response");
1771
+ if (json.status !== "completed") {
1772
+ return formatErrorResponse(
1773
+ 502,
1774
+ "upstream_error",
1775
+ `compaction turn did not complete (status: ${String(json.status ?? "unknown")})`,
1776
+ );
1595
1777
  }
1596
- }
1597
- // The internal turn answers 200 even when it failed or was truncated, so the body
1598
- // has to be inspected. Reporting a failure beats installing "(no summary
1599
- // available)" as replacement history and silently losing the conversation (#422).
1600
- if (json.error) {
1601
- const message = typeof json.error === "string"
1602
- ? json.error
1603
- : (json.error as { message?: unknown })?.message;
1604
- return formatErrorResponse(502, "upstream_error", typeof message === "string" ? message : "compaction turn failed");
1605
- }
1606
- if (json.status !== "completed") {
1607
- return formatErrorResponse(
1608
- 502,
1609
- "upstream_error",
1610
- `compaction turn did not complete (status: ${String(json.status ?? "unknown")})`,
1611
- );
1612
- }
1613
- const compactionItems = (json.output ?? []).filter(
1614
- (item): item is { type: string; encrypted_content?: string } =>
1615
- !!item && typeof item === "object" && (item as { type?: string }).type === "compaction",
1616
- );
1617
- if (compactionItems.length !== 1) {
1618
- return formatErrorResponse(
1619
- 502,
1620
- "invalid_response_error",
1621
- `compaction turn produced ${compactionItems.length} compaction items, expected exactly 1`,
1778
+ const compactionItems = (json.output ?? []).filter(
1779
+ (item): item is { type: string; encrypted_content?: string } =>
1780
+ !!item && typeof item === "object" && (item as { type?: string }).type === "compaction",
1622
1781
  );
1623
- }
1624
- // Native Responses backends return a real opaque OpenAI-encrypted compaction item. OCX cannot
1625
- // and should not decrypt it; preserve that item for /responses/compact callers. Synthetic
1626
- // routed summaries are our `ocx1:` envelope and must be decoded into v1 history items.
1627
- if (typeof compactionItems[0]!.encrypted_content === "string"
1628
- && compactionItems[0]!.encrypted_content.trim().length > 0
1629
- && !compactionItems[0]!.encrypted_content.startsWith("ocx1:")) {
1630
- const result = new Response(JSON.stringify({ output: compactionItems }), {
1631
- headers: { "Content-Type": "application/json" },
1632
- });
1782
+ if (compactionItems.length !== 1) {
1783
+ return formatErrorResponse(
1784
+ 502,
1785
+ "invalid_response_error",
1786
+ `compaction turn produced ${compactionItems.length} compaction items, expected exactly 1`,
1787
+ );
1788
+ }
1789
+ // Native Responses backends return a real opaque OpenAI-encrypted compaction item. OCX cannot
1790
+ // and should not decrypt it; preserve that item for /responses/compact callers. Synthetic
1791
+ // routed summaries are our `ocx1:` envelope and must be decoded into v1 history items.
1792
+ if (typeof compactionItems[0]!.encrypted_content === "string"
1793
+ && compactionItems[0]!.encrypted_content.trim().length > 0
1794
+ && !compactionItems[0]!.encrypted_content.startsWith("ocx1:")) {
1795
+ // Failover may reach native compaction after readable reasoning was withheld locally.
1796
+ // The opaque item cannot contain that text, so publish it (or its archive notice) alongside.
1797
+ const output = [...compactionItems, ...(retention.retainedReasoning ?? [])];
1798
+ if (retention.archived) output.push({ type: "message", role: "user",
1799
+ content: [{ type: "input_text", text: appendRetentionNotice("", retention.archived).trimStart() }] });
1800
+ const result = new Response(JSON.stringify({ output }), {
1801
+ headers: { "Content-Type": "application/json" },
1802
+ });
1803
+ if (!options.compactionRoutingOverride) rememberCompactHandoffRoute(req, admission, raw.model);
1804
+ keepArchive = true;
1805
+ return result;
1806
+ }
1807
+ const encrypted = compactionItems[0]!.encrypted_content;
1808
+ const decoded = typeof encrypted === "string" ? decodeCompactionSummary(encrypted) : null;
1809
+ // An empty `ocx1:` envelope decodes to "" rather than null, so length is what matters.
1810
+ if (decoded === null || decoded.trim().length === 0) {
1811
+ return formatErrorResponse(502, "invalid_response_error", "compaction turn produced an empty summary");
1812
+ }
1813
+ const summary = appendRetentionNotice(decoded, retention.archived);
1814
+ const output = [...buildCompactV1Output(extractCompactUserMessages(inputItems), summary),
1815
+ ...(retention.retainedReasoning ?? [])];
1633
1816
  if (!options.compactionRoutingOverride) rememberCompactHandoffRoute(req, admission, raw.model);
1817
+ const result = new Response(JSON.stringify({ output }), { headers: { "Content-Type": "application/json" } });
1818
+ keepArchive = true;
1634
1819
  return result;
1820
+ } finally {
1821
+ // Keep an archive only after successful replacement history publishes its path.
1822
+ if (!keepArchive && retention.archived) rmSync(retention.archived.path, { force: true });
1635
1823
  }
1636
- const encrypted = compactionItems[0]!.encrypted_content;
1637
- const decoded = typeof encrypted === "string" ? decodeCompactionSummary(encrypted) : null;
1638
- // An empty `ocx1:` envelope decodes to "" rather than null, so length is what matters.
1639
- if (decoded === null || decoded.trim().length === 0) {
1640
- return formatErrorResponse(502, "invalid_response_error", "compaction turn produced an empty summary");
1641
- }
1642
- const summary = decoded;
1643
- const output = buildCompactV1Output(extractCompactUserMessages(inputItems), summary);
1644
- if (!options.compactionRoutingOverride) rememberCompactHandoffRoute(req, admission, raw.model);
1645
- return new Response(JSON.stringify({ output }), { headers: { "Content-Type": "application/json" } });
1646
1824
  }