@coseung2/opencodex 2.8.0-cs.13 → 2.8.0-cs.15

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 (60) hide show
  1. package/gui/dist/assets/index-BhXIu7c0.js +67 -0
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +3 -3
  4. package/packages/ocx-notch/README.md +2 -1
  5. package/src/adapters/cursor/discovery.ts +6 -2
  6. package/src/adapters/cursor/effort-map.ts +3 -0
  7. package/src/adapters/google-antigravity-replay.ts +24 -0
  8. package/src/adapters/google.ts +16 -11
  9. package/src/chat/inbound.ts +5 -11
  10. package/src/cli/account-api.ts +9 -1
  11. package/src/cli/account-extended.ts +4 -1
  12. package/src/codex/account-label.ts +14 -1
  13. package/src/codex/account-lifecycle.ts +12 -1
  14. package/src/codex/account-namespaces.ts +21 -0
  15. package/src/codex/account-priority.ts +49 -0
  16. package/src/codex/account-store.ts +2 -1
  17. package/src/codex/auth-api.ts +108 -17
  18. package/src/codex/auth-context.ts +61 -16
  19. package/src/codex/catalog/metadata.ts +34 -12
  20. package/src/codex/catalog/parsing.ts +8 -1
  21. package/src/codex/catalog/provider-fetch.ts +24 -6
  22. package/src/codex/catalog.ts +1 -1
  23. package/src/codex/pool-rotation.ts +51 -4
  24. package/src/codex/quota.ts +154 -35
  25. package/src/codex/routing.ts +139 -33
  26. package/src/codex/warmup.ts +193 -85
  27. package/src/config.ts +84 -1
  28. package/src/lib/bounded-body.ts +13 -6
  29. package/src/lib/bun-stream-caps.ts +5 -6
  30. package/src/lib/redact.ts +13 -0
  31. package/src/oauth/index.ts +79 -12
  32. package/src/oauth/log.ts +3 -1
  33. package/src/oauth/store.ts +31 -8
  34. package/src/providers/antigravity-models.ts +53 -24
  35. package/src/providers/codex-capacity.ts +303 -0
  36. package/src/providers/model-rename-migration.ts +147 -0
  37. package/src/providers/model-rename-startup.ts +29 -0
  38. package/src/providers/quota.ts +126 -16
  39. package/src/providers/registry.ts +258 -38
  40. package/src/responses/parser.ts +19 -12
  41. package/src/responses/spill-store.ts +14 -1
  42. package/src/responses/state.ts +108 -14
  43. package/src/server/index.ts +9 -1
  44. package/src/server/management/logs-usage-routes.ts +1 -0
  45. package/src/server/management/oauth-account-routes.ts +8 -1
  46. package/src/server/relay.ts +10 -42
  47. package/src/server/request-log.ts +42 -1
  48. package/src/server/responses/compact.ts +16 -4
  49. package/src/server/responses/core.ts +217 -59
  50. package/src/server/responses/empty-completion-guard.ts +275 -0
  51. package/src/server/responses/encrypted-payload.ts +54 -39
  52. package/src/server/responses/fetch-helpers.ts +24 -3
  53. package/src/server/responses/ws-upstream.ts +318 -0
  54. package/src/server/sse-frame-buffer.ts +292 -0
  55. package/src/server/ws-bridge.ts +17 -11
  56. package/src/types.ts +8 -0
  57. package/src/usage/log.ts +24 -0
  58. package/src/usage/summary.ts +152 -2
  59. package/vendor/ocx-notch/win32-x64/ocx-notch.exe +0 -0
  60. package/gui/dist/assets/index-BucjyD4I.js +0 -67
