@bitkyc08/opencodex 2.52.0-preview.20260911 → 2.52.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 (166) hide show
  1. package/gui/dist/assets/index-CWXut3rG.js +115 -0
  2. package/gui/dist/assets/index-EdoPnm9_.css +1 -0
  3. package/gui/dist/index.html +2 -2
  4. package/gui/dist/provider-icons/devin.svg +49 -0
  5. package/gui/dist/provider-icons/omo.svg +42 -0
  6. package/package.json +3 -1
  7. package/src/AGENTS.md +1 -1
  8. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +0 -1
  9. package/src/adapters/command-code.ts +0 -1
  10. package/src/adapters/cursor/checkpoint-store.ts +37 -0
  11. package/src/adapters/cursor/live-transport.ts +74 -2
  12. package/src/adapters/cursor.ts +6 -0
  13. package/src/adapters/devin/cloud-direct/auth.ts +264 -0
  14. package/src/adapters/devin/cloud-direct/catalog.ts +306 -0
  15. package/src/adapters/devin/cloud-direct/chat.ts +1274 -0
  16. package/src/adapters/devin/cloud-direct/index.ts +65 -0
  17. package/src/adapters/devin/cloud-direct/metadata.ts +134 -0
  18. package/src/adapters/devin/cloud-direct/wire.ts +206 -0
  19. package/src/adapters/devin/live-models.ts +133 -0
  20. package/src/adapters/devin-cli/acp.ts +204 -0
  21. package/src/adapters/devin-cli/adapter.ts +345 -0
  22. package/src/adapters/devin-cli/binary.ts +69 -0
  23. package/src/adapters/devin-cli/models.ts +57 -0
  24. package/src/adapters/devin.ts +326 -0
  25. package/src/adapters/google.ts +1 -1
  26. package/src/adapters/openai-chat.ts +31 -10
  27. package/src/adapters/openai-responses.ts +16 -1
  28. package/src/adapters/registry.ts +25 -1
  29. package/src/bridge.ts +8 -2
  30. package/src/claude/inbound-cache-stabilize.ts +130 -0
  31. package/src/claude/inbound.ts +45 -5
  32. package/src/cli/account-auth.ts +60 -1
  33. package/src/cli/account-extended.ts +23 -30
  34. package/src/cli/account.ts +2 -1
  35. package/src/cli/capabilities.ts +50 -8
  36. package/src/cli/config-command.ts +2 -2
  37. package/src/cli/dispatch.ts +14 -2
  38. package/src/cli/export-command.ts +11 -25
  39. package/src/cli/help.ts +2 -2
  40. package/src/cli/opencode.ts +5 -0
  41. package/src/cli/registry.ts +15 -4
  42. package/src/clients/config-export/cline.ts +71 -0
  43. package/src/clients/config-export/contracts.ts +10 -1
  44. package/src/clients/config-export/model-metadata.ts +33 -0
  45. package/src/clients/config-export/zcode.ts +23 -12
  46. package/src/clients/config-export.ts +156 -4
  47. package/src/codex/account-store.ts +7 -2
  48. package/src/codex/auth-context.ts +1 -1
  49. package/src/codex/catalog/effort.ts +4 -3
  50. package/src/codex/catalog/metadata.ts +8 -4
  51. package/src/codex/catalog/native-models.ts +24 -4
  52. package/src/codex/catalog/parsing.ts +19 -1
  53. package/src/codex/catalog/provider-fetch.ts +57 -0
  54. package/src/codex/catalog/sync.ts +1 -1
  55. package/src/codex/context-compat.ts +97 -0
  56. package/src/codex/context-owner.ts +201 -0
  57. package/src/codex/history-provider.ts +161 -14
  58. package/src/codex/inject-coordination.ts +3 -2
  59. package/src/codex/inject.ts +128 -9
  60. package/src/codex/pool-rotation.ts +8 -292
  61. package/src/codex/quota.ts +41 -12
  62. package/src/codex/retired-model-migration.ts +41 -0
  63. package/src/codex/routing.ts +327 -22
  64. package/src/codex/warmup.ts +2 -2
  65. package/src/combos/failover.ts +5 -0
  66. package/src/combos/resolve.ts +11 -15
  67. package/src/config.ts +46 -14
  68. package/src/generated/compatibility-version.json +293 -113
  69. package/src/grok/grpc-web.ts +120 -0
  70. package/src/grok/reset-coupon-ledger.ts +139 -0
  71. package/src/grok/reset-coupons.ts +278 -0
  72. package/src/integrations/catalog-refresh.ts +1 -1
  73. package/src/integrations/cline-document.ts +73 -0
  74. package/src/integrations/cline-io.ts +149 -0
  75. package/src/integrations/cline-transaction.ts +42 -0
  76. package/src/integrations/config-io.ts +21 -2
  77. package/src/integrations/journal.ts +43 -1
  78. package/src/integrations/omp-yaml-source.ts +123 -4
  79. package/src/integrations/ownership-policy.ts +7 -0
  80. package/src/integrations/ownership.ts +36 -0
  81. package/src/integrations/registry.ts +27 -0
  82. package/src/integrations/state.ts +5 -2
  83. package/src/integrations/store.ts +6 -0
  84. package/src/integrations/writer.ts +53 -9
  85. package/src/lab/subject/behavior-fingerprint.ts +1 -1
  86. package/src/lib/abort.ts +36 -0
  87. package/src/lib/local-destinations.ts +1 -1
  88. package/src/lib/upstream-retry.ts +36 -1
  89. package/src/oauth/account-quota-rank.ts +11 -0
  90. package/src/oauth/callback-server.ts +10 -5
  91. package/src/oauth/devin/api-base.ts +63 -0
  92. package/src/oauth/devin/login.ts +1 -0
  93. package/src/oauth/devin/register-user.ts +186 -0
  94. package/src/oauth/devin/types.ts +71 -0
  95. package/src/oauth/devin-cli.ts +149 -0
  96. package/src/oauth/devin.ts +170 -0
  97. package/src/oauth/generic-account-failover.ts +169 -1
  98. package/src/oauth/index.ts +20 -1
  99. package/src/oauth/login-cli.ts +19 -5
  100. package/src/oauth/pool-kernel.ts +321 -0
  101. package/src/oauth/pool-settings-capability.ts +127 -9
  102. package/src/oauth/store.ts +7 -3
  103. package/src/oauth/token-guardian.ts +1 -1
  104. package/src/providers/codebuddy-models.ts +0 -3
  105. package/src/providers/command-code-efforts.ts +23 -4
  106. package/src/providers/default-aliases.ts +4 -0
  107. package/src/providers/derive.ts +8 -0
  108. package/src/providers/devin-cli-authmode-migration.ts +57 -0
  109. package/src/providers/key-failover.ts +175 -0
  110. package/src/providers/model-rename-startup.ts +21 -1
  111. package/src/providers/qoder-models.ts +0 -1
  112. package/src/providers/quota-key-accounts.ts +50 -0
  113. package/src/providers/quota-routing-cache.ts +65 -6
  114. package/src/providers/quota-types.ts +7 -0
  115. package/src/providers/quota.ts +113 -30
  116. package/src/providers/registry.ts +237 -77
  117. package/src/providers/stale-context-window-migration.ts +92 -0
  118. package/src/providers/zai-responses-migration.ts +45 -0
  119. package/src/quota/reset-observer.ts +2 -1
  120. package/src/quota/reset-seen-store.ts +9 -1
  121. package/src/remote-control/crypto.ts +442 -0
  122. package/src/remote-control/host.ts +175 -0
  123. package/src/remote-control/index.ts +100 -0
  124. package/src/remote-control/protocol.ts +200 -0
  125. package/src/remote-control/relay.ts +162 -0
  126. package/src/remote-control/workspace-agent-protocol.ts +246 -0
  127. package/src/remote-control/workspace-rpc-framing.ts +128 -0
  128. package/src/remote-control/workspace-tools.ts +237 -0
  129. package/src/remote-control/workspace-utf8.ts +24 -0
  130. package/src/responses/custom-tool-compat.ts +23 -0
  131. package/src/router.ts +6 -1
  132. package/src/routing/compatibility/behavior.ts +3 -0
  133. package/src/server/adapter-resolve.ts +6 -2
  134. package/src/server/auth-cors.ts +71 -6
  135. package/src/server/chat-completions.ts +5 -3
  136. package/src/server/chat-native.ts +9 -0
  137. package/src/server/claude-messages.ts +25 -30
  138. package/src/server/context-history.ts +207 -0
  139. package/src/server/images.ts +28 -1
  140. package/src/server/index.ts +42 -22
  141. package/src/server/live.ts +2 -1
  142. package/src/server/management/agent-settings-routes.ts +7 -1
  143. package/src/server/management/config-routes.ts +3 -3
  144. package/src/server/management/grok-coupon-routes.ts +287 -0
  145. package/src/server/management/integration-routes.ts +6 -1
  146. package/src/server/management/oauth-account-routes.ts +124 -7
  147. package/src/server/management/provider-routes.ts +77 -2
  148. package/src/server/management/route-registry.ts +6 -1
  149. package/src/server/management-api.ts +7 -0
  150. package/src/server/request-log.ts +4 -0
  151. package/src/server/responses/codex-ws-exchange.ts +90 -14
  152. package/src/server/responses/codex-ws-wire.ts +51 -1
  153. package/src/server/responses/compact.ts +8 -1
  154. package/src/server/responses/core.ts +188 -22
  155. package/src/server/responses/ws-upstream.ts +1 -1
  156. package/src/server/responses-undeclared-tool-guard.ts +261 -12
  157. package/src/server/zai-responses-startup.ts +21 -0
  158. package/src/types/config.ts +33 -3
  159. package/src/types/provider.ts +54 -4
  160. package/src/types/request.ts +1 -1
  161. package/src/types/tools.ts +36 -6
  162. package/src/update/job.ts +33 -11
  163. package/src/vision/plan.ts +40 -37
  164. package/src/web-search/index.ts +1 -0
  165. package/gui/dist/assets/index-BoBRSehJ.css +0 -1
  166. package/gui/dist/assets/index-Dx0xv2EA.js +0 -115
