@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
@@ -1,3 +1,4 @@
1
+ import { spendLedgerStorageErrorResponse } from "./spend-storage-error";
1
2
  import { createSteeringSettingsNormalizer } from "./native-steering-policy";
2
3
  import { nativeResponseControlEligible } from "./native-response-control";
3
4
  import { NativeInjectionReplay } from "./native-injection-replay";
@@ -8,10 +9,11 @@ import type {
8
9
  PassthroughAdmissionState,
9
10
  } from "./core-options";
10
11
  import type { PreparedResponsesRequest } from "./request-prepare";
11
- import type { ResponsesTransport } from "./request-transport";
12
+ import { CopilotAutoAdapterHandoff, type ResponsesTransport } from "./request-transport";
12
13
  import type { ResponsesEffects } from "./response-effects";
13
14
  import type { ResponsesSendBudget } from "./request-send-budget";
14
15
  import { transientSendCapFor } from "./request-send-budget";
16
+ import { renewMainQuotaDispatchForAttempt } from "../../codex/main-account-cache";
15
17
  import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
16
18
  import { isLocalUpstream } from "../../lib/local-upstream";
17
19
  import { codexSafetyBufferingFilterOptions, terminalStatusFromParsed } from "../relay";
@@ -165,6 +167,9 @@ import { isTransientConsoleGoUploadRejection } from "../../providers/opencode-ze
165
167
  import { planReasoningEffortDowngrade } from "../../providers/reasoning-metadata";
166
168
  import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
167
169
 
170
+ /** Transfer an uncommitted Copilot exchange to the existing translated pipeline without new admission. */
171
+ export interface PassthroughAdapterHandoff { kind: "adapter-handoff" }
172
+
168
173
  /** Prepares and recovers one native Responses exchange before client commitment. */
