@bitkyc08/opencodex 2.81.0 → 2.83.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 (272) hide show
  1. package/bin/ocx.mjs +166 -73
  2. package/gui/dist/assets/App-Ba8uG696.js +54 -0
  3. package/gui/dist/assets/{Tray-EwAOsP0S.js → Tray-IImbNPRy.js} +1 -1
  4. package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
  5. package/gui/dist/assets/{index-RsoaAxXY.js → index-BHRZdJuv.js} +13 -13
  6. package/gui/dist/assets/{quota-summary-D2lN8tDm.js → quota-summary-DiJO6sYY.js} +1 -1
  7. package/gui/dist/index.html +2 -2
  8. package/package.json +1 -1
  9. package/src/adapters/anthropic.ts +1 -1
  10. package/src/adapters/cursor/catalog.ts +2 -2
  11. package/src/adapters/cursor/effort-map.ts +1 -1
  12. package/src/adapters/devin/live-models.ts +2 -2
  13. package/src/adapters/google-truncation.ts +22 -0
  14. package/src/adapters/google.ts +7 -3
  15. package/src/adapters/ollama-native.ts +18 -14
  16. package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
  17. package/src/adapters/openai-responses/passthrough.ts +11 -4
  18. package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
  19. package/src/adapters/openai-responses/reasoning.ts +11 -1
  20. package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
  21. package/src/bridge/errors.ts +7 -1
  22. package/src/claude/agents-inject.ts +14 -3
  23. package/src/claude/context-windows.ts +22 -20
  24. package/src/claude/desktop-3p.ts +88 -12
  25. package/src/claude/desktop-picker.ts +1 -1
  26. package/src/claude/desktop-profile.ts +2 -1
  27. package/src/claude/gateway-cache.ts +18 -1
  28. package/src/claude/inbound.ts +77 -3
  29. package/src/claude/intercept/cli-catalog.ts +3 -0
  30. package/src/claude/intercept/picker-ca-persistence.ts +7 -6
  31. package/src/claude/intercept/picker-models.ts +14 -6
  32. package/src/claude/intercept/settings.ts +3 -0
  33. package/src/claude/long-context.ts +66 -0
  34. package/src/claude/model-info.ts +19 -16
  35. package/src/claude/outbound.ts +94 -9
  36. package/src/claude/subagent-model.ts +40 -11
  37. package/src/cli/account-auth.ts +20 -12
  38. package/src/cli/agent.ts +9 -5
  39. package/src/cli/aside-profile-recovery.ts +33 -0
  40. package/src/cli/capabilities-base.ts +5 -5
  41. package/src/cli/capabilities-integrations.ts +2 -2
  42. package/src/cli/cli-path-diagnostics.ts +132 -0
  43. package/src/cli/desktop-runtime-guidance.ts +65 -0
  44. package/src/cli/dispatch.ts +5 -0
  45. package/src/cli/doctor.ts +16 -3
  46. package/src/cli/index.ts +14 -9
  47. package/src/cli/integration-aside-sync.ts +2 -1
  48. package/src/cli/integrations.ts +16 -3
  49. package/src/cli/resolve.ts +26 -3
  50. package/src/cli/root.ts +4 -2
  51. package/src/cli/status.ts +64 -12
  52. package/src/cli/update-restart-child.ts +4 -2
  53. package/src/cli/update-restart-home.ts +28 -10
  54. package/src/cli/update-restart-service-record.ts +116 -0
  55. package/src/cli/update-restart-supervision.ts +96 -0
  56. package/src/cli/update-restart.ts +59 -13
  57. package/src/cli/v2.ts +27 -20
  58. package/src/cli/version-skew-notice.ts +77 -0
  59. package/src/clients/config-export/model-metadata.ts +16 -0
  60. package/src/clients/config-export/zcode-store.ts +46 -11
  61. package/src/clients/config-export/zcode.ts +3 -5
  62. package/src/clients/config-export.ts +1 -1
  63. package/src/clients/lazycodex.ts +1 -1
  64. package/src/clients/omo-role-models.ts +32 -7
  65. package/src/codex/auth-api/account-selection.ts +107 -0
  66. package/src/codex/auth-api/pool-mode-gate.ts +4 -5
  67. package/src/codex/auth-api/routes.ts +10 -49
  68. package/src/codex/auth-context.ts +28 -4
  69. package/src/codex/autostart-health.ts +12 -3
  70. package/src/codex/catalog/build-entries.ts +18 -0
  71. package/src/codex/catalog/bundled.ts +155 -26
  72. package/src/codex/catalog/combo-member.ts +3 -2
  73. package/src/codex/catalog/effort.ts +26 -5
  74. package/src/codex/catalog/gather-capture.ts +5 -0
  75. package/src/codex/catalog/model-hints.ts +15 -8
  76. package/src/codex/catalog/model-visibility.ts +3 -1
  77. package/src/codex/catalog/native-models.ts +1 -1
  78. package/src/codex/catalog/operator-tiers.ts +38 -0
  79. package/src/codex/catalog/provider-models.ts +68 -35
  80. package/src/codex/catalog/retained-sync.ts +7 -5
  81. package/src/codex/catalog/routed-gather.ts +15 -1
  82. package/src/codex/catalog-auto-refresh-sources.ts +3 -4
  83. package/src/codex/catalog-auto-refresh.ts +19 -0
  84. package/src/codex/catalog.ts +1 -1
  85. package/src/codex/codex-home-owner.ts +3 -0
  86. package/src/codex/config-write-lock.ts +333 -0
  87. package/src/codex/convergence.ts +2 -1
  88. package/src/codex/desktop-app/darwin.ts +1 -1
  89. package/src/codex/desktop-app/handoff.ts +1 -1
  90. package/src/codex/desktop-app/linux.ts +1 -1
  91. package/src/codex/desktop-app/lock.ts +1 -1
  92. package/src/codex/desktop-app/types.ts +1 -1
  93. package/src/codex/desktop-app/windows.ts +1 -1
  94. package/src/codex/desktop-app-restart.ts +1 -1
  95. package/src/codex/features.ts +257 -200
  96. package/src/codex/inject/config-write-section.ts +34 -0
  97. package/src/codex/inject/multi-agent-v2.ts +14 -6
  98. package/src/codex/inject/remove.ts +36 -11
  99. package/src/codex/inject/restore.ts +85 -20
  100. package/src/codex/inject-coordination.ts +15 -6
  101. package/src/codex/inject.ts +62 -20
  102. package/src/codex/journal.ts +62 -17
  103. package/src/codex/main-account-cache.ts +43 -0
  104. package/src/codex/main-account.ts +6 -7
  105. package/src/codex/paths.ts +2 -0
  106. package/src/codex/prepared-home.ts +18 -0
  107. package/src/codex/prompt-journal.ts +25 -12
  108. package/src/codex/prompt-layers.ts +49 -26
  109. package/src/codex/prompt-lock-claim.ts +134 -0
  110. package/src/codex/prompt-lock-evidence.ts +26 -0
  111. package/src/codex/prompt-lock-io.ts +16 -0
  112. package/src/codex/prompt-lock-owner.ts +130 -0
  113. package/src/codex/prompt-lock.ts +84 -53
  114. package/src/codex/routing/failure-window.ts +146 -0
  115. package/src/codex/routing/selection.ts +4 -1
  116. package/src/codex/routing.ts +13 -17
  117. package/src/codex/runtime.ts +106 -18
  118. package/src/codex/shim.ts +45 -19
  119. package/src/combos/jev-service-exchange.ts +281 -0
  120. package/src/combos/jev.ts +34 -220
  121. package/src/config/atomic-write.ts +1 -1
  122. package/src/config/codex-ws-reuse-setting.ts +10 -0
  123. package/src/config/live-reconcile.ts +17 -0
  124. package/src/config/load-degrade.ts +8 -0
  125. package/src/config/rebase-provenance.ts +11 -0
  126. package/src/config/schema/config-schema.ts +4 -0
  127. package/src/generated/compatibility-version.json +375 -227
  128. package/src/generated/model-metadata.ts +2 -2
  129. package/src/grok/inject.ts +276 -206
  130. package/src/grok/reset-coupon-ledger.ts +223 -57
  131. package/src/grok/reset-coupons.ts +13 -6
  132. package/src/grok/sync.ts +2 -1
  133. package/src/images/loop.ts +29 -11
  134. package/src/integrations/aside-profiles.ts +10 -3
  135. package/src/integrations/catalog-refresh.ts +18 -2
  136. package/src/integrations/cursor-detect.ts +1 -1
  137. package/src/integrations/writer.ts +9 -3
  138. package/src/lib/bun-path-runtime.d.mts +32 -0
  139. package/src/lib/bun-path-runtime.mjs +95 -0
  140. package/src/lib/bun-runtime-preflight.ts +140 -0
  141. package/src/lib/desktop-cli-handoff.d.mts +20 -0
  142. package/src/lib/desktop-cli-handoff.mjs +92 -0
  143. package/src/lib/desktop-cli-record.d.mts +29 -0
  144. package/src/lib/desktop-cli-record.mjs +102 -0
  145. package/src/lib/errors.ts +25 -0
  146. package/src/lib/redact.ts +2 -2
  147. package/src/lib/request-execution-budget.ts +8 -0
  148. package/src/lib/synced-state-location.ts +46 -6
  149. package/src/lib/test-home-guard.ts +87 -1
  150. package/src/lib/translator-budget.ts +17 -4
  151. package/src/lib/windows-atomic-replace.ts +38 -16
  152. package/src/lib/windows-elevation.ts +6 -0
  153. package/src/lib/windows-secret-acl.ts +92 -25
  154. package/src/lib/winsw.ts +2 -3
  155. package/src/oauth/anthropic-account-refusal.ts +39 -1
  156. package/src/oauth/anthropic-routing.ts +1 -1
  157. package/src/oauth/anthropic-send-ownership.ts +3 -3
  158. package/src/oauth/chatgpt.ts +113 -41
  159. package/src/oauth/index.ts +7 -3
  160. package/src/oauth/meta-muse-device.ts +1 -1
  161. package/src/oauth/store.ts +1 -1
  162. package/src/oauth/types.ts +1 -1
  163. package/src/providers/anthropic-fast.ts +1 -1
  164. package/src/providers/anthropic-reset-grant-ledger.ts +1 -1
  165. package/src/providers/azure-model-metadata.ts +115 -0
  166. package/src/providers/derive.ts +21 -0
  167. package/src/providers/devin-cli-authmode-migration.ts +1 -1
  168. package/src/providers/devin-provider-merge-migration.ts +1 -1
  169. package/src/providers/github-copilot-auto.ts +169 -0
  170. package/src/providers/model-presets.ts +1 -1
  171. package/src/providers/muse-subscription-usage.ts +1 -1
  172. package/src/providers/quota/vendor-probes-key.ts +127 -17
  173. package/src/providers/quota.ts +1 -1
  174. package/src/providers/registry/entries-core.ts +6 -6
  175. package/src/providers/registry/entries-extended.ts +4 -0
  176. package/src/providers/registry/model-seeds.ts +3 -3
  177. package/src/providers/xai-fast-model.ts +1 -1
  178. package/src/responses/reasoning-envelope.ts +29 -1
  179. package/src/responses/reasoning-replay-cache.ts +17 -1
  180. package/src/responses/spill-store.ts +15 -2
  181. package/src/responses/state/metrics.ts +8 -0
  182. package/src/responses/state/spill-failure.ts +12 -0
  183. package/src/responses/state/spill-queue.ts +12 -5
  184. package/src/responses/state.ts +18 -10
  185. package/src/responses/task-input.ts +17 -0
  186. package/src/server/background-lifecycle.ts +27 -0
  187. package/src/server/chat-completions.ts +1 -0
  188. package/src/server/chat-native.ts +28 -12
  189. package/src/server/claude-messages.ts +43 -6
  190. package/src/server/inbound-body-admission.ts +25 -7
  191. package/src/server/index/claude-intercept-lifecycle.ts +3 -1
  192. package/src/server/index/link-listener.ts +30 -17
  193. package/src/server/index/serve-options.ts +18 -4
  194. package/src/server/index/spend-ledger-lifecycle.ts +2 -1
  195. package/src/server/index/startup-warnings.ts +19 -0
  196. package/src/server/index.ts +2 -1
  197. package/src/server/lifecycle.ts +31 -4
  198. package/src/server/management/agent-settings-routes.ts +93 -32
  199. package/src/server/management/codex-agent-role-routes.ts +4 -2
  200. package/src/server/management/codex-prompt-routes.ts +1 -0
  201. package/src/server/management/codex-role-auto-assign.ts +13 -2
  202. package/src/server/management/config-routes.ts +59 -8
  203. package/src/server/management/context.ts +2 -2
  204. package/src/server/management/grok-coupon-routes.ts +277 -52
  205. package/src/server/management/link-routes.ts +42 -20
  206. package/src/server/management/route-registry.ts +2 -2
  207. package/src/server/management/shared.ts +3 -3
  208. package/src/server/messages-native-oauth.ts +9 -8
  209. package/src/server/messages-native.ts +49 -13
  210. package/src/server/messages-response-headers.ts +14 -0
  211. package/src/server/relay-eager.ts +2 -0
  212. package/src/server/relay.ts +18 -7
  213. package/src/server/request-log-conversation.ts +48 -5
  214. package/src/server/request-log-failure-attribution.ts +3 -0
  215. package/src/server/request-log-terminal-status.ts +186 -0
  216. package/src/server/request-log.ts +15 -89
  217. package/src/server/responses/adapter-continuation.ts +11 -3
  218. package/src/server/responses/adapter-dispatch.ts +99 -19
  219. package/src/server/responses/codex-ws-exchange.ts +17 -2
  220. package/src/server/responses/codex-ws-pool.ts +100 -13
  221. package/src/server/responses/codex-ws-wire.ts +9 -0
  222. package/src/server/responses/combo-requested-effort.ts +3 -1
  223. package/src/server/responses/compact.ts +3 -2
  224. package/src/server/responses/core-codex-account.ts +22 -1
  225. package/src/server/responses/core-combo-native.ts +20 -3
  226. package/src/server/responses/core-combo.ts +22 -14
  227. package/src/server/responses/core-lifetime.ts +3 -36
  228. package/src/server/responses/core-opaque-recovery.ts +5 -5
  229. package/src/server/responses/core-options.ts +5 -0
  230. package/src/server/responses/core-replay.ts +73 -0
  231. package/src/server/responses/core.ts +9 -9
  232. package/src/server/responses/empty-completion-guard.ts +18 -2
  233. package/src/server/responses/fetch-helpers.ts +26 -3
  234. package/src/server/responses/jev-model-invoke.ts +1 -1
  235. package/src/server/responses/passthrough-delivery.ts +24 -2
  236. package/src/server/responses/passthrough-dispatch.ts +134 -62
  237. package/src/server/responses/passthrough-execution.ts +3 -3
  238. package/src/server/responses/request-prepare.ts +4 -0
  239. package/src/server/responses/request-send-budget.ts +112 -49
  240. package/src/server/responses/request-transport.ts +125 -5
  241. package/src/server/responses/response-effects.ts +1 -0
  242. package/src/server/responses/run-turn-execution.ts +15 -5
  243. package/src/server/responses/sidecar-execution.ts +30 -16
  244. package/src/server/responses/sidecar-send-budget.ts +58 -0
  245. package/src/server/responses/spend-storage-error.ts +33 -0
  246. package/src/server/responses/ws-upstream.ts +19 -6
  247. package/src/server/startup-health-cache.ts +2 -1
  248. package/src/server/workflow-refusal.ts +3 -0
  249. package/src/service/cli.ts +20 -4
  250. package/src/service/desktop-command-guard.ts +32 -0
  251. package/src/service/desktop-startup.ts +66 -40
  252. package/src/service/desktop-supervision.d.mts +26 -0
  253. package/src/service/desktop-supervision.mjs +140 -0
  254. package/src/service/managing-cli.ts +1 -0
  255. package/src/service/orchestration.ts +46 -20
  256. package/src/service/repair.ts +25 -9
  257. package/src/service/state.ts +4 -3
  258. package/src/service/systemd.ts +17 -8
  259. package/src/service/windows-ops.ts +61 -22
  260. package/src/storage/policy-job.ts +5 -1
  261. package/src/storage/policy-worker.ts +3 -1
  262. package/src/storage/policy.ts +9 -0
  263. package/src/types/config.ts +21 -0
  264. package/src/types/request.ts +10 -0
  265. package/src/types/wire.ts +1 -0
  266. package/src/update/index.ts +57 -9
  267. package/src/update/restart-ownership.ts +10 -3
  268. package/src/update/runtime-ownership.d.mts +6 -0
  269. package/src/update/runtime-ownership.mjs +23 -7
  270. package/src/usage/expected-prices.ts +1 -1
  271. package/src/web-search/loop.ts +5 -2
  272. package/gui/dist/assets/App-7yhoys8a.js +0 -54