@@ -1,7 +1,23 @@
1
1
  import { MAX_CLIENT_SSE_FRAME_BYTES } from "../sse-frame-buffer";
2
+ import {
3
+ markResponseNonReplayable,
4
+ UPSTREAM_CLOSED_BEFORE_RESPONSE_CODE,
5
+ UPSTREAM_NO_RESPONSE_CODE,
6
+ } from "../../lib/upstream-retry";
2
7
  // If the 101 never arrives (network black hole), give SSE a chance well before
3
8
  // the caller's connect timeout (default 200s) would fire.
4
9
  export const UPGRADE_DEADLINE_MS = 10_000;
10
+ // Liveness while the exchange waits for its first response event. The proxy cannot know
11
+ // how long the origin legitimately needs before `response.created` (a multi-image replay
12
+ // spends that time in prefill; #4083 measured 30 s), and it is not the party that owns
13
+ // the slowness policy — the client holds its own deadline and the operator holds
14
+ // `connectTimeoutMs`. What the proxy can decide is whether the peer is still there, and
15
+ // WebSocket has a native answer: ping. While waiting, the exchange pings every
16
+ // CODEX_WS_LIVENESS_PING_INTERVAL_MS on sockets that expose `ping()`; any inbound frame
17
+ // or pong resets the silence clock, and only CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_MS of
18
+ // nothing at all settles the exchange as an origin-silence 504. A peer that never pongs
19
+ // keeps exactly the previous 90 s bound; a peer that does can never trip it while alive.
20
+ export const CODEX_WS_LIVENESS_PING_INTERVAL_MS = 15_000;
5
21
  export const CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_MS = 90_000;
6
22
  // Keep the push-based WS transport inside the same memory envelope as the
7
23
  // bounded SSE relays that consume this response. Unlike fetch response bodies,
@@ -48,6 +64,35 @@ export function markCodexWsResponse(response: Response, observed: boolean): void
48
64
  if (observed) quotaObservedResponses.add(response);
49
65
  }
50
66
 
67
+ /**
68
+ * The honest settlement for an exchange that sent its create frame and never saw a
69
+ * response event.
70
+ *
71
+ * Before this existed the relay committed a 200 SSE Response and errored its body, on the
72
+ * reasoning that a 5xx could make the pre-stream retry wrapper resend the frame. That
73
+ * reasoning was right about the resend and wrong about the status: it turned "no response"
74
+ * into "a response that failed", removed the code a user agent uses for its own retry
75
+ * policy, and neutered the client's first-byte timeout with chunked headers. The client
76
+ * on the direct path receives a gateway status in this situation and retries under its own
77
+ * policy; this response restores that equivalence. The resend is forbidden by the
78
+ * non-replayable marker instead (see upstream-retry.ts), and the structured code lets the
79
+ * combo failover reach the same verdict after it re-parses the body.
80
+ *
81
+ * 504 is the origin's silence (nothing at all, or nothing but liveness, for the tolerated
82
+ * window); 502 is a transport that closed or misbehaved after the send. Both carry the
83
+ * content-free stage detail in the message and the metadata snapshot in the headers, the
84
+ * same way a refused create does, so quota captured during the prelude is not lost.
85
+ */
86
+ export function codexWsPreResponseFailure(status: 502 | 504, message: string, prelude: Headers): Response {
87
+ const headers = new Headers(prelude);
88
+ headers.set("content-type", "application/json");
89
+ headers.set("cache-control", "no-store");
90
+ const code = status === 504 ? UPSTREAM_NO_RESPONSE_CODE : UPSTREAM_CLOSED_BEFORE_RESPONSE_CODE;
91
+ const response = new Response(JSON.stringify({ error: { type: "upstream_error", code, message } }), { status, headers });
92
+ markResponseNonReplayable(response);
93
+ return response;
94
+ }
95
+
51
96
  const CLOSED_BEFORE_TERMINAL = "codex websocket closed before a Responses terminal event";