169
174
  export async function preparePassthroughExchange(
170
175
  requestContext: Pick<ResponsesRequestContext, "config" | "logCtx" | "options" | "req">,
@@ -191,6 +196,12 @@ export async function preparePassthroughExchange(
191
196
  | "passiveQuotaWriterGeneration"
192
197
  | "oauthDispatch"
193
198
  | "resolveSelectionAdapter"
199
+ | "resolveCopilotSelection"
200
+ | "copilotRefusalResponse"
201
+ | "oauth401ReplayAttempted"
202
+ | "activeAdapter"
203
+ | "runTurnAdapter"
204
+ | "invalidateSameTargetRequest"
194
205
  | "isOAuth401ReplayProvider"
195
206
  | "sentOAuthSnapshot"
196
207
  | "refreshResolvedOAuthSelection"
@@ -225,6 +236,10 @@ export async function preparePassthroughExchange(
225
236
  | "pendingHopPermit"
226
237
  | "workflowRootId"
227
238
  | "sendsUsed"
239
+ | "targetSendsUsed"
240
+ | "initialSendAllowance"
241
+ | "noteInitialDispatch"
242
+ | "adapterSendBudget"
228
243
  >,
229
244
  ) {
230
245
  const { config, logCtx, options, req } = requestContext;
@@ -243,6 +258,8 @@ export async function preparePassthroughExchange(
243
258
  passiveQuotaWriterGeneration,
244
259
  oauthDispatch,
245
260
  resolveSelectionAdapter,
261
+ resolveCopilotSelection,
262
+ copilotRefusalResponse,
246
263
  isOAuth401ReplayProvider,
247
264
  refreshResolvedOAuthSelection,
248
265
  applyFailoverSnapshot,
@@ -727,7 +744,7 @@ export async function preparePassthroughExchange(
727
744
  // consumer's cancel to a signalled fetch, so we pass the signal and relay through relayWithAbort,
728
745
  // whose cancel() aborts the upstream — preventing leaked connections (RC2, passthrough path).
729
746
  const upstream = new AbortController();
730
- linkAbortSignal(upstream, options.abortSignal);
747
+ const cleanupUpstreamAbort = linkAbortSignal(upstream, options.abortSignal ?? req.signal);
731
748
  const connectMs = config.connectTimeoutMs ?? 200_000;
732
749
  let upstreamResponse: Response;
733
750
  /**
@@ -753,7 +770,7 @@ export async function preparePassthroughExchange(
753
770
  const transientSendPolicy = () => transientRetryPolicyFor(route.provider);
754
771
  const transientSendAttempts = (): number => transientSendCapFor(
755
772
  transientSendPolicy()?.attempts,
756
- sendBudgetState.sendsUsed,
773
+ sendBudgetState.targetSendsUsed,
757
774
  );
758
775
  const configuredTransientSendBudgetExhausted = (): boolean =>
759
776
  transientSendPolicy() !== null && transientSendAttempts() === 0;
@@ -798,6 +815,9 @@ export async function preparePassthroughExchange(
798
815
  const sendAmbiguousReplacement = (
799
816
  signal: AbortSignal = upstream.signal,
800
817
  ): Promise<Response> => {
818
+ if (admissionState.authCtx.kind === "main" && admissionState.authCtx.mainQuotaDispatch) {
819
+ admissionState.authCtx.mainQuotaDispatch = renewMainQuotaDispatchForAttempt(admissionState.authCtx.mainQuotaDispatch);
820
+ }
801
821
  const report = transientSendReporter();
802
822
  let started = false;
803
823
  const run = () => fetchWithHeaderTimeout(
@@ -814,7 +834,7 @@ export async function preparePassthroughExchange(
814
834
  httpOnly: true,
815
835
  providerName: route.providerName,
816
836
  modelId: route.modelId,
817
- dispatchOverride: oauthDispatch(request),
837
+ dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
818
838
  beforeDispatch: headers => {
819
839
  if (signal.aborted) throw signal.reason;
820
840
  if (!transportState.selectionIsCurrent(transportState.requestBindings.get(request))) {
@@ -888,9 +908,10 @@ export async function preparePassthroughExchange(
888
908
  describeOutboundBodyRefusal(result),
889
909
  );
890
910
  };
891
- const transportFailureResponse = (err: unknown): Response => {
911
+ const transportFailureResponse = (err: unknown): Response | PassthroughAdapterHandoff => {
912
+ cleanupUpstreamAbort();
892
913
  upstream.abort();
893
- if (options.abortSignal?.aborted) {
914
+ if (options.abortSignal?.aborted || req.signal.aborted) {
894
915
  releaseUpstreamHostAdmission(nativeHostState.lease);
895
916
  nativeHostState.lease = null;
896
917
  releaseCodexAuthContextProbeLease(admissionState.authCtx);
@@ -907,6 +928,17 @@ export async function preparePassthroughExchange(
907
928
  ?? formatErrorResponse(429, "request_send_budget_exhausted", err.message);
908
929
  }
909
930
  const refusal = unwrapUpstreamRetryEvidenceError(err);
931
+ if (refusal instanceof CopilotAutoAdapterHandoff) {
932
+ bindRefreshedAdapter(refusal.adapter);
933
+ return handoffToChat(refusal.adapter);
934
+ }
935
+ const copilotRefusal = copilotRefusalResponse(refusal);
936
+ if (copilotRefusal) {
937
+ releaseUpstreamHostAdmission(nativeHostState.lease);
938
+ nativeHostState.lease = null;
939
+ releaseCodexAuthContextProbeLease(admissionState.authCtx);
940
+ return copilotRefusal;
941
+ }
910
942
  // Pacing may outlive the selected account's admission. No fetch occurred, so do
911
943
  // not turn an operator pause into a 502 or charge it to host/account health.
912
944
  if (refusal instanceof OAuthAccountPausedError || refusal instanceof OAuthLoginRequiredError || refusal instanceof AnthropicAccountCooldownError) {
@@ -922,7 +954,7 @@ export async function preparePassthroughExchange(
922
954
  }
923
955
  return formatErrorResponse(403, "permission_error", publicOAuthAuthenticationErrorMessage(refusal));
924
956
  }
925
- const localRefusal = mapCodexAuthContextErrorToResponse(refusal, {
957
+ const localRefusal = spendLedgerStorageErrorResponse(err, logCtx) ?? mapCodexAuthContextErrorToResponse(refusal, {
926
958
  now: Date.now(), accountSelector: route.codexAccountNamespace,
927
959
  });
928
960
  if (localRefusal) {
@@ -968,6 +1000,11 @@ export async function preparePassthroughExchange(
968
1000
  };
969
1001
  const initialBodyRefusal = refuseOversizedOutboundBody(request);
970
1002
  if (initialBodyRefusal) return initialBodyRefusal;
1003
+ // A combo-owned first send is settled by the physical-dispatch receipt unless spend
1004
+ // enforcement is on, where the shared reporter already owns the permit lifecycle.
1005
+ // Stable per request: the Combo booking (`reserveDispatch`) already started and froze the spend
1006
+ // policy, so `spendEnforced` cannot change before dispatch even though no reporter starts it here.
1007
+ const receiptMode = !sendBudgetState.adapterSendBudget?.spendEnforced && Boolean(options.comboInitialSend);
971
1008
  try {
972
1009
  // Transient-5xx pre-stream retry (devlog/_plan/260716_claudecode_hardening/010):
973
1010
  // the ChatGPT backend emits transient 502/520s that an immediate retry absorbs.
@@ -991,9 +1028,10 @@ export async function preparePassthroughExchange(
991
1028
  nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
992
1029
  && responseEffects.plaintextV2AgentMessageToolNames.size === 0
993
1030
  ? options.nativeControl : undefined,
994
- dispatchOverride: oauthDispatch(request),
1031
+ dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
995
1032
  providerName: route.providerName,
996
1033
  modelId: route.modelId,
1034
+ onPhysicalDispatch: receiptMode ? sendBudgetState.noteInitialDispatch : undefined,
997
1035
  onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
998
1036
  beforeDispatch: isCanonicalOpenAiForwardProvider(route.provider)
999
1037
  ? createCodexAuthDispatchGuard(admissionState.authCtx, options.codexAuthPolicy ?? config, route.modelId, options.admission, options.visionDescribeTerminal === true) : undefined,
@@ -1004,7 +1042,10 @@ export async function preparePassthroughExchange(
1004
1042
  .then(adoptObservedResponse);
1005
1043
  },
1006
1044
  { abortSignal: upstream.signal, label: safeHostLabel(request.url),
1007
- attempts: remainingTransientSendBudget(transientSendAttempts()), onSendsConsumed: transientSendReporter(),
1045
+ attempts: sendBudgetState.initialSendAllowance(transientSendAttempts()),
1046
+ // Reporter path: direct requests and enforced spend. Receipt-mode sends are settled
1047
+ // by `onPhysicalDispatch`; a reporter as well would count them twice.
1048
+ ...(receiptMode ? {} : { onSendsConsumed: transientSendReporter() }),
1008
1049
  claimAmbiguousResend: claimPreHeaderResend,
1009
1050
  },
1010
1051
  );
@@ -1017,7 +1058,33 @@ export async function preparePassthroughExchange(
1017
1058
  const opaqueBlobRecoveryGuard: OpaqueBlobRecoveryGuard = { attempted: false };
1018
1059
  // At most one reasoning-effort downgrade per request.
1019
1060
  const reasoningEffortDowngradeGuard: { attempted: boolean } = { attempted: false };
1020
- let oauth401ReplayAttempted = false;
1061
+ /** Rebind the refreshed wire before transferring this request to the Chat pipeline. */
1062
+ function bindRefreshedAdapter(refreshedAdapter: typeof transportState.adapter) {
1063
+ bindRouteReasoningReplayScope({
1064
+ parsed,
1065
+ providerName: route.providerName,
1066
+ provider: route.provider,
1067
+ adapterName: refreshedAdapter.name,
1068
+ oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot,
1069
+ });
1070
+ logCtx.providerAdapter = refreshedAdapter.name;
1071
+ sealRequestAttemptIdentity(
1072
+ logCtx.activeAttempt,
1073
+ logCtx.provider,
1074
+ refreshedAdapter.name,
1075
+ logCtx.accountLogLabel,
1076
+ );
1077
+ recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, refreshedAdapter.name);
1078
+ }
1079
+ /** Transfer adapter ownership without retaining the native abort link or request cache. */
1080
+ function handoffToChat(refreshedAdapter: typeof transportState.adapter) {
1081
+ cleanupUpstreamAbort();
1082
+ upstream.abort();
1083
+ transportState.adapter = transportState.activeAdapter = transportState.runTurnAdapter = refreshedAdapter;
1084
+ transportState.invalidateSameTargetRequest();
1085
+ return { kind: "adapter-handoff" as const };
1086
+ }
1087
+ let oauth401ReplayAttempted = transportState.oauth401ReplayAttempted;
1021
1088
  let codex401ReplayKind: "main" | "stored" | null = null;
1022
1089
  // Console Go answers a transient 400 "Invalid upload request." for bodies it accepts
1023
1090
  // moments later; at most one byte-identical replay is allowed per request.
@@ -1026,7 +1093,7 @@ export async function preparePassthroughExchange(
1026
1093
  let rateLimitRetries = 0;
1027
1094
  const rebuildAndRefetch = async (
1028
1095
  recovery: AttemptRecoveryKind,
1029
- ): Promise<Response | { failed: Response }> => {
1096
+ ): Promise<Response | { failed: Response | PassthroughAdapterHandoff }> => {
1030
1097
  const retryAdapter = resolveSelectionAdapter(
1031
1098
  resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
1032
1099
  config.cacheRetention,
@@ -1096,7 +1163,7 @@ export async function preparePassthroughExchange(
1096
1163
  nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
1097
1164
  && responseEffects.plaintextV2AgentMessageToolNames.size === 0
1098
1165
  ? options.nativeControl : undefined,
1099
- dispatchOverride: oauthDispatch(request),
1166
+ dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
1100
1167
  providerName: route.providerName,
1101
1168
  modelId: route.modelId,
1102
1169
  onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
@@ -1203,7 +1270,7 @@ export async function preparePassthroughExchange(
1203
1270
  nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
1204
1271
  && responseEffects.plaintextV2AgentMessageToolNames.size === 0
1205
1272
  ? options.nativeControl : undefined,
1206
- dispatchOverride: oauthDispatch(request),
1273
+ dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
1207
1274
  providerName: route.providerName,
1208
1275
  modelId: route.modelId,
1209
1276
  onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
@@ -1265,7 +1332,7 @@ export async function preparePassthroughExchange(
1265
1332
  // fault and throw away the credential evidence the client needs.
1266
1333
  && !sendBudgetExhausted(transientSendAttempts())
1267
1334
  ) {
1268
- oauth401ReplayAttempted = true;
1335
+ oauth401ReplayAttempted = transportState.oauth401ReplayAttempted = true;
1269
1336
  try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
1270
1337
  let refreshed: OAuthAccessSnapshot;
1271
1338
  try {
@@ -1288,7 +1355,7 @@ export async function preparePassthroughExchange(
1288
1355
  if (route.providerName === "kiro") {
1289
1356
  parsed._kiroAuthContext = { ...(refreshed.kiro ?? {}) };
1290
1357
  }
1291
- const refreshedProvider = resolveProviderTransport(
1358
+ let refreshedProvider = resolveProviderTransport(
1292
1359
  route.providerName,
1293
1360
  {
1294
1361
  ...route.provider,
@@ -1301,29 +1368,21 @@ export async function preparePassthroughExchange(
1301
1368
  : undefined,
1302
1369
  );
1303
1370
  route.provider = refreshedProvider;
1371
+ try { await resolveCopilotSelection(parsed); }
1372
+ catch (err) { return transportFailureResponse(err); }
1373
+ refreshedProvider = route.provider;
1304
1374
  const refreshedAdapter = resolveSelectionAdapter(
1305
1375
  resolveWireProtocolOverride(route.providerName, route.modelId, refreshedProvider, inboundWire, route.staticPolicy),
1306
1376
  config.cacheRetention,
1307
1377
  );
1378
+ bindRefreshedAdapter(refreshedAdapter);
1308
1379
  if (!("passthrough" in refreshedAdapter) || !refreshedAdapter.passthrough) {
1380
+ if (route.providerName === "github-copilot" && refreshedAdapter.name === "openai-chat") {
1381
+ return handoffToChat(refreshedAdapter);
1382
+ }
1309
1383
  upstream.abort();
1310
1384
  return formatErrorResponse(502, "upstream_error", "OAuth refresh changed the provider wire unexpectedly");
1311
1385
  }
1312
- bindRouteReasoningReplayScope({
1313
- parsed,
1314
- providerName: route.providerName,
1315
- provider: refreshedProvider,
1316
- adapterName: refreshedAdapter.name,
1317
- oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot,
1318
- });
1319
- logCtx.providerAdapter = refreshedAdapter.name;
1320
- sealRequestAttemptIdentity(
1321
- logCtx.activeAttempt,
1322
- logCtx.provider,
1323
- refreshedAdapter.name,
1324
- logCtx.accountLogLabel,
1325
- );
1326
- recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, refreshedAdapter.name);
1327
1386
  try {
1328
1387
  request = await refreshedAdapter.buildRequest(parsed, {
1329
1388
  headers: requestState.selectedForwardHeaders,
@@ -1356,7 +1415,7 @@ export async function preparePassthroughExchange(
1356
1415
  nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
1357
1416
  && responseEffects.plaintextV2AgentMessageToolNames.size === 0
1358
1417
  ? options.nativeControl : undefined,
1359
- dispatchOverride: oauthDispatch(request),
1418
+ dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
1360
1419
  providerName: route.providerName,
1361
1420
  modelId: route.modelId,
1362
1421
  onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
@@ -1397,38 +1456,50 @@ export async function preparePassthroughExchange(
1397
1456
  true,
1398
1457
  );
1399
1458
  if (hop.allowed) {
1400
- const nextAccountId = rotateGenericOAuthAccountOn429(
1401
- config, route.providerName, transportState.genericFailoverAccountId,
1402
- upstreamResponse.headers.get("retry-after"),
1403
- Date.now(),
1404
- route.modelId,
1405
- );
1406
- let snapshot: OAuthAccessSnapshot | undefined;
1407
- if (nextAccountId) {
1408
- try { snapshot = await failoverAccountSnapshot(route.providerName, nextAccountId); }
1409
- catch { /* Keep the original 429 body readable when the next credential is unavailable. */ }
1410
- }
1411
- if (snapshot && await applyFailoverSnapshot(snapshot)) {
1412
- transportState.genericFailovers += 1;
1413
- route.provider = resolveProviderTransport(
1414
- route.providerName, route.provider, parsed.options.promptCacheKey, transportState.sentOAuthSnapshot?.apiBaseUrl,
1459
+ let transferred = false;
1460
+ try {
1461
+ const nextAccountId = rotateGenericOAuthAccountOn429(
1462
+ config, route.providerName, transportState.genericFailoverAccountId,
1463
+ upstreamResponse.headers.get("retry-after"),
1464
+ Date.now(),
1465
+ route.modelId,
1415
1466
  );
1416
- bindRouteReasoningReplayScope({
1417
- parsed, providerName: route.providerName, provider: route.provider,
1418
- adapterName: "openai-responses", oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot,
1419
- });
1467
+ let snapshot: OAuthAccessSnapshot | undefined;
1468
+ if (nextAccountId) {
1469
+ try { snapshot = await failoverAccountSnapshot(route.providerName, nextAccountId); }
1470
+ catch { /* Keep the original 429 body readable when the next credential is unavailable. */ }
1471
+ }
1472
+ if (snapshot && await applyFailoverSnapshot(snapshot)) {
1473
+ transportState.genericFailovers += 1;
1474
+ route.provider = resolveProviderTransport(
1475
+ route.providerName, route.provider, parsed.options.promptCacheKey, transportState.sentOAuthSnapshot?.apiBaseUrl,
1476
+ );
1477
+ const rotatedAdapter = resolveSelectionAdapter(
1478
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
1479
+ config.cacheRetention,
1480
+ );
1481
+ bindRefreshedAdapter(rotatedAdapter);
1482
+ try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already closed */ }
1483
+ sendBudgetState.pendingHopPermit = hop.permit;
1484
+ if (route.providerName === "github-copilot" && rotatedAdapter.name === "openai-chat") {
1485
+ transferred = true;
1486
+ return handoffToChat(rotatedAdapter);
1487
+ }
1488
+ const result = await rebuildAndRefetch("oauth-account-429");
1489
+ sendBudgetState.pendingHopPermit = undefined;
1490
+ if ("failed" in result) return result.failed;
1491
+ upstreamResponse = result;
1492
+ continue passthroughRecovery;
1493
+ }
1494
+ } catch (err) {
1420
1495
  try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already closed */ }
1421
- // The replay IS this hop's send, so the rebuild spends the reservation instead of
1422
- // asking for one of its own.
1423
- sendBudgetState.pendingHopPermit = hop.permit;
1424
- const result = await rebuildAndRefetch("oauth-account-429");
1425
- sendBudgetState.pendingHopPermit = undefined;
1426
- if ("failed" in result) return result.failed;
1427
- upstreamResponse = result;
1428
- continue passthroughRecovery;
1496
+ return transportFailureResponse(err);
1497
+ } finally {
1498
+ if (!transferred) {
1499
+ if (sendBudgetState.pendingHopPermit === hop.permit) sendBudgetState.pendingHopPermit = undefined;
1500
+ hop.permit?.release();
1501
+ }
1429
1502
  }
1430
- // No credential moved, so the reservation costs nothing.
1431
- hop.permit?.release();
1432
1503
  } else {
1433
1504
  // The activation quorum ignores cooldowns; prove that the selector has a live alternate
1434
1505
  // before describing this as a recovery that only the shared request budget withheld.
@@ -1491,7 +1562,7 @@ export async function preparePassthroughExchange(
1491
1562
  nativeControl: nativeResponseControlEligible(route.provider, options.nativeControl) && options.inboundTransport === "websocket" && !options.comboAttempt
1492
1563
  && responseEffects.plaintextV2AgentMessageToolNames.size === 0
1493
1564
  ? options.nativeControl : undefined,
1494
- dispatchOverride: oauthDispatch(request),
1565
+ dispatchOverride: oauthDispatch(request, parsed, "passthrough"),
1495
1566
  providerName: route.providerName,
1496
1567
  modelId: route.modelId,
1497
1568
  onCodexWsQuota: codexWsQuotaObserver(admissionState.authCtx, route.provider, route.modelId),
@@ -1858,6 +1929,7 @@ export async function preparePassthroughExchange(
1858
1929
  const localUpstream = isLocalUpstream(route.provider.baseUrl);
1859
1930
 
1860
1931
  return {
1932
+ kind: undefined,
1861
1933
  codexSafetyBufferingOptions,
1862
1934
  imageGenCallAliases,
1863
1935
  routedCustomToolNames,
@@ -1900,4 +1972,4 @@ export async function preparePassthroughExchange(
1900
1972
  };
1901
1973
  }
1902
1974
 
1903
- export type PassthroughExchange = Exclude<Awaited<ReturnType<typeof preparePassthroughExchange>>, Response>;
1975
+ export type PassthroughExchange = Exclude<Awaited<ReturnType<typeof preparePassthroughExchange>>, Response | PassthroughAdapterHandoff>;
@@ -8,7 +8,7 @@ import type { ResponsesTransport } from "./request-transport";
8
8
  import type { ResponsesSidecarAuth } from "./request-sidecar-auth";
9
9
  import type { ResponsesEffects } from "./response-effects";
10
10
  import type { ResponsesSendBudget } from "./request-send-budget";
11
- import { preparePassthroughExchange } from "./passthrough-dispatch";
11
+ import { preparePassthroughExchange, type PassthroughAdapterHandoff } from "./passthrough-dispatch";
12
12
  import { deliverPassthroughResponse } from "./passthrough-delivery";
13
13
  import { guardDirectPassthroughBodyInactivity } from "../../lib/response-body-inactivity";
14
14
  import { resolveStallTimeoutMs } from "../../stall-timeout";
@@ -24,7 +24,7 @@ export async function executePassthroughResponse(
24
24
  sidecarState: ResponsesSidecarAuth,
25
25
  responseEffects: ResponsesEffects,
26
26
  sendBudgetState: ResponsesSendBudget,
27
- ): Promise<Response> {
27
+ ): Promise<Response | PassthroughAdapterHandoff> {
28
28
  const nativeHostState: PassthroughAdmissionState = { lease: admissionState.pendingHostAdmissionLease };
29
29
  admissionState.pendingHostAdmissionLease = null;
30
30
  try {
@@ -37,7 +37,7 @@ export async function executePassthroughResponse(
37
37
  responseEffects,
38
38
  sendBudgetState,
39
39
  );
40
- if (nativeExchange instanceof Response) return nativeExchange;
40
+ if (nativeExchange instanceof Response || nativeExchange.kind === "adapter-handoff") return nativeExchange;
41
41
  const response = await deliverPassthroughResponse(
42
42
  requestContext,
43
43
  admissionState,
@@ -381,6 +381,10 @@ export async function prepareResponsesRequest(
381
381
  let toolBridgeMaps: ReturnType<typeof buildToolBridgeMaps>;
382
382
  try {
383
383
  parsed = parseRequest(body);
384
+ if (options.inboundWire === "anthropic") {
385
+ parsed._nativeReasoningReplay = options.nativeReasoningReplay;
386
+ parsed._nativeReasoningMint = options.nativeReasoningMint;
387
+ }
384
388
  parsed._promptCacheKeyIsSharedCohort = options.promptCacheKeyIsSharedCohort;
385
389
  // The body may have been rebuilt since the inbound observation (previous-response
386
390
  // expansion); alias the parsed raw body to the same draft so the outbound