@@ -32,7 +32,7 @@ import {
32
32
  type RequestLogContext,
33
33
  } from "../request-log";
34
34
  import { captureRouteStaticPolicy, routeConcreteModel } from "../../router";
35
- import { comboDefaultEffort, concreteComboRequestBody, getCombo } from "../../combos";
35
+ import { comboDefaultEffort, concreteComboRequestBody, getCombo, type JevDecision } from "../../combos";
36
36
  import { supportedLadderFor } from "../effort-policy";
37
37
  import { applyDroidReasoningDefault } from "../droid-reasoning-default";
38
38
  import { resolveWireProtocolOverride } from "../adapter-resolve";
@@ -94,6 +94,7 @@ export interface ComboProtocolLanes {
94
94
  target: ComboTarget,
95
95
  targetRoute: RouteResult,
96
96
  targetSendBudget: TransientSendBudget | undefined,
97
+ initialDecision?: Pick<JevDecision, "effort">,
97
98
  ): NativeComboChildPlan | undefined;
98
99
  }
99
100
 
@@ -101,6 +102,7 @@ interface CandidateVerdict {
101
102
  skip?: ProtocolFeature[];
102
103
  }
103
104
 
105
+ /** Build opt-in native eligibility and child plans; only the initial plan applies a JEV choice. */
104
106
  export function createComboProtocolLanes(input: {
105
107
  source: ComboProtocolSource | undefined;
106
108
  req: Request;
@@ -203,7 +205,7 @@ export function createComboProtocolLanes(input: {
203
205
  400, unrepresentableMessage(features), "invalid_request_error", "unsupported_feature",
204
206
  ), "chat");
205
207
  },
206
- nativeChild: (target, targetRoute, targetSendBudget) => {
208
+ nativeChild: (target, targetRoute, targetSendBudget, initialDecision) => {
207
209
  // Without the request's execution budget a native child could not share its sends, so it
208
210
  // keeps the bridge rather than opening a tracker of its own.
209
211
  if (!isRequestExecutionBudget(targetSendBudget)) return undefined;
@@ -214,7 +216,22 @@ export function createComboProtocolLanes(input: {
214
216
  provider: targetRoute.provider,
215
217
  modelId: targetRoute.modelId,
216
218
  });
217
- applyComboEffort(body, config, comboId, target, targetRoute);
219
+ if (initialDecision) {
220
+ // A validated JEV choice owns only this first dispatch, including explicit no-effort.
221
+ // Do not run ordinary force defaults for null: they require a non-null default.
222
+ delete body.service_tier;
223
+ delete body.thinking_budget;
224
+ delete body.thinking;
225
+ const reasoning = body.reasoning;
226
+ if (reasoning && typeof reasoning === "object" && !Array.isArray(reasoning)) {
227
+ const preserved = { ...reasoning as Rec };
228
+ delete preserved.effort;
229
+ if (Object.keys(preserved).length > 0) body.reasoning = preserved;
230
+ else delete body.reasoning;
231
+ }
232
+ if (initialDecision.effort === null) delete body.reasoning_effort;
233
+ else body.reasoning_effort = initialDecision.effort;
234
+ } else applyComboEffort(body, config, comboId, target, targetRoute);
218
235
  return { route, body, sendBudget: targetSendBudget };
219
236
  },
220
237
  };
@@ -717,30 +717,32 @@ export async function executeComboResponses(
717
717
  const targetRoute = routeConcreteModel(config, `${pick.target.provider}/${pick.target.model}`);
718
718
  // The inherited spend tracker observes this parent log, before the child has its own label.
719
719
  logCtx.spendPoolId = targetRoute.providerName;
720
- // The first target seeds the ledger's target identity and charges nothing; every later one
721
- // is a real transition, refused once the declared hops, the alternate-target ledger or the
722
- // request total are spent. `countedExternally` is required: the child charges its own
723
- // physical sends, and charging here as well would halve the cap without saying so.
720
+ // Derive the target's allowance before booking its initial send; the booking occupies it.
721
+ const targetSendBudget = comboSendScope
722
+ ? comboTargetSendBudget(comboSendScope, combo.targets.length - 1 - comboTargetsDispatched)
723
+ : options.sendBudget;
724
+ // Every target prepays one send. Only its child may consume that booking; later targets
725
+ // remain bounded by the shared total, transition ledger and per-target holdback.
726
+ // `countedExternally` is required: the child charges its own physical sends against this
727
+ // booking through the exact permit, and charging here as well would halve the cap.
724
728
  const hopDecision = comboSendScope?.reserveDispatch({
725
729
  sendClass: firstComboTarget ? "initial" : "combo-failover",
726
730
  targetKey: `${pick.target.provider}/${pick.target.model}`,
727
731
  countedExternally: true,
728
732
  });
729
- if (hopDecision && hopDecision.allowed) hopDecision.permit.use();
730
- else if (hopDecision && firstComboTarget) {
733
+ if (hopDecision && !hopDecision.allowed && firstComboTarget) {
731
734
  // A refused initial reservation authorizes no child send and has no upstream failure to return.
732
735
  return formatErrorResponse(429, SEND_BUDGET_EXHAUSTED_CODE, "request send budget exhausted before combo dispatch");
733
736
  }
734
- else if (hopDecision) {
737
+ else if (hopDecision && !hopDecision.allowed) {
735
738
  // Out of budget is not this target's failure. The established exhaustion contract is to
736
739
  // return the last real upstream answer with its status, headers and any quota body
737
740
  // intact rather than to mint a synthetic error, and a later target only exists because
738
741
  // an earlier one already recorded one.
739
742
  return exhaustedFailure();
740
743
  }
741
- const targetSendBudget = comboSendScope
742
- ? comboTargetSendBudget(comboSendScope, combo.targets.length - 1 - comboTargetsDispatched)
743
- : options.sendBudget;
744
+ const initialSend = hopDecision?.allowed ? { permit: hopDecision.permit, producerOwned: false } : undefined;
745
+ try {
744
746
  comboTargetsDispatched += 1;
745
747
  const childLog: RequestLogContext = {
746
748
  model: pick.target.model,
@@ -764,7 +766,7 @@ export async function executeComboResponses(
764
766
  initialJevDecision ? initialJevDecision.effort : comboDefaultEffort(config, comboId),
765
767
  initialJevDecision?.effort === null ? [] : targetReasoningEfforts,
766
768
  combo.reasoningEffortMode,
767
- initialJevDecision !== undefined && initialJevDecision.effort !== null ? "force" : combo.defaultEffortMode,
769
+ initialJevDecision ? (initialJevDecision.effort === null ? "fallback" : "force") : combo.defaultEffortMode,
768
770
  );
769
771
  if (initialJevDecision) {
770
772
  delete childBody.service_tier;
@@ -860,7 +862,7 @@ export async function executeComboResponses(
860
862
  && targetEligible(target)
861
863
  && !isComboTargetInCooldown(comboId, target),
862
864
  );
863
- const nativeChild = protocolLanes?.nativeChild(pick.target, targetRoute, targetSendBudget);
865
+ const nativeChild = protocolLanes?.nativeChild(pick.target, targetRoute, targetSendBudget, initialJevDecision);
864
866
  response = nativeChild ? await dispatchNativeComboChild({
865
867
  source: options.protocolSource!,
866
868
  plan: nativeChild,
@@ -890,6 +892,7 @@ export async function executeComboResponses(
890
892
  // parent arrived with.
891
893
  sendBudget: targetSendBudget,
892
894
  comboAttempt: true,
895
+ comboInitialSend: initialSend,
893
896
  comboDispatchPermit: hopDecision?.allowed ? hopDecision.permit : undefined,
894
897
  comboReplaySnapshot,
895
898
  deferCodexResetDerivedCooldown,
@@ -909,8 +912,8 @@ export async function executeComboResponses(
909
912
  onNativePassthroughCancel: callbackGate.onCancel,
910
913
  onResponseComplete: callbackGate.onResponseComplete,
911
914
  });
912
- // Native Chat rebuilds its own body and does not apply the initial JEV effort.
913
- restoreOriginalRequestedEffort(childLog, nativeChild ? undefined : initialJevDecision?.effort);
915
+ // Both lanes applied the initial choice before provider pins/caps; retain those transitions.
916
+ restoreOriginalRequestedEffort(childLog, initialJevDecision?.effort);
914
917
  } catch (error) {
915
918
  callbackGate.discard();
916
919
  if (options.abortSignal?.aborted) {
@@ -1185,6 +1188,11 @@ export async function executeComboResponses(
1185
1188
  // Waiting or recovery may have observed cancellation after the check above.
1186
1189
  if (options.abortSignal?.aborted) return clientCancelledResponse();
1187
1190
  }
1191
+ } finally {
1192
+ // External receipts make release a no-op after real work. runTurn owns its asynchronous
1193
+ // producer; core transfers cleanup there before returning a streaming response.
1194
+ if (!initialSend?.producerOwned) initialSend?.permit.release();
1195
+ }
1188
1196
  }
1189
1197
  const failure = exhaustedFailure();
1190
1198
  if (
@@ -1,4 +1,5 @@
1
1
  import type { TranslatorBudget } from "../../lib/translator-budget";
2
+ import { finalizeTranslatorBudgetResponse } from "../../lib/translator-budget";
2
3
  import {
3
4
  isNativePassthroughSseResponse,
4
5
  markNativePassthroughSseResponse,
@@ -37,42 +38,8 @@ export const UPSTREAM_JSON_BODY_READ_OPTIONS = {
37
38
 
38
39
 
39
40
 
40
- export function finalizeOwnedTranslatorBudget(response: Response, budget: TranslatorBudget): Response {
41
- if (!response.body) {
42
- budget.dispose();
43
- return response;
44
- }
45
- const reader = response.body.getReader();
46
- let finalized = false;
47
- const finalize = () => {
48
- if (finalized) return;
49
- finalized = true;
50
- budget.dispose();
51
- };
52
- const body = new ReadableStream<Uint8Array>({
53
- async pull(controller) {
54
- try {
55
- const result = await reader.read();
56
- if (result.done) {
57
- finalize();
58
- controller.close();
59
- } else {
60
- controller.enqueue(result.value);
61
- }
62
- } catch (error) {
63
- finalize();
64
- controller.error(error);
65
- }
66
- },
67
- async cancel(reason) {
68
- try { await reader.cancel(reason); } finally { finalize(); }
69
- },
70
- });
71
- const finalizedResponse = new Response(body, {
72
- status: response.status,
73
- statusText: response.statusText,
74
- headers: response.headers,
75
- });
41
+ export function finalizeOwnedTranslatorBudget(response: Response, budget: TranslatorBudget, signal?: AbortSignal): Response {
42
+ const finalizedResponse = finalizeTranslatorBudgetResponse(response, budget, signal);
76
43
  if (isNativePassthroughSseResponse(response)) {
77
44
  markNativePassthroughSseResponse(finalizedResponse);
78
45
  }
@@ -465,13 +465,13 @@ export function resetStreamedOpaqueBlobLogContext(logCtx: RequestLogContext): vo
465
465
  export type OpaqueBlobRecoveryGuard = { attempted: boolean };
466
466
 
467
467
 
468
- export type OpaqueBlobRecoveryResult =
468
+ export type OpaqueBlobRecoveryResult<Failure = Response> =
469
469
  | { kind: "skipped" }
470
470
  | { kind: "recovered"; response: Response }
471
- | { kind: "failed"; response: Response };
471
+ | { kind: "failed"; response: Failure };
472
472
 
473
473
 
474
- export async function attemptOpaqueBlobRecovery(
474
+ export async function attemptOpaqueBlobRecovery<Failure = Response>(
475
475
  args: {
476
476
  response: Response;
477
477
  outboundBody?: string;
@@ -480,8 +480,8 @@ export async function attemptOpaqueBlobRecovery(
480
480
  guard: OpaqueBlobRecoveryGuard;
481
481
  signal: AbortSignal;
482
482
  },
483
- rebuild: (kind: AttemptRecoveryKind) => Promise<Response | { failed: Response }>,
484
- ): Promise<OpaqueBlobRecoveryResult> {
483
+ rebuild: (kind: AttemptRecoveryKind) => Promise<Response | { failed: Failure }>,
484
+ ): Promise<OpaqueBlobRecoveryResult<Failure>> {
485
485
  const errorBody = await opaqueBlobRejectionBodyForRecovery(
486
486
  args.response,
487
487
  args.outboundBody,
@@ -16,6 +16,7 @@ import type { ExplicitOpenAiCallerAuth } from "../../providers/openai-sidecar";
16
16
  import type { CallerDirectAuth } from "../../providers/caller-authorization";
17
17
  import type { CompactionRoutingOverride } from "./compaction-routing";
18
18
  import type { TranslatorBudget } from "../../lib/translator-budget";
19
+ import type { NativeReasoningOwner } from "../../responses/reasoning-replay-cache";
19
20
  import type { TransientSendBudget } from "../../lib/upstream-retry";
20
21
  import type { RequestLogContext } from "../request-log";
21
22
  import type { UpstreamHostAdmissionLease } from "../../codex/upstream-host-health";
@@ -134,6 +135,8 @@ export interface HandleResponsesOptions {
134
135
  * it. Omitted means a genuine Responses inbound.
135
136
  */
136
137
  inboundWire?: InboundWire;
138
+ nativeReasoningReplay?: ReadonlyMap<string, string>;
139
+ nativeReasoningMint?: { owner?: NativeReasoningOwner };
137
140
  /** Droid's per-request effort default; each concrete combo or policy target applies it only if its ladder allows it. */
138
141
  droidDefaultEffort?: string;
139
142
  /** PF-07: the Chat source a combo child may send natively; set only by the Chat ingress. */
@@ -158,6 +161,8 @@ export interface HandleResponsesOptions {
158
161
  callerDirectAuth?: CallerDirectAuth | null;
159
162
  /** Internal recursion guard; callers outside this module must not set it. */
160
163
  comboAttempt?: boolean;
164
+ /** Child-owned prepaid initial send; capacity remains charged until dispatch or unsent release. */
165
+ comboInitialSend?: { permit: SingleUseDispatchPermit; producerOwned?: boolean };
161
166
  /** Exact externally booked combo hop, used for its child's spend preflight and send reports. */
162
167
  comboDispatchPermit?: SingleUseDispatchPermit;
163
168
  /** Internal handoff: this combo was selected by shadow-call interception. */
@@ -19,6 +19,9 @@ import {
19
19
  reasoningReplayCodexCredentialIdentity,
20
20
  reasoningReplayKeyCredentialIdentity,
21
21
  durableReplayDestinationIdentity,
22
+ nativeReasoningTag,
23
+ nativeReasoningTagMatches,
24
+ type NativeReasoningOwner,
22
25
  bindReasoningReplayScope,
23
26
  reasoningReplayServingIdentityChanged,
24
27
  reasoningReplayItemStoreChanged,
@@ -118,6 +121,16 @@ export function bindRouteReasoningReplayScope(args: {
118
121
  ...args,
119
122
  modelId: parsed.modelId,
120
123
  });
124
+ const owner = nativeReasoningOwnerForRoute(args);
125
+ if (owner) parsed._nativeReasoningBoundOwner = owner;
126
+ else delete parsed._nativeReasoningBoundOwner;
127
+ if (parsed._nativeReasoningReplay?.size) {
128
+ for (const [blob, tag] of parsed._nativeReasoningReplay) {
129
+ if (!nativeReasoningTagMatches(nativeReasoningTag(owner, blob), tag)) {
130
+ stripNativeReasoningBlob(parsed, blob);
131
+ }
132
+ }
133
+ }
121
134
  const continuationDestinationIdentity = providerContinuationDestinationIdentity(parsed, provider);
122
135
  const continuationOwner = providerContinuationOwnerFromReplayIdentity(
123
136
  replayIdentity && continuationDestinationIdentity
@@ -156,6 +169,66 @@ export function bindRouteReasoningReplayScope(args: {
156
169
  bindProviderContinuationForRoute(parsed, continuationOwner);
157
170
  }
158
171
 
172
+ function stripNativeReasoningBlob(parsed: OcxParsedRequest, blob: string): void {
173
+ const input = (parsed._rawBody as { input?: unknown } | undefined)?.input;
174
+ if (Array.isArray(input)) {
175
+ for (const item of input) {
176
+ if (item && typeof item === "object" && item.type === "reasoning" && item.encrypted_content === blob) {
177
+ delete item.encrypted_content;
178
+ delete item.id;
179
+ }
180
+ }
181
+ }
182
+ for (const message of parsed.context.messages) {
183
+ if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
184
+ for (const part of message.content) {
185
+ if (part.type !== "thinking" || typeof part.signature !== "string") continue;
186
+ try {
187
+ const item: unknown = JSON.parse(part.signature);
188
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
189
+ const reasoning = item as Record<string, unknown>;
190
+ if (reasoning.type !== "reasoning" || reasoning.encrypted_content !== blob) continue;
191
+ delete reasoning.encrypted_content;
192
+ delete reasoning.id;
193
+ part.signature = JSON.stringify(reasoning);
194
+ delete part.itemId;
195
+ } catch { /* A provider signature is not the parser's JSON carrier. */ }
196
+ }
197
+ }
198
+ }
199
+
200
+ export function nativeReasoningOwnerForRoute(args: {
201
+ provider: OcxProviderConfig;
202
+ oauthCredentialSnapshot?: Pick<OAuthAccessSnapshot, "accountId" | "generation">;
203
+ codexAuthContext?: CodexAuthContext;
204
+ }): NativeReasoningOwner | undefined {
205
+ const { provider } = args;
206
+ const salt = thoughtSignatureReplaySalt();
207
+ if (!salt || salt.length < 16) return undefined;
208
+ const destination = durableReplayDestinationIdentity(JSON.stringify([
209
+ provider.baseUrl.trim().replace(/\/+$/, ""), provider.responsesPath ?? "",
210
+ ]));
211
+ let credential: string | undefined;
212
+ if (provider.authMode === "oauth") {
213
+ const snapshot = args.oauthCredentialSnapshot;
214
+ if (snapshot?.accountId && snapshot.generation !== undefined) credential = durableReplayCredentialIdentity(
215
+ "oauth", `${snapshot.accountId}\0${snapshot.generation}`, provider.headers, salt,
216
+ );
217
+ } else if (provider.authMode === "forward") {
218
+ const context = args.codexAuthContext;
219
+ if (context && (context.kind === "pool" || context.kind === "main-pool")
220
+ && context.accountId && context.writerGeneration !== undefined
221
+ && (context.kind !== "pool" || context.generation !== undefined)) {
222
+ credential = durableReplayCredentialIdentity("codex", JSON.stringify([
223
+ context.accountId, context.kind === "pool" ? context.generation : null, context.writerGeneration,
224
+ ]), provider.headers, salt);
225
+ }
226
+ } else if (provider.authMode !== "local") {
227
+ credential = durableReplayCredentialIdentity("key", nonEmptyProviderApiKey(provider), provider.headers, salt);
228
+ }
229
+ return destination && credential ? { destination, credential } : undefined;
230
+ }
231
+
159
232
 
160
233
  function routeReasoningReplayIdentity(args: {
161
234
  providerName: string;
@@ -28,9 +28,6 @@ import { deliverAdapterResponse } from "./adapter-delivery";
28
28
  import { releaseUpstreamHostAdmission } from "../../codex/upstream-host-health";
29
29
  import { releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
30
30
  import { runWithCompactionRecovery } from "./compaction-recovery";
31
-
32
- /** Public Responses entry and compatibility exports. Implementations live with their owners. */
33
-
34
31
  /**
35
32
  * Route one `/v1/responses` request through the adapter pipeline: recovery loop, passthrough
36
33
  * wire, image/web-search bridges, and the terminal-guard continuation.
@@ -66,16 +63,17 @@ export async function handleResponses(
66
63
  // Once at ingress, spend observer included: a combo child inherits the parent's holder.
67
64
  sendBudget: options.sendBudget ?? createInferenceSendBudget(req, logCtx),
68
65
  }, handleResponsesInner);
69
- const finalResponse = ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget) : response;
66
+ const finalResponse = ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget, abortSignal) : response;
70
67
  if (!accountLoad.lease) { release(); return finalResponse; }
71
68
  return finalizeAccountLease(finalResponse, release);
72
69
  } catch (error) {
73
70
  release();
74
71
  if (ownsBudget) translatorBudget.dispose();
75
72
  throw error;
73
+ } finally {
74
+ if (!options.comboInitialSend?.producerOwned) options.comboInitialSend?.permit.release();
76
75
  }
77
76
  }
78
-
79
77
  export async function handleComboResponses(
80
78
  req: Request,
81
79
  rawBody: unknown,
@@ -94,7 +92,6 @@ export async function handleComboResponses(
94
92
  requestDispatchers,
95
93
  );
96
94
  }
97
-
98
95
  /** Compose request phases while retaining the original admission-finally ownership. */
99
96
  async function handleResponsesInner(
100
97
  req: Request,
@@ -107,6 +104,7 @@ async function handleResponsesInner(
107
104
  pendingHostAdmissionLease: null,
108
105
  authCtx: { kind: "main", accountId: null },
109
106
  };
107
+ let releasePendingSend = () => {};
110
108
  try {
111
109
  const requestState = await prepareResponsesRequest(requestContext, admissionState, requestDispatchers);
112
110
  if (requestState instanceof Response) return requestState;
@@ -124,7 +122,7 @@ async function handleResponsesInner(
124
122
  const sendBudgetState = createResponsesSendBudget(requestContext);
125
123
  if (sendBudgetState instanceof Response) return sendBudgetState;
126
124
  if ("passthrough" in transportState.adapter && transportState.adapter.passthrough && !sidecarState.routedCompaction) {
127
- return await executePassthroughResponse(
125
+ const passthroughResult = await executePassthroughResponse(
128
126
  requestContext,
129
127
  admissionState,
130
128
  requestState,
@@ -133,6 +131,9 @@ async function handleResponsesInner(
133
131
  responseEffects,
134
132
  sendBudgetState,
135
133
  );
134
+ if (passthroughResult instanceof Response) return passthroughResult;
135
+ const unclaimedHop = sendBudgetState.pendingHopPermit;
136
+ releasePendingSend = () => { if (sendBudgetState.pendingHopPermit === unclaimedHop) { unclaimedHop?.release(); sendBudgetState.pendingHopPermit = undefined; } };
136
137
  }
137
138
  const sidecarPlans = await executeResponsesSidecars(
138
139
  requestContext,
@@ -182,15 +183,14 @@ async function handleResponsesInner(
182
183
  continuationState,
183
184
  );
184
185
  } finally {
186
+ releasePendingSend();
185
187
  if (admissionState.pendingHostAdmissionLease) {
186
188
  releaseUpstreamHostAdmission(admissionState.pendingHostAdmissionLease);
187
189
  releaseCodexAuthContextProbeLease(admissionState.authCtx);
188
190
  }
189
191
  }
190
192
  }
191
-
192
193
  const requestDispatchers: ResponsesDispatchers = { handleResponses, handleComboResponses };
193
-
194
194
  export { adapterNeedsForcedContinuation } from "./core-replay";
195
195
  export {
196
196
  sidecarOutcomeRecorder, codexLogAccountId, usesCodexForwardPoolAuth, preAuthUpstreamHostCircuitKey,
@@ -293,11 +293,27 @@ export async function* guardEmptyCompletionEventStream(
293
293
  return;
294
294
  }
295
295
  if (event.type === "error") {
296
- if (retries > 0 && event.status !== 499) {
296
+ if (event.replaySafeBeforeOutput === true && retries < maxRetries) {
297
+ // The upstream failed the turn before any text or tool call (#6876: Vertex dropped a
298
+ // malformed function call). Nothing actionable reached the client, so this is the same
299
+ // replay-safe shape as an empty completion: retry the identical turn once.
300
+ usage = mergeUsage(usage, event.usage);
301
+ retries += 1;
302
+ try {
303
+ source = await options.continuation();
304
+ } catch {
305
+ yield emptyCompletionRetryFailedEvent(usage, true);
306
+ return;
307
+ }
308
+ terminalSeen = true;
309
+ break;
310
+ }
311
+ if (retries > 0 && event.status !== 499 && event.replaySafeBeforeOutput !== true) {
297
312
  // The retry failed upstream. Its body cannot reach the client (the
298
313
  // 200 head went out with the first attempt), so state the failure in
299
314
  // the stream's own error framing — same move as the router's
300
- // empty_completion_retry_failed. Client cancels (499) pass through.
315
+ // empty_completion_retry_failed. Client cancels (499) pass through, and a
316
+ // replay-safe failure that repeats keeps its own, more specific message.
301
317
  yield emptyCompletionRetryFailedEvent(mergeUsage(usage, event.usage), true);
302
318
  return;
303
319
  }
@@ -43,6 +43,10 @@ const pacingWebsocketDowngradeWarned = new Set<string>();
43
43
  */
44
44
  const EGRESS_DECIDED = Symbol.for("opencodex.provider-egress.decided");
45
45
  const UPSTREAM_REWRITTEN = Symbol.for("opencodex.plugins.upstream-rewritten");
46
+ // Survives queued credential rebuilds and executor replacement without importing budget owners.
47
+ const PHYSICAL_DISPATCH = Symbol.for("opencodex.physical-dispatch");
48
+ // One WS attempt and its HTTP fallback are one logical physical-send receipt.
49
+ const WS_FALLBACK_RECEIPT = Symbol.for("opencodex.ws-fallback-receipt");
46
50
 
47
51
  /**
48
52
  * Announce once, per provider, that an explicit egress route moved this provider off the
@@ -200,6 +204,7 @@ export function sendWithConnectionPolicy(
200
204
  const largeCodexBody = typeof body === "string"
201
205
  && /^https:\/\/chatgpt\.com\/backend-api\/codex\/responses(?:\/compact)?$/.test(target)
202
206
  && Buffer.byteLength(body, "utf8") >= 1024 * 1024;
207
+ (init as Record<symbol, (() => void) | undefined> | undefined)?.[PHYSICAL_DISPATCH]?.();
203
208
  return physicalFetch(input, {
204
209
  ...init,
205
210
  ...(largeCodexBody ? { body: Buffer.from(body, "utf8") } : {}),
@@ -233,10 +238,13 @@ export interface ProviderFetchOptions {
233
238
  onCodexWsQuota?: CodexWsQuotaObserver;
234
239
  /** Synchronous admission at actual credential dispatch, after pacing/backoff. */
235
240
  beforeDispatch?: (headers: Headers) => void;
241
+ /** One-shot receipt after final local HTTP admission, immediately before executor entry. */
242
+ onPhysicalDispatch?: () => void;
236
243
  /** Revalidate/rebuild a queued request at its physical send boundary, after pacing. */
237
244
  dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch) => Promise<Response>;
238
245
  }
239
246
 
247
+ /** Compose provider pacing and transport admission; receipts run only at executor entry. */
240
248
  export function providerFetch(
241
249
  provider: OcxProviderConfig,
242
250
  runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
@@ -301,7 +309,14 @@ export function providerFetch(
301
309
  options.beforeDispatch?.(new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined)));
302
310
  // No proxy option is attached here: a `dispatchOverride` may rebuild this request against
303
311
  // a different destination, so the route is decided at the physical send instead.
304
- const dispatchInit = { ...withUpstreamHttpVersion(input, init, provider), timeout: 0 };
312
+ const sharedReceipt = (init as Record<symbol, (() => void) | undefined> | undefined)?.[WS_FALLBACK_RECEIPT];
313
+ let notified = false;
314
+ const dispatchInit = { ...withUpstreamHttpVersion(input, init, provider), timeout: 0,
315
+ ...(options.onPhysicalDispatch ? { [PHYSICAL_DISPATCH]: () => {
316
+ if (sharedReceipt) sharedReceipt();
317
+ else if (!notified) { options.onPhysicalDispatch!(); notified = true; }
318
+ } } : {}),
319
+ };
305
320
  return options.dispatchOverride
306
321
  ? options.dispatchOverride(input, dispatchInit, dispatch)
307
322
  : dispatch(input, dispatchInit);
@@ -332,8 +347,16 @@ export function providerFetch(
332
347
  // used, protocol pin included: a WS turn that falls back is serving the
333
348
  // request over HTTP, and dropping the provider's `upstreamHttpVersion`
334
349
  // there would silently negotiate a transport the operator ruled out.
335
- return codexWsUpstreamFetch(input, init, httpFetch, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
336
- async () => { (await waitForPacing(init.signal ?? undefined))?.release(); });
350
+ let notified = false;
351
+ const notifyOnce = (): void => {
352
+ if (notified) return;
353
+ notified = true;
354
+ options.onPhysicalDispatch?.();
355
+ };
356
+ const fallback = ((url: Parameters<typeof globalThis.fetch>[0], fallbackInit?: RequestInit) =>
357
+ httpFetch(url, { ...fallbackInit, [WS_FALLBACK_RECEIPT]: notifyOnce } as RequestInit)) as typeof globalThis.fetch;
358
+ return codexWsUpstreamFetch(input, init, fallback, runtime, options.onCodexWsQuota, options.beforeDispatch, options.nativeControl,
359
+ async () => { (await waitForPacing(init.signal ?? undefined))?.release(); }, notifyOnce);
337
360
  }
338
361
  return httpFetch(input, init);
339
362
  };
@@ -136,7 +136,7 @@ export function createJevModelInvoker(context: JevModelInvokerContext): JevModel
136
136
  if (new TextEncoder().encode(body).byteLength > JEV_MAX_REQUEST_BYTES) {
137
137
  throw new JevModelInvokeError("malformed", "decision request too large");
138
138
  }
139
- const lease = tryAdmitTurn();
139
+ const lease = tryAdmitTurn(undefined, signal);
140
140
  if (!lease) throw new JevModelInvokeError("network", "decision turn not admitted");
141
141
  const childLog: RequestLogContext = { model, provider: "unknown", inboundProtocol: "responses" };
142
142
  let usage: Record<string, number> | undefined;
@@ -24,13 +24,16 @@ import { teeWithBoundedInspection } from "../inspection-tee";
24
24
  import {
25
25
  codexForwardTerminalOutcomeRecorder,
26
26
  usesCodexForwardPoolAuth,
27
+ liveMainQuotaDispatch,
27
28
  codexQuotaOutcomeMeta,
28
29
  codexDenialOutcomeMeta,
29
30
  isFixedCodexAccount,
30
31
  shouldDeferCodexResetDerivedCooldown,
31
32
  } from "./core-codex-account";
33
+ import { isMainQuotaDispatchLive, isMainQuotaDispatchWsClaimed } from "../../codex/main-account-cache";
34
+ import { MAIN_CODEX_ACCOUNT_ID } from "../../codex/account-id";
32
35
  import type { ResponsesTerminalStatus } from "../../bridge";
33
- import { isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse } from "./ws-upstream";
36
+ import { isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse, isCodexWsPreludeProjection } from "./ws-upstream";
34
37
  import { recordSubagentQuotaFailureForThreadSpawn } from "../../codex/subagent-model-fallback";
35
38
  import { recordCodexUpstreamOutcome } from "../../codex/routing";
36
39
  import { codexProbeLeaseId, codexProbeQuotaScope, codexTransientProbeGrant, releaseCodexAuthContextProbeLease } from "../../codex/auth-context";
@@ -139,6 +142,7 @@ import {
139
142
  httpStatusFromTerminalError,
140
143
  inspectResponseLogJson,
141
144
  inspectResponseLogSsePayloadParsed,
145
+ noteUpstreamRequestId,
142
146
  } from "../request-log";
143
147
  import { restoreRoutedCustomCallsInJson } from "../../responses/custom-tool-compat";
144
148
  import { restoreRoutedToolSearchCallsInJson } from "../../responses/tool-search-compat";
@@ -406,6 +410,9 @@ export async function deliverPassthroughResponse(
406
410
  | "localUpstream"
407
411
  >,
408
412
  ): Promise<Response> {
413
+ const { route } = requestState;
414
+ // The proof must belong to the response whose headers are published.
415
+ const arrivalMainDispatch = liveMainQuotaDispatch(admissionState.authCtx, route.provider);
409
416
  const { logCtx, config, options, req } = requestContext;
410
417
  const {
411
418
  codexSafetyBufferingOptions,
@@ -429,7 +436,7 @@ export async function deliverPassthroughResponse(
429
436
  normalizeFunctionCompletionJson,
430
437
  } = nativeExchange;
431
438
  const { commitReasoningReplayServingRoute, recordTerminalOutcomes } = responseEffects;
432
- const { parsed, route, subagentQuotaFailureModel, clientRequestedStream, translatorBudget, inboundWire } = requestState;
439
+ const { parsed, subagentQuotaFailureModel, clientRequestedStream, translatorBudget, inboundWire } = requestState;
433
440
  const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";
434
441
  const { openAiSidecar } = sidecarState;
435
442
  const { requestBindings } = transportState;
@@ -452,6 +459,7 @@ export async function deliverPassthroughResponse(
452
459
  }
453
460
 
454
461
  const headers = sanitizePassthroughHeaders(upstreamResponse.headers, codexSafetyBufferingOptions);
462
+ if (!upstreamResponse.ok) noteUpstreamRequestId(logCtx, headers);
455
463
  const resolvedModel = headers.get("openai-model")?.trim();
456
464
  if (resolvedModel) {
457
465
  logCtx.servedModel = resolvedModel;
@@ -535,6 +543,20 @@ export async function deliverPassthroughResponse(
535
543
  ...(admissionState.authCtx.kind === "pool" ? { credentialGeneration: admissionState.authCtx.generation } : {}),
536
544
  });
537
545
  }
546
+ } else {
547
+ // The WS observer is the only plain-main publisher for a WebSocket exchange;
548
+ // a prelude projection carries the prelude snapshot, not fresh evidence.
549
+ // The dispatch claim is authoritative because downstream wrappers can replace the Response.
550
+ if (arrivalMainDispatch && !isMainQuotaDispatchWsClaimed(arrivalMainDispatch)
551
+ && !(isCodexWsUpstreamResponse(upstreamResponse) || isCodexWsPreludeProjection(upstreamResponse))) {
552
+ const { applyAccountQuotaFromUpstreamHeaders } = await import("../../codex/auth-api");
553
+ // Import yields; same-account token replacement leaves the identity writer live.
554
+ // Re-check the credential fence with no await before publication.
555
+ if (isMainQuotaDispatchLive(arrivalMainDispatch)) {
556
+ applyAccountQuotaFromUpstreamHeaders(MAIN_CODEX_ACCOUNT_ID, upstreamResponse.headers,
557
+ arrivalMainDispatch.configGeneration, arrivalMainDispatch.writer, { modelId: route.modelId });
558
+ }
559
+ }
538
560
  }
539
561
 
540
562
  // Non-2xx passthrough failures must never reach Codex as an empty body —