52
97
 
53
98
  /**
@@ -78,6 +123,10 @@ export type CodexWsFailureStage = {
78
123
  firstFrameMs: number | null;
79
124
  /** Milliseconds from send to this failure; null when the failure predates the send. */
80
125
  elapsedMs: number | null;
126
+ /** Liveness pings the exchange sent while waiting for the first response event. */
127
+ pings: number;
128
+ /** Pongs the peer answered with; zero on a peer that never answers pings. */
129
+ pongs: number;
81
130
  };
82
131
 
83
132
  /**
@@ -113,7 +162,8 @@ export function codexWsFailureDetail(stage: CodexWsFailureStage): string {
113
162
  return ` [cause=${classifyCodexWsFailure(stage)} request=${stage.requestBytes}B`
114
163
  + ` sent=${stage.sent ? "yes" : "no"} frames=${stage.upstreamFrames}`
115
164
  + ` control=${stage.controlFrames} relayed=${stage.relayedEvents}`
116
- + ` first-frame=${duration(stage.firstFrameMs)} elapsed=${duration(stage.elapsedMs)}]`;
165
+ + ` first-frame=${duration(stage.firstFrameMs)} elapsed=${duration(stage.elapsedMs)}`
166
+ + ` pings=${stage.pings} pongs=${stage.pongs}]`;
117
167
  }
118
168
 
119
169
  export type ResponsesWsRelayEvent = {
@@ -111,7 +111,7 @@ import {
111
111
  UnsupportedContentEncodingError,
112
112
  } from "../request-decompress";
113
113
  import { resolveAdapter, resolveWireProtocolOverride } from "../adapter-resolve";
114
- import { hasKeyPoolFailover, rotateProviderTransportOn429 } from "../../providers/key-failover";
114
+ import { hasKeyPoolFailover, rotateProviderTransportOn429, selectProactiveApiKeyTransport } from "../../providers/key-failover";
115
115
  import { shouldAttemptImageTierRetry } from "../image-retry";
116
116
  import { resolveProviderTransport } from "../../providers/xai-transport";
117
117
  import type { WsData } from "../ws-bridge";
@@ -743,6 +743,13 @@ export async function handleResponsesCompact(
743
743
  ? CODEX_FORWARD_BASE_URL
744
744
  : (compactProvider.baseUrl ?? "").replace(/\/+$/, "");
745
745
  if (compactProvider.authMode !== "forward" && compactProvider.apiKey) {
746
+ // Native compact never enters handleResponses, so it needs its own pre-dispatch key
747
+ // pick. Kept inside this branch on purpose: the overlay above owns the forward and
748
+ // codexAccountMode cases, and the picker returns null for them anyway.
749
+ // Transport variant, for the same reason as the Responses core: the persisted row has no
750
+ // registry backfills, and compactProvider is read for its transport fields below.
751
+ const warmKeyProvider = selectProactiveApiKeyTransport(config, route.providerName, compactProvider);
752
+ if (warmKeyProvider?.apiKey) compactProvider = warmKeyProvider;
746
753
  headers.set("authorization", `Bearer ${resolveProviderApiKey(compactProvider.apiKey)}`);
747
754
  }
748
755
  const { reasoning: _reasoning, ...compactBodyRaw } = raw as typeof raw & { reasoning?: unknown };
@@ -1,4 +1,6 @@
1
1
  import type { Server } from "bun";
2
+ import { recordContextSessionOwner } from "../../codex/context-owner";
3
+ import { contextRelayActivated } from "../../codex/context-compat";
2
4
  import { randomUUID } from "node:crypto";
3
5
  import { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type ResponsesTerminalStatus } from "../../bridge";
4
6
  import { formatPassthroughUpstreamError } from "./passthrough-error";
@@ -145,6 +147,7 @@ import {
145
147
  GENERIC_OAUTH_MAX_FAILOVERS_PER_REQUEST,
146
148
  isGenericFailoverProvider,
147
149
  isGenericOAuthFailoverEnabled,
150
+ noteGenericPoolSelection,
148
151
  preferredInitialAccount,
149
152
  rotateGenericOAuthAccountOn429,
150
153
  } from "../../oauth/generic-account-failover";
@@ -210,6 +213,8 @@ import {
210
213
  applyUpstreamRecoveryInit,
211
214
  fetchWithResetRetry,
212
215
  fetchWithTransientRetry,
216
+ isNonReplayableResponse,
217
+ isTransientUpstreamStatus,
213
218
  prepareSameTarget429Wait,
214
219
  } from "../../lib/upstream-retry";
215
220
  import {
@@ -218,6 +223,7 @@ import {
218
223
  isProxyAdmissionSecret,
219
224
  validateForwardAdmissionCredential,
220
225
  } from "../auth-cors";
226
+ import { resolveContextPrincipal } from "../auth-cors";
221
227
  import type { DataPlaneAdmission } from "../auth-cors";
222
228
  import { createTranslatorBudget, isTranslatorBudgetExceededError, type TranslatorBudget } from "../../lib/translator-budget";
223
229
  import { captureExplicitOpenAiCallerAuth, listOpenAiForwardSidecarCandidates, resolveFirstUsableOpenAiSidecar, type ExplicitOpenAiCallerAuth, type ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar";
@@ -265,6 +271,7 @@ import type { AdapterRequest, ProviderAdapter } from "../../adapters/base";
265
271
  import { providerApiKeySelectionIsCurrent, resolveCurrentProviderApiKeyTransport } from "../../providers/api-key-selection";
266
272
  import {
267
273
  hasKeyPoolFailover,
274
+ selectProactiveApiKeyTransport,
268
275
  rateLimitRetryDelayMs,
269
276
  rateLimitRetryPolicyFor,
270
277
  rotateProviderTransportOn429,
@@ -290,6 +297,7 @@ import {
290
297
  upstreamErrorMessageFromPayload,
291
298
  } from "../../lib/errors";
292
299
  import type { AdmissionLease } from "../../lib/admission";
300
+ import { tryClaimNativeMainProfileForTurn as tryClaimStoredSidecarMainProfile } from "../../codex/native-main-admission";
293
301
  import { prepareEffortNormalization, supportedLadderFor } from "../effort-policy";
294
302
  import { isThreadSpawnRequest } from "../effort-policy";
295
303
  import {
@@ -410,7 +418,7 @@ import {
410
418
  payloadRewriteAsBlockRewrite,
411
419
  relaySseWithBlockRewrite,
412
420
  } from "../sse-payload-rewrite";
413
- import { restoreRoutedCustomCalls, restoreRoutedCustomCallsInJson } from "../../responses/custom-tool-compat";
421
+ import { hasUnmappedRoutedCustomToolOutput, restoreRoutedCustomCalls, restoreRoutedCustomCallsInJson } from "../../responses/custom-tool-compat";
414
422
  import { createRoutedCustomToolRestoreBlockRewrite } from "../responses-custom-tool-repair";
415
423
  import { collectFunctionCallRepairSchemas, repairFunctionCallsInJson } from "../../responses/function-call-compat";
416
424
  import { createResponsesFunctionToolRepairBlockRewrite } from "../responses-function-tool-repair";
@@ -424,10 +432,13 @@ import {
424
432
  type RoutedNamespaceToolAliases,
425
433
  } from "../../responses/namespace-tool-compat";
426
434
  import {
435
+ collectDeclaredBareWireToolNames,
427
436
  collectDeclaredNamelessClientCallTypes,
428
437
  collectDeclaredWireToolNames,
429
438
  collectProviderExecutedCallTypes,
430
439
  createUndeclaredToolCallGuardBlockRewrite,
440
+ normalizeDefaultNamespaceInJson,
441
+ normalizeDefaultNamespaceInResponse,
431
442
  currentTurnWireToolCatalogBody,
432
443
  hasExplicitWireToolCatalog,
433
444
  undeclaredToolCallMessage,
@@ -827,7 +838,8 @@ async function opaqueBlobRejectionBodyForRecovery(
827
838
  signal: AbortSignal,
828
839
  ): Promise<string | undefined> {
829
840
  if (
830
- response.status < 400
841
+ isNonReplayableResponse(response)
842
+ || response.status < 400
831
843
  || (response.status >= 500 && response.status !== 502)
832
844
  || adapterName !== "openai-responses"
833
845
  || alreadyAttempted
@@ -1121,6 +1133,9 @@ export async function shouldRetryCodexPoolAccountQuota(
1121
1133
  response: Response,
1122
1134
  signal?: AbortSignal,
1123
1135
  ): Promise<boolean> {
1136
+ // A post-send WebSocket gateway status must not become a second account's send; the
1137
+ // body carries no quota evidence either, but the marker is the contract, not the prose.
1138
+ if (isNonReplayableResponse(response)) return false;
1124
1139
  if (response.status === 402 || response.status === 429) return true;
1125
1140
  if (response.status < 500 || response.status >= 600) return false;
1126
1141
  try {
@@ -1136,6 +1151,28 @@ export async function shouldRetryCodexPoolAccountQuota(
1136
1151
  }
1137
1152
  }
1138
1153
 
1154
+ /**
1155
+ * A pre-stream upstream 5xx another Codex account may still be able to serve.
1156
+ *
1157
+ * `server_is_overloaded` is the shape this exists for. The ChatGPT backend refuses in a few
1158
+ * hundred milliseconds, the body carries no quota evidence, and nothing in that exchange is
1159
+ * account health — so the pool keeps choosing the same account and every request fails on it
1160
+ * while the other accounts sit idle. That is what an operator sees as the pool refusing to move.
1161
+ *
1162
+ * The status stays exactly as upstream sent it. `classifyCodexUpstreamOutcome` maps 5xx to the
1163
+ * transient class, so the account earns an ordinary failure streak and `upstreamFailoverThreshold`
1164
+ * decides when it is soft-avoided, rather than a quota cooldown it never earned.
1165
+ *
1166
+ * Deliberately narrow. {@link isNonReplayableResponse} still refuses: a post-send WebSocket
1167
+ * gateway status means the body already reached the origin, so sending it from a second account
1168
+ * could duplicate a turn the origin may still be running. A 5xx whose body confirms quota is not
1169
+ * routed here either — {@link shouldRetryCodexPoolAccountQuota} classifies that one first and
1170
+ * carries the cooldown with it.
1171
+ */
1172
+ export function shouldRetryCodexPoolAccountTransient(response: Response): boolean {
1173
+ return !isNonReplayableResponse(response) && isTransientUpstreamStatus(response.status);
1174
+ }
1175
+
1139
1176
  interface CodexPoolAccountRetryArgs {
1140
1177
  /** Sanitized caller input, before any selected Pool credential was materialized. */
1141
1178
  callerAuthHeaders: Headers;
@@ -1310,6 +1347,21 @@ async function retryCodexPoolOnAlternateAccount(
1310
1347
  const inboundWire = options.inboundWire ?? "responses";
1311
1348
  const entitlementResolver = options.resolveCodexModelEntitlements ?? resolveCodexModelEntitlements;
1312
1349
  let retryAuthCtx: CodexAuthContext | undefined;
1350
+ // A transient 5xx must record even when this request cannot move: the ordinary terminal
1351
+ // recorder only fires for an OK event-stream body, so a pre-stream refusal would otherwise
1352
+ // leave the account looking healthy no matter how many times it refused, and the pool would
1353
+ // keep handing it the next request.
1354
+ const recordUnmovedTransientOutcome = (): void => {
1355
+ if (!isTransientUpstreamStatus(outcomeStatus)) return;
1356
+ recordCodexUpstreamOutcome(config, firstAuthCtx.accountId, outcomeStatus, {
1357
+ threadId: firstAuthCtx.affinityKey,
1358
+ fixedAccount: firstAuthCtx.fixedAccount,
1359
+ modelId: route.modelId,
1360
+ probeLeaseId: codexProbeLeaseId(firstAuthCtx),
1361
+ probeQuotaScope: codexProbeQuotaScope(firstAuthCtx),
1362
+ writerGeneration: firstAuthCtx.writerGeneration,
1363
+ });
1364
+ };
1313
1365
  if (outcomeStatus === 400 && ACCOUNT_GATED_NATIVE_OPENAI_MODELS.has(route.modelId)) {
1314
1366
  invalidateCodexModelEntitlementsForAccount(firstAuthCtx.accountId);
1315
1367
  let refreshed;
@@ -1334,6 +1386,7 @@ async function retryCodexPoolOnAlternateAccount(
1334
1386
  // Exact account selectors may retry the same confirmed account above, but must never resolve
1335
1387
  // an alternate. Quota failures and a refreshed entitlement miss remain terminal.
1336
1388
  if (!retryAuthCtx && (firstAuthCtx.fixedAccount || args.sameAccountOnly === true)) {
1389
+ recordUnmovedTransientOutcome();
1337
1390
  return { kind: "no-alternate" };
1338
1391
  }
1339
1392
  try {
@@ -1384,6 +1437,7 @@ async function retryCodexPoolOnAlternateAccount(
1384
1437
  writerGeneration: firstAuthCtx.writerGeneration,
1385
1438
  });
1386
1439
  }
1440
+ recordUnmovedTransientOutcome();
1387
1441
  return { kind: "no-alternate" };
1388
1442
  }
1389
1443
 
@@ -1426,6 +1480,7 @@ async function retryCodexPoolOnAlternateAccount(
1426
1480
  const retryAdapter = resolveAdapter(
1427
1481
  resolveWireProtocolOverride(route.providerName, route.modelId, retryProvider, inboundWire),
1428
1482
  config.cacheRetention,
1483
+ route.providerName,
1429
1484
  );
1430
1485
  bindRouteReasoningReplayScope({
1431
1486
  parsed,
@@ -1670,6 +1725,10 @@ export interface ConsumedComboFailure {
1670
1725
 
1671
1726
 
1672
1727
  export interface HandleResponsesOptions {
1728
+ /** Internal Claude replay identity; consumed only by the final canonical Go transport. */
1729
+ claudeGoAffinity?: { sessionLane?: string };
1730
+ /** Validated Claude metadata identity; projected only into final canonical attempt headers. */
1731
+ claudeNativeSessionId?: string;
1673
1732
  /** Original live policy owner; separate from caller-specific routing/sidecar snapshots. */
1674
1733
  codexAuthPolicy?: CodexAuthPolicyConfig;
1675
1734
  turnAdmissionLease?: AdmissionLease;
@@ -1728,6 +1787,8 @@ export interface HandleResponsesOptions {
1728
1787
  trustedClaudeMainAuth?: { authorization: string; chatgptAccountId?: string };
1729
1788
  /** Sidecar-only auth captured before route changes; null means no usable original pair. */
1730
1789
  openAiSidecarAuth?: ExplicitOpenAiCallerAuth | null;
1790
+ /** Internal Chat bridge permission to obtain claimed stored auth only for a final Direct sidecar. */
1791
+ allowStoredOpenAiSidecarAuth?: boolean;
1731
1792
  /** Original caller-owned native pair; separate from any claimed sidecar enrichment. */
1732
1793
  nativeCallerAuth?: ExplicitOpenAiCallerAuth | null;
1733
1794
  /** Caller Direct credential under Direct\'s own predicate; restored only for the canonical OpenAI final route. */
@@ -2037,6 +2098,15 @@ function canPassThroughEncryptedV2AgentTask(
2037
2098
  ).adapter === "openai-responses";
2038
2099
  }
2039
2100
 
2101
+ /** Keep synthesized Claude identity out of request headers reused by policy/combo fallback. */
2102
+ function withClaudeNativeSession(headers: Headers, provider: OcxProviderConfig, sessionId?: string): Headers {
2103
+ if (!sessionId || !isCanonicalOpenAiForwardProvider(provider)
2104
+ || headers.has("session_id") || headers.has("session-id") || headers.has("thread-id")) return headers;
2105
+ const forwarded = new Headers(headers);
2106
+ forwarded.set("session_id", sessionId);
2107
+ return forwarded;
2108
+ }
2109
+
2040
2110
  type ResponsesAuthResolution =
2041
2111
  | { ok: true; authCtx: CodexAuthContext; headers: Headers; callerAuthHeaders: Headers; substituteMainCredential: boolean }
2042
2112
  | { ok: false; response: Response };
@@ -2471,6 +2541,7 @@ async function applyFinalRouteRequestNormalization(args: {
2471
2541
  logCtx: RequestLogContext;
2472
2542
  inboundWire: InboundWire;
2473
2543
  inboundTransport?: "websocket";
2544
+ claudeGoAffinity?: HandleResponsesOptions["claudeGoAffinity"];
2474
2545
  }): Promise<void> {
2475
2546
  const { parsed, route, config, req, logCtx, inboundWire, inboundTransport } = args;
2476
2547
  const effortSelector = prepareEffortNormalization(parsed, route);
@@ -2498,7 +2569,8 @@ async function applyFinalRouteRequestNormalization(args: {
2498
2569
 
2499
2570
  // Settle the wire once so logging, fast-mode, auth, and sidecars read the adapter
2500
2571
  // this request will actually use (#404).
2501
- route.provider = resolveOpenCodeGoTransport(route.provider, getOrAllocateRequestSessionLane(req));
2572
+ route.provider = resolveOpenCodeGoTransport(route.provider,
2573
+ args.claudeGoAffinity ? args.claudeGoAffinity.sessionLane : getOrAllocateRequestSessionLane(req));
2502
2574
  route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire);
2503
2575
  if (preserveAnthropicResponseModel) parsed._responseModelId = responseModelId;
2504
2576
  logCtx.model = route.modelId;
@@ -3891,6 +3963,22 @@ async function handleResponsesInner(
3891
3963
  );
3892
3964
  }
3893
3965
 
3966
+ if (hasUnexpandedPreviousResponse) {
3967
+ const continuationProvider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire);
3968
+ // Stateless destinations cannot resolve the omitted prefix. Stateful destinations may,
3969
+ // but a lowered custom result still needs its call to recover the original wire type.
3970
+ // Native function/custom continuations without lowering keep their upstream-owned state.
3971
+ if (continuationProvider.adapter === "openai-responses"
3972
+ && (continuationProvider.statelessResponses === true
3973
+ || hasUnmappedRoutedCustomToolOutput(parsed._rawBody, continuationProvider.supportsResponsesCustomTools))) {
3974
+ return formatErrorResponse(
3975
+ 400,
3976
+ "previous_response_not_found",
3977
+ "Routed continuation requires unavailable local history; resend the full conversation without previous_response_id.",
3978
+ );
3979
+ }
3980
+ }
3981
+
3894
3982
  // Captured before normalization: whether the CLIENT asked for SSE. The
3895
3983
  // transport-neutral upstream-streaming policy below may force a bounded JSON
3896
3984
  // upstream for reliability (#875); the answer must then be reframed to SSE
@@ -3904,6 +3992,7 @@ async function handleResponsesInner(
3904
3992
  logCtx,
3905
3993
  inboundWire,
3906
3994
  inboundTransport: options.inboundTransport,
3995
+ claudeGoAffinity: options.claudeGoAffinity,
3907
3996
  });
3908
3997
  // Attribute local auth/cooldown failures to the public selector too; exact auth may fail before
3909
3998
  // the normal post-resolution provider label is assigned.
@@ -3965,8 +4054,8 @@ async function handleResponsesInner(
3965
4054
  const finalAuth = await resolveResponsesCodexAuth(req, config, route, options, credentialDomainWasRewritten);
3966
4055
  if (!finalAuth.ok) return finalAuth.response;
3967
4056
  authCtx = finalAuth.authCtx;
3968
- selectedForwardHeaders = finalAuth.headers;
3969
- callerAuthHeaders = finalAuth.callerAuthHeaders;
4057
+ selectedForwardHeaders = withClaudeNativeSession(finalAuth.headers, route.provider, options.claudeNativeSessionId);
4058
+ callerAuthHeaders = withClaudeNativeSession(finalAuth.callerAuthHeaders, route.provider, options.claudeNativeSessionId);
3970
4059
  substituteMainCredential = finalAuth.substituteMainCredential;
3971
4060
  }
3972
4061
 
@@ -4158,7 +4247,7 @@ async function handleResponsesInner(
4158
4247
  && credentialGeneration(row.credential) === binding.snapshot.generation;
4159
4248
  };
4160
4249
  const resolveSelectionAdapter = (provider: OcxProviderConfig, retention = config.cacheRetention): ProviderAdapter => {
4161
- const resolved = resolveAdapter(provider, retention);
4250
+ const resolved = resolveAdapter(provider, retention, route.providerName);
4162
4251
  if (route.provider.authMode === "forward") return resolved;
4163
4252
  const binding: DispatchBinding | undefined = route.provider.authMode === "oauth"
4164
4253
  ? oauthSelection && servingOAuthSnapshot
@@ -4397,6 +4486,10 @@ async function handleResponsesInner(
4397
4486
  // whichever account is active by the time the response comes back (#2568).
4398
4487
  if (isGenericFailoverProvider(route.providerName, route.provider)) {
4399
4488
  genericFailoverAccountId = resolved.accountId;
4489
+ // Advance the pool cursor only now that this account is actually admitted. The
4490
+ // helper returns immediately unless the kernel is on AND the strategy is
4491
+ // round-robin, so quota and fill-first pools reach it without being touched.
4492
+ noteGenericPoolSelection(config, route.providerName, resolved.accountId);
4400
4493
  }
4401
4494
  // Anthropic is excluded from isGenericFailoverProvider -- its own pool owns affinity and
4402
4495
  // a fail-closed local-cli credential rule -- so without this stamp its identity is
@@ -4434,6 +4527,26 @@ async function handleResponsesInner(
4434
4527
  return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
4435
4528
  }
4436
4529
  }
4530
+ // Key-auth twin of the OAuth preference above: pick a warm key BEFORE the first attempt when
4531
+ // the committed one is already cooling, instead of spending the request earning a 429 the
4532
+ // runtime could already predict. The picker refuses to override a healthy committed key and
4533
+ // returns null without a configured strategy, so an ordinary install evaluates one predicate.
4534
+ //
4535
+ // It RETURNS a rebuilt route rather than mutating one, and the assignment has to land here --
4536
+ // ahead of the transport pin below, the adapterProvider copy that follows it, and the request
4537
+ // the HTTP path bakes later. The image bridge and web search read route.provider directly and
4538
+ // have no stale-selection re-read to save them, so ordering is the whole correctness argument.
4539
+ //
4540
+ // The Transport variant, not the bare picker: the picker answers with the PERSISTED row, and
4541
+ // a built-in provider stored in its valid minimal form would lose the adapter id, base URL
4542
+ // and static headers registry backfill supplies, throwing `Unknown adapter: undefined`.
4543
+ const proactiveKeyProvider = selectProactiveApiKeyTransport(
4544
+ config,
4545
+ route.providerName,
4546
+ route.provider,
4547
+ parsed.options.promptCacheKey,
4548
+ );
4549
+ if (proactiveKeyProvider) route.provider = proactiveKeyProvider;
4437
4550
  route.provider = resolveProviderTransport(
4438
4551
  route.providerName,
4439
4552
  route.provider,
@@ -4530,21 +4643,42 @@ async function handleResponsesInner(
4530
4643
  }
4531
4644
 
4532
4645
  let openAiSidecar: ResolvedOpenAiForwardSidecar | undefined;
4533
- const needsOpenAiVision = shouldResolveOpenAiVisionSidecar(config, route.provider, route.modelId, parsed);
4534
- const needsOpenAiSearch = shouldResolveOpenAiWebSearchSidecar(config, parsed, isPassthrough);
4646
+ const visionDescribeTerminal = options.visionDescribeTerminal === true;
4647
+ const routedCompaction = parsed._compactionRequest === true
4648
+ && !isCanonicalOpenAiForwardProvider(route.provider);
4649
+ const needsOpenAiVision = !visionDescribeTerminal
4650
+ && shouldResolveOpenAiVisionSidecar(config, route.provider, route.modelId, parsed);
4651
+ const needsOpenAiSearch = !routedCompaction && !adapter.runTurn
4652
+ && shouldResolveOpenAiWebSearchSidecar(config, parsed, isPassthrough);
4535
4653
  if (needsOpenAiVision || needsOpenAiSearch) {
4536
4654
  try {
4655
+ const candidates = listOpenAiForwardSidecarCandidates(config);
4656
+ let sidecarAuth = options.openAiSidecarAuth;
4657
+ if (!sidecarAuth && options.allowStoredOpenAiSidecarAuth === true
4658
+ && route.codexAccountId === undefined
4659
+ && candidates.some(candidate => candidate.accountMode === "direct")
4660
+ && tryClaimStoredSidecarMainProfile(options.turnAdmissionLease)) {
4661
+ // Request-local helper authority only: never promote this pair to caller, primary,
4662
+ // or retry credentials. Claim before reading so profile switches remain fenced.
4663
+ try {
4664
+ const { getMainAccountToken } = await import("../../codex/main-account");
4665
+ const token = getMainAccountToken();
4666
+ if (token) sidecarAuth = captureExplicitOpenAiCallerAuth(new Headers({
4667
+ authorization: `Bearer ${token.accessToken}`, "chatgpt-account-id": token.chatgptAccountId,
4668
+ }), config);
4669
+ } catch { /* stored enrichment is optional */ }
4670
+ }
4537
4671
  // Preserve explicit OpenAI helper auth across route changes without returning it to
4538
4672
  // primary-provider headers or alternate-main retry. The resolver revalidates scope.
4539
4673
  const sidecarHeaders = new Headers(req.headers);
4540
4674
  sidecarHeaders.delete("authorization");
4541
4675
  sidecarHeaders.delete("chatgpt-account-id");
4542
- if (options.openAiSidecarAuth) {
4543
- sidecarHeaders.set("authorization", options.openAiSidecarAuth.authorization);
4544
- sidecarHeaders.set("chatgpt-account-id", options.openAiSidecarAuth.chatgptAccountId);
4676
+ if (sidecarAuth) {
4677
+ sidecarHeaders.set("authorization", sidecarAuth.authorization);
4678
+ sidecarHeaders.set("chatgpt-account-id", sidecarAuth.chatgptAccountId);
4545
4679
  }
4546
4680
  openAiSidecar = await resolveFirstUsableOpenAiSidecar(
4547
- listOpenAiForwardSidecarCandidates(config),
4681
+ candidates,
4548
4682
  sidecarHeaders,
4549
4683
  config,
4550
4684
  {
@@ -4579,7 +4713,6 @@ async function handleResponsesInner(
4579
4713
  // call must never plan another describe. The flag arrives from the Chat
4580
4714
  // surface (whose bridge rebuilds headers) or as the raw header for native
4581
4715
  // Responses callers. Marked + text-only routed model → strip, depth cap 1.
4582
- const visionDescribeTerminal = options.visionDescribeTerminal === true;
4583
4716
  const visionPlan = visionDescribeTerminal
4584
4717
  ? undefined
4585
4718
  : planVisionSidecar(config, route.provider, route.modelId, parsed, openAiSidecar, {
@@ -4641,10 +4774,16 @@ async function handleResponsesInner(
4641
4774
  // `compaction_trigger` item — only the canonical ChatGPT backend speaks that
4642
4775
  // contract. An API-key gateway would receive the trigger, answer with an ordinary
4643
4776
  // message, and leave Codex fataling on a missing compaction item (#422).
4644
- const routedCompaction = parsed._compactionRequest === true
4645
- && !isCanonicalOpenAiForwardProvider(route.provider);
4646
- const commitReasoningReplayServingRoute = (): void => {
4777
+ const commitReasoningReplayServingRoute = (outboundHeaders?: HeadersInit): void => {
4647
4778
  commitReasoningReplayServingIdentity(parsed._reasoningReplayScope);
4779
+ // History has no model namespace. Record the account that actually accepted this
4780
+ // final attempt, after refresh/failover, rather than guessing from mutable affinity.
4781
+ // Recording is relay state. With the feature off there is no relay, so building an owner
4782
+ // registry for it is out of scope for this request.
4783
+ if (outboundHeaders && isCanonicalOpenAiForwardProvider(route.provider) && contextRelayActivated()) {
4784
+ recordContextSessionOwner(resolveContextPrincipal(req, config, options.admission), req.headers,
4785
+ route.provider.baseUrl, authCtx, new Headers(outboundHeaders), substituteMainCredential);
4786
+ }
4648
4787
  };
4649
4788
  if (routedCompaction) {
4650
4789
  delete parsed.context.tools;
@@ -4714,6 +4853,7 @@ async function handleResponsesInner(
4714
4853
  );
4715
4854
  const clientExplicitWireToolCatalog = hasExplicitWireToolCatalog(clientToolAuthorizationBody);
4716
4855
  const clientDeclaredWireToolNames = collectDeclaredWireToolNames(clientToolAuthorizationBody);
4856
+ const clientDeclaredBareWireToolNames = collectDeclaredBareWireToolNames(clientToolAuthorizationBody);
4717
4857
  const clientDeclaredNamelessCallTypes = collectDeclaredNamelessClientCallTypes(
4718
4858
  clientToolAuthorizationBody,
4719
4859
  );
@@ -4789,6 +4929,7 @@ async function handleResponsesInner(
4789
4929
  };
4790
4930
  let outboundRequestBody: Record<string, unknown> | undefined;
4791
4931
  const declaredWireToolNames = new Set<string>();
4932
+ const declaredBareWireToolNames = new Set<string>();
4792
4933
  const declaredNamelessClientCallTypes = new Set<string>();
4793
4934
  // `buildToolBridgeMaps` creates a bare alias only when the caller selected exactly one
4794
4935
  // namespaced tool through a bare tool_choice. Restore that request-bounded identity before
@@ -4838,12 +4979,17 @@ async function handleResponsesInner(
4838
4979
  // aliases are authoritative. A continuation's outbound body still contains historical
4839
4980
  // catalogs (and may promote historical tool-search definitions), so it can never widen the
4840
4981
  // current caller snapshot captured above.
4982
+ declaredBareWireToolNames.clear();
4841
4983
  if (replayedInputPrefixLength === 0) {
4842
4984
  for (const name of collectDeclaredWireToolNames(outboundRequestBody)) {
4843
4985
  declaredWireToolNames.add(name);
4844
4986
  }
4987
+ for (const name of collectDeclaredBareWireToolNames(outboundRequestBody)) {
4988
+ declaredBareWireToolNames.add(name);
4989
+ }
4845
4990
  }
4846
4991
  for (const name of clientDeclaredWireToolNames) declaredWireToolNames.add(name);
4992
+ for (const name of clientDeclaredBareWireToolNames) declaredBareWireToolNames.add(name);
4847
4993
  declaredNamelessClientCallTypes.clear();
4848
4994
  if (replayedInputPrefixLength === 0) {
4849
4995
  for (const callType of collectDeclaredNamelessClientCallTypes(outboundRequestBody)) {
@@ -4936,6 +5082,7 @@ async function handleResponsesInner(
4936
5082
  declaredWireToolNames,
4937
5083
  declaredNamelessClientCallTypes,
4938
5084
  providerExecutedCallTypes,
5085
+ declaredBareWireToolNames,
4939
5086
  ) !== undefined) {
4940
5087
  inspectionSawUndeclaredTool = true;
4941
5088
  }
@@ -4973,11 +5120,19 @@ async function handleResponsesInner(
4973
5120
  declaredWireToolNames,
4974
5121
  declaredNamelessClientCallTypes,
4975
5122
  providerExecutedCallTypes,
5123
+ declaredBareWireToolNames,
4976
5124
  ) !== undefined
4977
5125
  ) {
4978
5126
  return;
4979
5127
  }
4980
- rememberPassthroughResponse?.(replayResponse);
5128
+ const normalizedReplayResponse = (undeclaredToolGuardActive
5129
+ ? normalizeDefaultNamespaceInResponse(
5130
+ replayResponse,
5131
+ declaredWireToolNames,
5132
+ declaredBareWireToolNames,
5133
+ ).value
5134
+ : replayResponse) as typeof replayResponse;
5135
+ rememberPassthroughResponse?.(normalizedReplayResponse);
4981
5136
  const firstCompletion = !inspectedCompletionSeen;
4982
5137
  inspectedCompletionSeen = true;
4983
5138
  if (firstCompletion && (inspectedTerminal === null || firstTerminalAllowsRecall)) {
@@ -5291,7 +5446,7 @@ async function handleResponsesInner(
5291
5446
  }
5292
5447
  authCtx = replay.authCtx;
5293
5448
  route.provider = replay.provider;
5294
- selectedForwardHeaders = replay.headers;
5449
+ selectedForwardHeaders = withClaudeNativeSession(replay.headers, replay.provider, options.claudeNativeSessionId);
5295
5450
  const replayAdapter = resolveSelectionAdapter(
5296
5451
  resolveWireProtocolOverride(route.providerName, route.modelId, replay.provider, inboundWire),
5297
5452
  config.cacheRetention,
@@ -5618,6 +5773,10 @@ async function handleResponsesInner(
5618
5773
  // ChatGPT sometimes wraps quota exhaustion in a generic 5xx. Normalize only
5619
5774
  // body-confirmed cases to quota evidence so cooldown and rotation both apply.
5620
5775
  poolRetryOutcome = upstreamResponse.status >= 500 ? 429 : upstreamResponse.status;
5776
+ } else if (!authCtx.fixedAccount && shouldRetryCodexPoolAccountTransient(upstreamResponse)) {
5777
+ // A plain transient 5xx the same-account retry layer could not absorb. Keep the real
5778
+ // status so it records as transient rather than quota.
5779
+ poolRetryOutcome = upstreamResponse.status;
5621
5780
  }
5622
5781
 
5623
5782
  if (poolRetryOutcome !== undefined) {
@@ -5863,7 +6022,7 @@ async function handleResponsesInner(
5863
6022
  // For streamed passthrough, a successful terminal response means non-error upstream status
5864
6023
  // before relay starts. Waiting for SSE completion would retain request state across the whole
5865
6024
  // stream; a later body failure does not undo that this destination accepted and served the turn.
5866
- commitReasoningReplayServingRoute();
6025
+ commitReasoningReplayServingRoute(request.headers);
5867
6026
  const terminalRepairPolicy = providerModelResponsesTerminalRepair(
5868
6027
  route.providerName,
5869
6028
  route.provider,
@@ -5998,6 +6157,7 @@ async function handleResponsesInner(
5998
6157
  declaredWireToolNames,
5999
6158
  declaredNamelessClientCallTypes,
6000
6159
  providerExecutedCallTypes,
6160
+ declaredBareWireToolNames,
6001
6161
  )
6002
6162
  : undefined,
6003
6163
  ].filter((rewrite): rewrite is NonNullable<typeof rewrite> => rewrite !== undefined);
@@ -6198,7 +6358,7 @@ async function handleResponsesInner(
6198
6358
  }
6199
6359
  const text = bounded.text;
6200
6360
  inspectResponseLogJson(logCtx, text);
6201
- const clientJson = (() => {
6361
+ let clientJson = (() => {
6202
6362
  const restoredNamespace = restoreRoutedNamespaceCallsInJson(
6203
6363
  scrubSelfNamedToolCallNamespaceInJson(
6204
6364
  restoreImageGenCallsInJson(text, imageGenCallAliases),
@@ -6244,6 +6404,7 @@ async function handleResponsesInner(
6244
6404
  declaredWireToolNames,
6245
6405
  declaredNamelessClientCallTypes,
6246
6406
  providerExecutedCallTypes,
6407
+ declaredBareWireToolNames,
6247
6408
  );
6248
6409
  } catch {
6249
6410
  return undefined;
@@ -6252,8 +6413,13 @@ async function handleResponsesInner(
6252
6413
  if (undeclared !== undefined) {
6253
6414
  return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared));
6254
6415
  }
6416
+ clientJson = normalizeDefaultNamespaceInJson(
6417
+ clientJson,
6418
+ declaredWireToolNames,
6419
+ declaredBareWireToolNames,
6420
+ );
6255
6421
  }
6256
- commitReasoningReplayServingRoute();
6422
+ commitReasoningReplayServingRoute(request.headers);
6257
6423
  try {
6258
6424
  rememberPassthroughResponseChecked(
6259
6425
  JSON.parse(text) as { id?: unknown; output?: unknown; status?: unknown; model?: unknown },
@@ -6335,7 +6501,7 @@ async function handleResponsesInner(
6335
6501
  }
6336
6502
  // An unclassified passthrough body is relayed directly and has no bounded completion observer;
6337
6503
  // use the same non-error-status success boundary as SSE instead of retaining per-stream state.
6338
- commitReasoningReplayServingRoute();
6504
+ commitReasoningReplayServingRoute(request.headers);
6339
6505
  const body = relayWithAbort(upstreamResponse.body, upstream);
6340
6506
  const turnAc = new AbortController();
6341
6507
  const tracked = body ? trackStreamLifetime(body, turnAc, undefined, options.turnAdmissionLease) : null;
@@ -20,7 +20,7 @@ import { codexWsExchange } from "./codex-ws-exchange";
20
20
  import { CodexWsSession } from "./codex-ws-session";
21
21
  import { codexWsPool, codexWsReuseIdentity } from "./codex-ws-pool";
22
22
  import { codexWsCreateFrameExceedsLimit } from "./codex-ws-wire";
23
- export { CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_MS, MAX_CODEX_WS_FRAME_BYTES, MAX_CODEX_WS_QUEUE_BYTES,
23
+ export { CODEX_WS_LIVENESS_PING_INTERVAL_MS, CODEX_WS_RESPONSE_PRELUDE_TIMEOUT_MS, MAX_CODEX_WS_FRAME_BYTES, MAX_CODEX_WS_QUEUE_BYTES,
24
24
  MAX_CODEX_WS_CREATE_FRAME_BYTES, CODEX_WS_CREATE_FRAME_LIMIT_BYTES, codexWsCreateFrameExceedsLimit,
25
25
  isCodexWsQuotaObservedResponse, isCodexWsUpstreamResponse } from "./codex-ws-wire";
26
26
  export const MIN_BOUNDED_CODEX_WS_BUN_VERSION = "1.4.0";