@@ -3,7 +3,6 @@ import { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type Resp
3
3
  import { formatPassthroughUpstreamError } from "./passthrough-error";
4
4
  import { describeUpstreamConnectFailure } from "./upstream-error";
5
5
  import {
6
- getConfigPath,
7
6
  multiAgentGuidanceEnabled,
8
7
  resolveEnvValue,
9
8
  } from "../../config";
@@ -42,6 +41,7 @@ import {
42
41
  getOAuthCredentialProjectId,
43
42
  getValidAccessTokenForAccount,
44
43
  getValidAccessTokenSnapshot,
44
+ publicOAuthAuthenticationErrorMessage,
45
45
  type OAuthAccessSnapshot,
46
46
  UnsupportedOAuthProviderError,
47
47
  } from "../../oauth";
@@ -60,7 +60,7 @@ import {
60
60
  import { buildWebSearchTool, planWebSearch, runWithWebSearch, shouldResolveOpenAiWebSearchSidecar } from "../../web-search";
61
61
  import { buildImageTool, buildVideoTool, planImageBridge, planVideoBridge, runWithImageBridge, clampImageMaxRounds, IMAGE_GEN_TOOL_NAME, VIDEO_GEN_TOOL_NAME } from "../../images";
62
62
  import { describeImagesInPlace, planVisionSidecar, shouldResolveOpenAiVisionSidecar, stripImagesInPlace } from "../../vision";
63
- import { createAdapterEventQueue, preflightAdapterEvents } from "../../adapters/run-turn-queue";
63
+ import { createAdapterEventQueue, preflightAdapterEvents, type AdapterEventQueue } from "../../adapters/run-turn-queue";
64
64
  import {
65
65
  applyCodexAuthContextToProvider,
66
66
  CodexAccountCooldownError,
@@ -78,9 +78,11 @@ import {
78
78
  stripCodexRuntimeProviderFields,
79
79
  type CodexAuthContext,
80
80
  } from "../../codex/auth-context";
81
+ import { codexAuthContextLogLabel } from "../../codex/account-label";
81
82
  import {
82
83
  computeQuotaCooldown,
83
84
  formatCodexProviderForLog,
85
+ getPoolAccountPlan,
84
86
  previewCodexAccountForRequest,
85
87
  recordCodexUpstreamOutcome,
86
88
  type CodexUpstreamOutcome,
@@ -161,7 +163,9 @@ import type { EffectiveSubagentRoster, SpawnAgentSurface } from "../../codex/cat
161
163
  import { buildToolBridgeMaps, collabSurface, injectDeveloperMessage, multiAgentGuidanceText } from "./collaboration";
162
164
  import { hasUnreadableEncryptedAgentTask, looksLikeBackendCiphertext, sanitizeEncryptedContentInPlace } from "./encrypted-payload";
163
165
  import { fetchWithHeaderTimeout, providerFetch, safeHostLabel } from "./fetch-helpers";
166
+ import { isCodexWsUpstreamResponse, type BunRuntimeGateInput } from "./ws-upstream";
164
167
  import { guardTerminalEventStream } from "./terminal-guard";
168
+ import { emptyCompletionRetryEnabled, guardEmptyCompletionEventStream } from "./empty-completion-guard";
165
169
 
166
170
  /**
167
171
  * Adapters whose continuation state must survive Codex's store:false requests.
@@ -178,7 +182,9 @@ export function sidecarOutcomeRecorder(
178
182
  return authCtx.kind === "pool" || authCtx.kind === "main-pool"
179
183
  ? outcome => recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
180
184
  threadId,
185
+ fixedAccount: authCtx.fixedAccount,
181
186
  probeLeaseId: authCtx.probeLeaseId,
187
+ probeQuotaScope: authCtx.probeQuotaScope,
182
188
  writerGeneration: authCtx.writerGeneration,
183
189
  })
184
190
  : undefined;
@@ -264,6 +270,7 @@ interface CodexPoolAccountRetryArgs {
264
270
  // needs the inbound scope or the retry could land on a different wire than the
265
271
  // first attempt.
266
272
  inboundWire?: InboundWire;
273
+ codexWsRuntimeIdentity?: BunRuntimeGateInput;
267
274
  translatorBudget: TranslatorBudget;
268
275
  };
269
276
  firstAuthCtx: Extract<CodexAuthContext, { kind: "pool" | "main-pool" }>;
@@ -326,6 +333,7 @@ async function retryCodexPoolOnAlternateAccount(
326
333
  req, config, route, parsed, logCtx, options, firstAuthCtx, firstResponse,
327
334
  outcomeStatus, upstream, connectMs, passthroughEstimate, stream,
328
335
  } = args;
336
+ if (firstAuthCtx.fixedAccount) return { kind: "no-alternate" };
329
337
  const inboundWire = options.inboundWire ?? "responses";
330
338
  let retryAuthCtx: CodexAuthContext | undefined;
331
339
  try {
@@ -353,6 +361,7 @@ async function retryCodexPoolOnAlternateAccount(
353
361
  firstAuthCtx.accountId,
354
362
  firstResponse.headers,
355
363
  firstAuthCtx.writerGeneration,
364
+ getPoolAccountPlan(config, firstAuthCtx.accountId),
356
365
  );
357
366
  }
358
367
  if (!shouldDeferCodexResetDerivedCooldown(firstResponse, options.deferCodexResetDerivedCooldown)) {
@@ -392,6 +401,13 @@ async function retryCodexPoolOnAlternateAccount(
392
401
  retryAuthCtx.accountId,
393
402
  config,
394
403
  );
404
+ logCtx.accountLogLabel = codexAuthContextLogLabel(retryAuthCtx, config);
405
+ sealRequestAttemptIdentity(
406
+ logCtx.activeAttempt,
407
+ logCtx.provider,
408
+ retryAdapter.name,
409
+ logCtx.accountLogLabel,
410
+ );
395
411
 
396
412
  noteAttemptSend(logCtx.activeAttempt, passthroughEstimate);
397
413
  try {
@@ -405,7 +421,7 @@ async function retryCodexPoolOnAlternateAccount(
405
421
  upstream.signal,
406
422
  connectMs,
407
423
  stream,
408
- providerFetch(route.provider),
424
+ providerFetch(route.provider, options.codexWsRuntimeIdentity),
409
425
  );
410
426
  return {
411
427
  kind: "retried",
@@ -440,6 +456,7 @@ export function codexForwardTerminalOutcomeRecorder(
440
456
  // prior soft-avoid so a healthy account isn't stuck avoided.
441
457
  recordCodexUpstreamOutcome(config, authCtx.accountId, 200, {
442
458
  threadId,
459
+ fixedAccount: authCtx.fixedAccount,
443
460
  modelId,
444
461
  probeLeaseId: codexProbeLeaseId(authCtx),
445
462
  probeQuotaScope: codexProbeQuotaScope(authCtx),
@@ -461,6 +478,7 @@ export function codexForwardTerminalOutcomeRecorder(
461
478
  : (httpStatusOverride ?? logCtx?.terminalHttpStatus ?? 502);
462
479
  recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
463
480
  threadId,
481
+ fixedAccount: authCtx.fixedAccount,
464
482
  modelId,
465
483
  probeLeaseId: codexProbeLeaseId(authCtx),
466
484
  probeQuotaScope: codexProbeQuotaScope(authCtx),
@@ -514,6 +532,8 @@ export interface ConsumedComboFailure {
514
532
 
515
533
 
516
534
  export interface HandleResponsesOptions {
535
+ /** Injectable only for transport capability tests; production uses the live Bun identity. */
536
+ codexWsRuntimeIdentity?: BunRuntimeGateInput;
517
537
  turnAdmissionLease?: AdmissionLease;
518
538
  forceEmptyResponseId?: boolean;
519
539
  abortSignal?: AbortSignal;
@@ -702,6 +722,15 @@ type ResponsesAuthResolution =
702
722
  | { ok: true; authCtx: CodexAuthContext; headers: Headers }
703
723
  | { ok: false; response: Response };
704
724
 
725
+ type AccountQualifiedRoute = RouteResult & {
726
+ codexAccountId?: string;
727
+ codexAccountNamespace?: string;
728
+ };
729
+
730
+ function accountQualifiedRoute(route: RouteResult): AccountQualifiedRoute {
731
+ return route as AccountQualifiedRoute;
732
+ }
733
+
705
734
  /**
706
735
  * Resolve Codex auth for a route. On unusable contexts, releases any probe lease
707
736
  * before returning the 401 (nothing reaches upstream).
@@ -713,10 +742,16 @@ async function resolveResponsesCodexAuth(
713
742
  options: HandleResponsesOptions,
714
743
  ): Promise<ResponsesAuthResolution> {
715
744
  try {
716
- if (route.codexAccountMode === "direct") validateForwardAdmissionCredential(req.headers, config);
745
+ const qualified = accountQualifiedRoute(route);
746
+ if (route.codexAccountMode === "direct" && qualified.codexAccountId === undefined) {
747
+ validateForwardAdmissionCredential(req.headers, config);
748
+ }
717
749
  let authCtx: CodexAuthContext;
718
750
  if (route.codexAccountMode) {
719
- authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode, { modelId: route.modelId });
751
+ authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode, {
752
+ accountId: qualified.codexAccountId,
753
+ modelId: route.modelId,
754
+ });
720
755
  options.onCodexAuthContextResolved?.(authCtx);
721
756
  } else {
722
757
  authCtx = { kind: "main", accountId: null };
@@ -736,7 +771,10 @@ async function resolveResponsesCodexAuth(
736
771
  };
737
772
  } catch (err) {
738
773
  if (err instanceof CodexAccountCooldownError) {
739
- return { ok: false, response: cooldownErrorResponse(err) };
774
+ return {
775
+ ok: false,
776
+ response: cooldownErrorResponse(err, Date.now(), accountQualifiedRoute(route).codexAccountNamespace),
777
+ };
740
778
  }
741
779
  if (err instanceof CodexThreadAffinityExpiredError) {
742
780
  return {
@@ -968,6 +1006,7 @@ export async function handleComboResponses(
968
1006
  attempt,
969
1007
  childLog.provider,
970
1008
  childLog.providerAdapter ?? attempt.adapter,
1009
+ childLog.accountLogLabel,
971
1010
  );
972
1011
  finishRequestAttempt(attempt, 499, Date.now() - started, childLog.usage);
973
1012
  (logCtx.attempts ??= []).push(attempt);
@@ -1022,6 +1061,7 @@ export async function handleComboResponses(
1022
1061
  attempt,
1023
1062
  childLog.provider,
1024
1063
  childLog.providerAdapter ?? attempt.adapter,
1064
+ childLog.accountLogLabel,
1025
1065
  );
1026
1066
  (logCtx.attempts ??= []).push(attempt);
1027
1067
  attemptRetained = true;
@@ -1066,6 +1106,7 @@ export async function handleComboResponses(
1066
1106
  attempt,
1067
1107
  childLog.provider,
1068
1108
  childLog.providerAdapter ?? attempt.adapter,
1109
+ childLog.accountLogLabel,
1069
1110
  );
1070
1111
  finishRequestAttempt(
1071
1112
  attempt,
@@ -1258,7 +1299,7 @@ async function handleResponsesInner(
1258
1299
  if (parsed._rawBody && typeof parsed._rawBody === "object") {
1259
1300
  (parsed._rawBody as Record<string, unknown>).reasoning = { effort: "low" };
1260
1301
  }
1261
- (logCtx as unknown as Record<string, unknown>).shadowCallRewrittenFrom = _sciOriginal;
1302
+ logCtx.shadowCallRewrittenFrom = _sciOriginal;
1262
1303
  // Helpers must not resume/append into the parent thread's Cursor conversation.
1263
1304
  parsed._cursorIsolateConversation = true;
1264
1305
  }
@@ -1358,6 +1399,9 @@ async function handleResponsesInner(
1358
1399
 
1359
1400
  route.provider = applyCodexAuthContextToProvider(route.provider, authCtx, route.codexAccountMode);
1360
1401
  logCtx.provider = formatCodexProviderForLog(route.providerName, codexLogAccountId(authCtx), config);
1402
+ const accountLogLabel = codexAuthContextLogLabel(authCtx, config);
1403
+ if (accountLogLabel) logCtx.accountLogLabel = accountLogLabel;
1404
+ else delete logCtx.accountLogLabel;
1361
1405
  // Prefer Codex pool account as the Cursor thread namespace when present. Cursor routes without
1362
1406
  // codexAccountMode still get a credential-derived scope inside the Cursor adapter.
1363
1407
  const identityScope = codexLogAccountId(authCtx);
@@ -1422,13 +1466,14 @@ async function handleResponsesInner(
1422
1466
  }
1423
1467
  } catch (err) {
1424
1468
  if (err instanceof UnsupportedOAuthProviderError) {
1469
+ const safeProviderName = redactSecretString(route.providerName);
1425
1470
  return formatErrorResponse(
1426
1471
  400,
1427
1472
  "invalid_request_error",
1428
- `${err.message}. Remove or reconfigure provider '${route.providerName}' in ${getConfigPath()}.`,
1473
+ `${redactSecretString(err.message)}. Remove or reconfigure provider '${safeProviderName}' in the OpenCodex configuration.`,
1429
1474
  );
1430
1475
  }
1431
- return formatErrorResponse(401, "authentication_error", err instanceof Error ? err.message : String(err));
1476
+ return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
1432
1477
  }
1433
1478
  }
1434
1479
  route.provider = resolveProviderTransport(
@@ -1440,7 +1485,7 @@ async function handleResponsesInner(
1440
1485
  const adapterProvider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire);
1441
1486
  const adapter = resolveAdapter(adapterProvider, config.cacheRetention);
1442
1487
  logCtx.providerAdapter = adapter.name;
1443
- sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name);
1488
+ sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name, logCtx.accountLogLabel);
1444
1489
  const isPassthrough = "passthrough" in adapter && !!adapter.passthrough;
1445
1490
 
1446
1491
  if (adapter.name === "kiro" && parsed.previousResponseId && !parsed._previousResponseInputExpanded) {
@@ -1585,6 +1630,7 @@ async function handleResponsesInner(
1585
1630
  if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
1586
1631
  recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
1587
1632
  threadId: req.headers.get("x-codex-parent-thread-id"),
1633
+ fixedAccount: authCtx.fixedAccount,
1588
1634
  modelId: route.modelId,
1589
1635
  probeLeaseId: codexProbeLeaseId(authCtx),
1590
1636
  probeQuotaScope: codexProbeQuotaScope(authCtx),
@@ -1607,7 +1653,7 @@ async function handleResponsesInner(
1607
1653
  method: request.method,
1608
1654
  headers: request.headers,
1609
1655
  body: request.body,
1610
- }, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
1656
+ }, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider, options.codexWsRuntimeIdentity));
1611
1657
  },
1612
1658
  { abortSignal: upstream.signal, label: safeHostLabel(request.url) },
1613
1659
  );
@@ -1617,7 +1663,7 @@ async function handleResponsesInner(
1617
1663
  request.releaseBodyObservation?.();
1618
1664
  }
1619
1665
 
1620
- if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
1666
+ if (usesCodexForwardPoolAuth(authCtx, route.provider) && !authCtx.fixedAccount) {
1621
1667
  let poolRetryOutcome: number | undefined;
1622
1668
  if (await shouldRetryCodexPoolAccountModel400(
1623
1669
  upstreamResponse,
@@ -1682,15 +1728,18 @@ async function handleResponsesInner(
1682
1728
  );
1683
1729
  const terminalBodyWillRecord = !!terminalRecorder && upstreamResponse.ok && isEventStream;
1684
1730
  // Capture quota from upstream response for multi-account tracking
1685
- if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
1686
- // primary was the 5h window; it now carries weekly data for GPT plans.
1687
- // Prefer primary when present, fall back to secondary for compatibility.
1731
+ if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
1732
+ // The upstream has shipped both primary=5h/secondary=weekly and
1733
+ // primary=weekly/secondary=monthly shapes. Header duration metadata is
1734
+ // parsed by applyAccountQuotaFromUpstreamHeaders, which preserves every
1735
+ // window it can identify instead of assuming a fixed slot meaning.
1688
1736
  const quotaMeta = codexQuotaOutcomeMeta(upstreamResponse);
1689
1737
  const { applyAccountQuotaFromUpstreamHeaders } = await import("../../codex/auth-api");
1690
1738
  applyAccountQuotaFromUpstreamHeaders(
1691
1739
  authCtx.accountId,
1692
1740
  upstreamResponse.headers,
1693
1741
  authCtx.writerGeneration,
1742
+ getPoolAccountPlan(config, authCtx.accountId),
1694
1743
  );
1695
1744
  if (terminalBodyWillRecord) {
1696
1745
  options.setTerminalOutcomeRecorder?.((status, httpStatusOverride) => {
@@ -1720,6 +1769,7 @@ async function handleResponsesInner(
1720
1769
  recordCodexUpstreamOutcome(config, authCtx.accountId, upstreamResponse.status, {
1721
1770
  ...quotaMeta,
1722
1771
  threadId: req.headers.get("x-codex-parent-thread-id"),
1772
+ fixedAccount: authCtx.fixedAccount,
1723
1773
  modelId: route.modelId,
1724
1774
  probeLeaseId: codexProbeLeaseId(authCtx),
1725
1775
  probeQuotaScope: codexProbeQuotaScope(authCtx),
@@ -1775,7 +1825,12 @@ async function handleResponsesInner(
1775
1825
  needsClientRewrite,
1776
1826
  config.streamMode ?? "auto",
1777
1827
  );
1778
- if (eagerPath?.useEagerRelay || win32EagerRewrite) {
1828
+ // A successful Codex WS upgrade is push-driven. Keep it out of tee(),
1829
+ // where an inspection branch can outrun a slow client without a bound.
1830
+ const forceCodexWsEagerRelay = isCodexWsUpstreamResponse(upstreamResponse);
1831
+ const inlineEagerRewrite = needsClientRewrite
1832
+ && (forceCodexWsEagerRelay || eagerPath?.useEagerRelay === true || win32EagerRewrite);
1833
+ if (forceCodexWsEagerRelay || eagerPath?.useEagerRelay || win32EagerRewrite) {
1779
1834
  const turn = trackActiveTurnLease(upstream, options.turnAdmissionLease);
1780
1835
  const reportNativeTerminal = recordTerminalOutcomes
1781
1836
  ? (status: ResponsesTerminalStatus, httpStatusOverride?: number) => {
@@ -1811,7 +1866,7 @@ async function handleResponsesInner(
1811
1866
  finishInspection: () => inspector.finish(),
1812
1867
  disposeInspection: () => inspector.dispose(),
1813
1868
  sawTerminal: () => inspector.reported(),
1814
- ...(win32EagerRewrite
1869
+ ...(inlineEagerRewrite
1815
1870
  ? { rewritePayload: composeSsePayloadRewrites(...payloadRewrites) }
1816
1871
  : {}),
1817
1872
  onSynthetic: kind => {
@@ -1822,12 +1877,13 @@ async function handleResponsesInner(
1822
1877
  } else {
1823
1878
  logCtx.transportPhase = "mid_stream";
1824
1879
  logCtx.terminalSource = "synthetic";
1880
+ if (logCtx.activeAttempt) logCtx.activeAttempt.streamAborted = true;
1825
1881
  reportNativeTerminal("failed", 502);
1826
1882
  }
1827
1883
  },
1828
1884
  onClientCancel: () => options.onNativePassthroughCancel?.(),
1829
1885
  onDone: () => turn.finish("inspection_settled"),
1830
- }, win32EagerRewrite ? { rewriteBudget: translatorBudget } : undefined);
1886
+ }, inlineEagerRewrite ? { rewriteBudget: translatorBudget } : undefined);
1831
1887
  // selectEagerPath admits only no-rewrite traffic on both eligible platforms;
1832
1888
  // win32 rewrite traffic reaches this relay too, but with the payload rewrite
1833
1889
  // applied inline — never via an image/item-id JS pull wrapper (#32111, #864).
@@ -2013,7 +2069,7 @@ async function handleResponsesInner(
2013
2069
  : clampImageMaxRounds(config.images?.videoMaxRounds ?? 2),
2014
2070
  connectTimeoutMs: config.connectTimeoutMs ?? 200_000,
2015
2071
  stallTimeoutSec: config.stallTimeoutSec,
2016
- fetchImpl: providerFetch(route.provider),
2072
+ fetchImpl: providerFetch(route.provider, options.codexWsRuntimeIdentity),
2017
2073
  onRequestBuilt: request => recordAdapterReasoning(logCtx, request),
2018
2074
  ...(vidPlan?.timeoutMs ? { videoTimeoutMs: vidPlan.timeoutMs } : {}),
2019
2075
  onUsage: usage => {
@@ -2124,22 +2180,29 @@ async function handleResponsesInner(
2124
2180
  return wsResponse;
2125
2181
  }
2126
2182
 
2183
+ const emptyCompletionGuardEnabled = emptyCompletionRetryEnabled(config)
2184
+ && !options.comboAttempt
2185
+ && !routedCompaction;
2186
+
2127
2187
  if (adapter.runTurn) {
2128
2188
  const runTurnAbort = new AbortController();
2129
2189
  linkAbortSignal(runTurnAbort, options.abortSignal);
2130
2190
  const queue = createAdapterEventQueue({
2131
2191
  onBacklogExceeded: () => runTurnAbort.abort(),
2132
2192
  });
2133
- const runTurn = async (): Promise<void> => {
2193
+ const runTurnAttempt = async (
2194
+ targetQueue: AdapterEventQueue,
2195
+ recovery?: AttemptRecoveryKind,
2196
+ ): Promise<void> => {
2134
2197
  try {
2135
- noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens);
2198
+ noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery);
2136
2199
  await adapter.runTurn?.(
2137
2200
  parsed,
2138
2201
  { headers: selectedForwardHeaders, abortSignal: runTurnAbort.signal, translatorBudget },
2139
- queue.push,
2202
+ targetQueue.push,
2140
2203
  );
2141
2204
  } catch (err) {
2142
- queue.push({
2205
+ targetQueue.push({
2143
2206
  type: "error",
2144
2207
  message: err instanceof Error ? err.message : String(err),
2145
2208
  });
@@ -2149,9 +2212,17 @@ async function handleResponsesInner(
2149
2212
  if (!logCtx.conversationId && parsed._cursorConversationId) {
2150
2213
  logCtx.conversationId = normalizeLogConversationId(parsed._cursorConversationId);
2151
2214
  }
2152
- queue.close();
2215
+ targetQueue.close();
2153
2216
  }
2154
2217
  };
2218
+ const runTurn = async (): Promise<void> => runTurnAttempt(queue);
2219
+ const runTurnRetrySource = (): AsyncIterable<AdapterEvent> => {
2220
+ const retryQueue = createAdapterEventQueue({
2221
+ onBacklogExceeded: () => runTurnAbort.abort(),
2222
+ });
2223
+ void runTurnAttempt(retryQueue, "empty-completion");
2224
+ return retryQueue.stream();
2225
+ };
2155
2226
 
2156
2227
  const { toolNsMap, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
2157
2228
  if (parsed.stream) {
@@ -2167,8 +2238,14 @@ async function handleResponsesInner(
2167
2238
  }
2168
2239
  eventSource = preflight.stream;
2169
2240
  }
2241
+ const guardedSource = emptyCompletionGuardEnabled
2242
+ ? guardEmptyCompletionEventStream({
2243
+ firstEvents: eventSource,
2244
+ continuation: runTurnRetrySource,
2245
+ })
2246
+ : eventSource;
2170
2247
  const sseStream = bridgeToResponsesSSE(
2171
- eventSource, parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
2248
+ guardedSource, parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
2172
2249
  () => {
2173
2250
  runTurnAbort.abort();
2174
2251
  queue.close();
@@ -2208,7 +2285,17 @@ async function handleResponsesInner(
2208
2285
  }
2209
2286
 
2210
2287
  await runTurn();
2211
- const events = await queue.collect();
2288
+ const firstAttemptEvents = await queue.collect();
2289
+ let events: AdapterEvent[];
2290
+ if (emptyCompletionGuardEnabled) {
2291
+ events = [];
2292
+ for await (const event of guardEmptyCompletionEventStream({
2293
+ firstEvents: (async function* () { yield* firstAttemptEvents; })(),
2294
+ continuation: runTurnRetrySource,
2295
+ })) events.push(event);
2296
+ } else {
2297
+ events = firstAttemptEvents;
2298
+ }
2212
2299
  if (options.comboAttempt) {
2213
2300
  const firstMeaningful = events.find(event => event.type !== "heartbeat");
2214
2301
  if (!firstMeaningful || firstMeaningful.type === "error") {
@@ -2257,6 +2344,14 @@ async function handleResponsesInner(
2257
2344
  ? request.usageLog.inputTokens
2258
2345
  : undefined;
2259
2346
  if (inputTokenEstimate !== undefined) logCtx.usageLogInputTokens = inputTokenEstimate;
2347
+ // Empty-completion recovery must resend the exact same serialized request. Keep a
2348
+ // same-target cache and invalidate it whenever credentials, adapter, parsed input,
2349
+ // or image tier changes.
2350
+ let sameTargetRequest: typeof request | undefined = request;
2351
+ let sameTargetParsed: OcxParsedRequest | undefined = parsed;
2352
+ let sameTargetToken = 0;
2353
+ let transportToken = 0;
2354
+ const invalidateSameTargetRequest = (): void => { transportToken += 1; };
2260
2355
  let upstreamResponse: Response;
2261
2356
  try {
2262
2357
  if (activeAdapter.fetchResponse) {
@@ -2274,7 +2369,7 @@ async function handleResponsesInner(
2274
2369
  method: request.method,
2275
2370
  headers: request.headers,
2276
2371
  body: request.body,
2277
- }, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
2372
+ }, recovery), upstream.signal, connectMs, parsed.stream, providerFetch(route.provider, options.codexWsRuntimeIdentity));
2278
2373
  },
2279
2374
  { abortSignal: upstream.signal, label: safeHostLabel(request.url) },
2280
2375
  );
@@ -2301,18 +2396,26 @@ async function handleResponsesInner(
2301
2396
  const rebuildAndRefetch = async (
2302
2397
  recovery: AttemptRecoveryKind,
2303
2398
  ): Promise<Response | { failed: Response }> => {
2304
- const retryRequest = await activeAdapter.buildRequest(parsed, {
2305
- headers: selectedForwardHeaders,
2306
- translatorBudget,
2307
- ...(imageTierBias > 0 ? { imageTierBias } : {}),
2308
- });
2309
- recordAdapterReasoning(logCtx, retryRequest);
2399
+ let retryRequest: typeof request;
2400
+ if (sameTargetRequest !== undefined && sameTargetParsed === parsed && sameTargetToken === transportToken) {
2401
+ retryRequest = sameTargetRequest;
2402
+ } else {
2403
+ retryRequest = await activeAdapter.buildRequest(parsed, {
2404
+ headers: selectedForwardHeaders,
2405
+ translatorBudget,
2406
+ ...(imageTierBias > 0 ? { imageTierBias } : {}),
2407
+ });
2408
+ recordAdapterReasoning(logCtx, retryRequest);
2409
+ sameTargetRequest = retryRequest;
2410
+ sameTargetParsed = parsed;
2411
+ sameTargetToken = transportToken;
2412
+ }
2310
2413
  const retryEstimate = typeof retryRequest.usageLog?.inputTokens === "number"
2311
2414
  ? retryRequest.usageLog.inputTokens
2312
2415
  : undefined;
2313
2416
  if (retryEstimate !== undefined) logCtx.usageLogInputTokens = retryEstimate;
2314
2417
  logCtx.providerAdapter = activeAdapter.name;
2315
- sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
2418
+ sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
2316
2419
  noteAttemptSend(logCtx.activeAttempt, retryEstimate, recovery);
2317
2420
  try {
2318
2421
  try {
@@ -2320,7 +2423,7 @@ async function handleResponsesInner(
2320
2423
  ? await activeAdapter.fetchResponse(retryRequest, { abortSignal: upstream.signal, timeoutMs: connectMs, stream: parsed.stream })
2321
2424
  : await fetchWithHeaderTimeout(retryRequest.url, {
2322
2425
  method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
2323
- }, upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
2426
+ }, upstream.signal, connectMs, parsed.stream, providerFetch(route.provider, options.codexWsRuntimeIdentity));
2324
2427
  } finally {
2325
2428
  retryRequest.releaseBodyObservation?.();
2326
2429
  }
@@ -2361,6 +2464,7 @@ async function handleResponsesInner(
2361
2464
  route.providerName === "github-copilot" ? getOAuthCredentialApiBaseUrl(route.providerName) : undefined,
2362
2465
  );
2363
2466
  route.provider = refreshedProvider;
2467
+ invalidateSameTargetRequest();
2364
2468
  activeAdapter = resolveAdapter(
2365
2469
  resolveWireProtocolOverride(route.providerName, route.modelId, refreshedProvider, inboundWire),
2366
2470
  config.cacheRetention,
@@ -2386,6 +2490,7 @@ async function handleResponsesInner(
2386
2490
  // until runtime cleanup (one per rotated key under a rate-limit storm).
2387
2491
  try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
2388
2492
  route.provider = rotated;
2493
+ invalidateSameTargetRequest();
2389
2494
  activeAdapter = resolveAdapter(
2390
2495
  resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
2391
2496
  config.cacheRetention,
@@ -2416,13 +2521,14 @@ async function handleResponsesInner(
2416
2521
  anthropicPoolAccountId = nextAccountId;
2417
2522
  anthropicPoolFailovers += 1;
2418
2523
  route.provider = { ...route.provider, apiKey: accessToken };
2524
+ invalidateSameTargetRequest();
2419
2525
  promoteAnthropicActiveAccount(nextAccountId);
2420
2526
  logCtx.provider = formatAnthropicProviderForLog("anthropic", nextAccountId, config);
2421
2527
  activeAdapter = resolveAdapter(
2422
2528
  resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
2423
2529
  config.cacheRetention,
2424
2530
  );
2425
- sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
2531
+ sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
2426
2532
  const result = await rebuildAndRefetch("anthropic-oauth-429");
2427
2533
  if ("failed" in result) return result.failed;
2428
2534
  upstreamResponse = result;
@@ -2440,6 +2546,7 @@ async function handleResponsesInner(
2440
2546
  })) {
2441
2547
  imageRetryAttempted = true;
2442
2548
  imageTierBias = 1;
2549
+ invalidateSameTargetRequest();
2443
2550
  try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
2444
2551
  const result = await rebuildAndRefetch("image-413");
2445
2552
  if ("failed" in result) return result.failed;
@@ -2487,25 +2594,44 @@ async function handleResponsesInner(
2487
2594
  // one internal continuation pass. A continuation failure becomes an in-stream adapter error so
2488
2595
  // the client never sees a second hidden HTTP response or an unbounded retry loop.
2489
2596
  const terminalGuardEnabled = activeAdapter.name === "anthropic" && !options.comboAttempt && !routedCompaction;
2490
- const fetchTerminalGuardContinuation = async function* (nextParsed: OcxParsedRequest): AsyncGenerator<AdapterEvent> {
2597
+ const fetchTerminalGuardContinuation = async function* (
2598
+ nextParsed: OcxParsedRequest,
2599
+ initialRecoveryKind?: AttemptRecoveryKind,
2600
+ ): AsyncGenerator<AdapterEvent> {
2491
2601
  let imageTierBias = 0;
2492
2602
  let response: Response | undefined;
2603
+ let nextRecoveryKind = initialRecoveryKind;
2493
2604
  while (true) {
2605
+ const recoveryKind = nextRecoveryKind;
2606
+ nextRecoveryKind = undefined;
2494
2607
  try {
2495
- const continuationRequest = await activeAdapter.buildRequest(nextParsed, {
2496
- headers: selectedForwardHeaders,
2497
- translatorBudget,
2498
- ...(imageTierBias > 0 ? { imageTierBias } : {}),
2499
- });
2500
- recordAdapterReasoning(logCtx, continuationRequest);
2501
- const continuationEstimate = typeof continuationRequest.usageLog?.inputTokens === "number"
2502
- ? continuationRequest.usageLog.inputTokens
2608
+ let continuationRequest: typeof request;
2609
+ if (
2610
+ sameTargetRequest !== undefined
2611
+ && sameTargetParsed === nextParsed
2612
+ && sameTargetToken === transportToken
2613
+ ) {
2614
+ continuationRequest = sameTargetRequest;
2615
+ } else {
2616
+ continuationRequest = await activeAdapter.buildRequest(nextParsed, {
2617
+ headers: selectedForwardHeaders,
2618
+ translatorBudget,
2619
+ ...(imageTierBias > 0 ? { imageTierBias } : {}),
2620
+ });
2621
+ recordAdapterReasoning(logCtx, continuationRequest);
2622
+ sameTargetRequest = continuationRequest;
2623
+ sameTargetParsed = nextParsed;
2624
+ sameTargetToken = transportToken;
2625
+ }
2626
+ const builtContinuationRequest = continuationRequest;
2627
+ const continuationEstimate = typeof builtContinuationRequest.usageLog?.inputTokens === "number"
2628
+ ? builtContinuationRequest.usageLog.inputTokens
2503
2629
  : undefined;
2504
2630
  if (continuationEstimate !== undefined) logCtx.usageLogInputTokens = continuationEstimate;
2505
2631
  try {
2506
2632
  if (activeAdapter.fetchResponse) {
2507
- noteAttemptSend(logCtx.activeAttempt, continuationEstimate);
2508
- response = await activeAdapter.fetchResponse(continuationRequest, {
2633
+ noteAttemptSend(logCtx.activeAttempt, continuationEstimate, recoveryKind);
2634
+ response = await activeAdapter.fetchResponse(builtContinuationRequest, {
2509
2635
  abortSignal: upstream.signal,
2510
2636
  timeoutMs: connectMs,
2511
2637
  stream: nextParsed.stream,
@@ -2513,25 +2639,25 @@ async function handleResponsesInner(
2513
2639
  } else {
2514
2640
  response = await fetchWithResetRetry(
2515
2641
  recovery => {
2516
- noteAttemptSend(logCtx.activeAttempt, continuationEstimate, recovery);
2642
+ noteAttemptSend(logCtx.activeAttempt, continuationEstimate, recovery ?? recoveryKind);
2517
2643
  return fetchWithHeaderTimeout(
2518
- continuationRequest.url,
2644
+ builtContinuationRequest.url,
2519
2645
  applyUpstreamRecoveryInit({
2520
- method: continuationRequest.method,
2521
- headers: continuationRequest.headers,
2522
- body: continuationRequest.body,
2646
+ method: builtContinuationRequest.method,
2647
+ headers: builtContinuationRequest.headers,
2648
+ body: builtContinuationRequest.body,
2523
2649
  }, recovery),
2524
2650
  upstream.signal,
2525
2651
  connectMs,
2526
2652
  nextParsed.stream,
2527
- providerFetch(route.provider),
2653
+ providerFetch(route.provider, options.codexWsRuntimeIdentity),
2528
2654
  );
2529
2655
  },
2530
- { abortSignal: upstream.signal, label: safeHostLabel(continuationRequest.url) },
2656
+ { abortSignal: upstream.signal, label: safeHostLabel(builtContinuationRequest.url) },
2531
2657
  );
2532
2658
  }
2533
2659
  } finally {
2534
- continuationRequest.releaseBodyObservation?.();
2660
+ builtContinuationRequest.releaseBodyObservation?.();
2535
2661
  }
2536
2662
  } catch (error) {
2537
2663
  if (options.abortSignal?.aborted) {
@@ -2552,6 +2678,7 @@ async function handleResponsesInner(
2552
2678
  if (rotated) {
2553
2679
  try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
2554
2680
  route.provider = rotated;
2681
+ invalidateSameTargetRequest();
2555
2682
  activeAdapter = resolveAdapter(
2556
2683
  resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
2557
2684
  config.cacheRetention,
@@ -2578,13 +2705,14 @@ async function handleResponsesInner(
2578
2705
  anthropicPoolAccountId = nextAccountId;
2579
2706
  anthropicPoolFailovers += 1;
2580
2707
  route.provider = { ...route.provider, apiKey: accessToken };
2708
+ invalidateSameTargetRequest();
2581
2709
  promoteAnthropicActiveAccount(nextAccountId);
2582
2710
  logCtx.provider = formatAnthropicProviderForLog("anthropic", nextAccountId, config);
2583
2711
  activeAdapter = resolveAdapter(
2584
2712
  resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
2585
2713
  config.cacheRetention,
2586
2714
  );
2587
- sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
2715
+ sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
2588
2716
  continue;
2589
2717
  } catch {
2590
2718
  // fall through to emit continuation error below
@@ -2598,6 +2726,7 @@ async function handleResponsesInner(
2598
2726
  alreadyAttempted: imageTierBias > 0,
2599
2727
  })) {
2600
2728
  imageTierBias = 1;
2729
+ invalidateSameTargetRequest();
2601
2730
  try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
2602
2731
  continue;
2603
2732
  }
@@ -2639,6 +2768,19 @@ async function handleResponsesInner(
2639
2768
  }
2640
2769
  };
2641
2770
 
2771
+ const fetchGuardedEmptyCompletionRetry = (): AsyncIterable<AdapterEvent> => {
2772
+ const retryEvents = fetchTerminalGuardContinuation(parsed, "empty-completion");
2773
+ return terminalGuardEnabled
2774
+ ? guardTerminalEventStream({
2775
+ parsed,
2776
+ firstEvents: retryEvents,
2777
+ adapterName: activeAdapter.name,
2778
+ maxAutoContinuations: 1,
2779
+ continuation: fetchTerminalGuardContinuation,
2780
+ })
2781
+ : retryEvents;
2782
+ };
2783
+
2642
2784
  if (parsed.stream) {
2643
2785
  const initialEventStream = activeAdapter.parseStream(upstreamResponse, translatorBudget);
2644
2786
  const eventStream = terminalGuardEnabled
@@ -2650,9 +2792,15 @@ async function handleResponsesInner(
2650
2792
  continuation: fetchTerminalGuardContinuation,
2651
2793
  })
2652
2794
  : initialEventStream;
2795
+ const guardedEventStream = emptyCompletionGuardEnabled
2796
+ ? guardEmptyCompletionEventStream({
2797
+ firstEvents: eventStream,
2798
+ continuation: fetchGuardedEmptyCompletionRetry,
2799
+ })
2800
+ : eventStream;
2653
2801
  const { toolNsMap, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
2654
2802
  const sseStream = bridgeToResponsesSSE(
2655
- eventStream, parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
2803
+ guardedEventStream, parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
2656
2804
  () => upstream.abort(), 2_000,
2657
2805
  {
2658
2806
  translatorBudget,
@@ -2694,17 +2842,27 @@ async function handleResponsesInner(
2694
2842
  let events: AdapterEvent[];
2695
2843
  try {
2696
2844
  const initialEvents = await activeAdapter.parseResponse(upstreamResponse, translatorBudget);
2845
+ let terminalGuardedEvents: AdapterEvent[];
2697
2846
  if (terminalGuardEnabled) {
2698
- events = [];
2847
+ terminalGuardedEvents = [];
2699
2848
  for await (const event of guardTerminalEventStream({
2700
2849
  parsed,
2701
2850
  firstEvents: (async function* () { yield* initialEvents; })(),
2702
2851
  adapterName: activeAdapter.name,
2703
2852
  maxAutoContinuations: 1,
2704
2853
  continuation: fetchTerminalGuardContinuation,
2854
+ })) terminalGuardedEvents.push(event);
2855
+ } else {
2856
+ terminalGuardedEvents = initialEvents;
2857
+ }
2858
+ if (emptyCompletionGuardEnabled) {
2859
+ events = [];
2860
+ for await (const event of guardEmptyCompletionEventStream({
2861
+ firstEvents: (async function* () { yield* terminalGuardedEvents; })(),
2862
+ continuation: fetchGuardedEmptyCompletionRetry,
2705
2863
  })) events.push(event);
2706
2864
  } else {
2707
- events = initialEvents;
2865
+ events = terminalGuardedEvents;
2708
2866
  }
2709
2867
  } finally {
2710
2868
  cleanupUpstreamAbort();