@bitkyc08/opencodex 2.28.0 → 2.29.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 (90) hide show
  1. package/README.md +9 -1
  2. package/gui/dist/assets/index-BNESwCzn.js +102 -0
  3. package/gui/dist/assets/index-CH7ncHCC.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +1 -1
  6. package/src/adapters/base.ts +3 -1
  7. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  8. package/src/adapters/cursor/discovery.ts +25 -0
  9. package/src/adapters/cursor/live-transport.ts +19 -2
  10. package/src/adapters/cursor/native-exec.ts +47 -2
  11. package/src/adapters/cursor/protobuf-request.ts +80 -23
  12. package/src/adapters/cursor/request-builder.ts +119 -5
  13. package/src/adapters/cursor/transport.ts +5 -0
  14. package/src/adapters/cursor/types.ts +13 -0
  15. package/src/adapters/cursor.ts +109 -3
  16. package/src/adapters/google-antigravity-replay.ts +31 -4
  17. package/src/adapters/google.ts +323 -38
  18. package/src/adapters/openai-chat.ts +19 -0
  19. package/src/adapters/openai-responses.ts +218 -37
  20. package/src/bridge.ts +38 -20
  21. package/src/claude/desktop-3p.ts +15 -6
  22. package/src/cli/agent.ts +44 -1
  23. package/src/cli/claude-agent-startup-sync.ts +73 -0
  24. package/src/cli/dispatch.ts +24 -1
  25. package/src/cli/ensure-desired-integrations.ts +152 -0
  26. package/src/cli/help.ts +1 -1
  27. package/src/cli/index.ts +40 -38
  28. package/src/cli/integrations.ts +1 -1
  29. package/src/cli/registry.ts +2 -2
  30. package/src/clients/config-export.ts +119 -20
  31. package/src/codex/affinity-debug.ts +162 -0
  32. package/src/codex/inject.ts +46 -14
  33. package/src/codex/journal.ts +22 -8
  34. package/src/config.ts +1 -0
  35. package/src/generated/compatibility-version.json +134 -66
  36. package/src/integrations/mutation-flight.ts +71 -0
  37. package/src/integrations/owned-refresh.ts +74 -0
  38. package/src/integrations/registry.ts +25 -2
  39. package/src/integrations/writer.ts +32 -1
  40. package/src/lab/public/signature.ts +25 -1
  41. package/src/lab/subject/behavior-fingerprint.ts +1 -1
  42. package/src/lib/redact.ts +2 -2
  43. package/src/oauth/log.ts +3 -1
  44. package/src/providers/derive.ts +9 -0
  45. package/src/providers/fastwire.ts +24 -18
  46. package/src/providers/openai-tiers.ts +60 -1
  47. package/src/providers/registry.ts +14 -7
  48. package/src/providers/xai-responses-opt-in.ts +15 -0
  49. package/src/responses/compaction.ts +18 -0
  50. package/src/responses/custom-tool-compat.ts +70 -5
  51. package/src/responses/namespace-tool-compat.ts +356 -0
  52. package/src/responses/parser.ts +2 -2
  53. package/src/responses/provider-continuation.ts +98 -0
  54. package/src/responses/reasoning-replay-cache.ts +125 -7
  55. package/src/responses/spill-store.ts +6 -1
  56. package/src/responses/state.ts +11 -0
  57. package/src/router.ts +14 -0
  58. package/src/routing/compatibility/behavior.ts +1 -0
  59. package/src/server/auth-cors.ts +4 -0
  60. package/src/server/management/agent-settings-routes.ts +57 -9
  61. package/src/server/management/config-routes.ts +134 -15
  62. package/src/server/management/integration-routes.ts +8 -55
  63. package/src/server/management/model-routes.ts +23 -1
  64. package/src/server/management/provider-routes.ts +22 -0
  65. package/src/server/management/vision-sidecar-options.ts +18 -11
  66. package/src/server/management/web-search-sidecar-options.ts +120 -0
  67. package/src/server/responses/core.ts +588 -78
  68. package/src/server/responses/responses-field-backfill.ts +7 -11
  69. package/src/server/responses/terminal-guard.ts +22 -11
  70. package/src/server/responses-custom-tool-repair.ts +3 -1
  71. package/src/server/responses-reasoning-summary-rewrite.ts +7 -0
  72. package/src/server/responses-tool-search-repair.ts +64 -14
  73. package/src/sidecar/auth.ts +92 -0
  74. package/src/sidecar/candidates.ts +83 -0
  75. package/src/types/config.ts +26 -5
  76. package/src/types/provider.ts +18 -0
  77. package/src/types/request.ts +26 -0
  78. package/src/types.ts +1 -0
  79. package/src/usage/log.ts +2 -0
  80. package/src/vision/index.ts +8 -9
  81. package/src/web-search/backends.ts +108 -0
  82. package/src/web-search/exa-executor.ts +88 -0
  83. package/src/web-search/gemini-executor.ts +141 -0
  84. package/src/web-search/index.ts +139 -12
  85. package/src/web-search/loop.ts +45 -5
  86. package/src/web-search/parse.ts +34 -23
  87. package/src/web-search/sources.ts +60 -0
  88. package/src/web-search/xai-executor.ts +219 -0
  89. package/gui/dist/assets/index-D2sP-biU.js +0 -102
  90. package/gui/dist/assets/index-DQsMZzI5.css +0 -1
@@ -1,4 +1,5 @@
1
1
  import type { Server } from "bun";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type ResponsesTerminalStatus } from "../../bridge";
3
4
  import { formatPassthroughUpstreamError } from "./passthrough-error";
4
5
  import {
@@ -15,17 +16,20 @@ import {
15
16
  import { parseRequest } from "../../responses/parser";
16
17
  import {
17
18
  bindReasoningReplayScope,
19
+ commitReasoningReplayServingIdentity,
18
20
  reasoningReplayCodexCredentialIdentity,
19
21
  reasoningReplayDestinationIdentity,
20
22
  durableReplayDestinationIdentity,
21
23
  durableReplayCredentialIdentity,
22
24
  reasoningReplayKeyCredentialIdentity,
23
25
  reasoningReplayOAuthCredentialIdentity,
26
+ reasoningReplayServingIdentityChanged,
24
27
  } from "../../responses/reasoning-replay-cache";
25
28
  import { awaitThoughtSignatureDurability, thoughtSignatureReplaySalt } from "../../responses/thought-signature-replay";
26
29
  import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
27
30
  import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
28
31
  import {
32
+ copyPreviousResponseReplayProvenance,
29
33
  expandPreviousResponseInput,
30
34
  markBodyNonPersistable,
31
35
  previousResponseProviderState,
@@ -33,6 +37,13 @@ import {
33
37
  previousResponseScopeMismatch,
34
38
  rememberResponseState,
35
39
  } from "../../responses/state";
40
+ import {
41
+ isValidProviderContinuationOwner,
42
+ mergeProviderContinuationPayload,
43
+ providerContinuationOwnerFromReplayIdentity,
44
+ providerContinuationRouteScope,
45
+ sameProviderContinuationOwner,
46
+ } from "../../responses/provider-continuation";
36
47
  import {
37
48
  comboRouteDecisionTrace,
38
49
  NoEligiblePolicyCandidateError,
@@ -62,7 +73,17 @@ import { injectionDebugLog } from "../../lib/injection-debug-log";
62
73
  import { resolveClientRetryAfter } from "../../lib/retry-after";
63
74
  import { enrichOpenCodeZenRateLimitMessage } from "../../providers/opencode-zen-rate-limit";
64
75
  import { modelInList, namespacedToolName } from "../../types";
65
- import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig, OcxProviderContinuationState, OcxUsage, TierDecision } from "../../types";
76
+ import type {
77
+ AdapterEvent,
78
+ OcxConfig,
79
+ OcxParsedRequest,
80
+ OcxProviderConfig,
81
+ OcxProviderContinuationOwner,
82
+ OcxProviderContinuationState,
83
+ OcxReasoningReplayIdentity,
84
+ OcxUsage,
85
+ TierDecision,
86
+ } from "../../types";
66
87
  import {
67
88
  forceRefreshOAuthAccessSnapshot,
68
89
  getOAuthCredentialApiBaseUrl,
@@ -115,6 +136,7 @@ import {
115
136
  resolveCodexModelEntitlements,
116
137
  } from "../../codex/model-entitlements";
117
138
  import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS } from "../../codex/catalog/native-models";
139
+ import { captureCodexAffinityDiagnostic } from "../../codex/affinity-debug";
118
140
  import {
119
141
  computeQuotaCooldown,
120
142
  formatCodexProviderForLog,
@@ -275,6 +297,12 @@ import { restoreRoutedCustomCallsInJson } from "../../responses/custom-tool-comp
275
297
  import { createRoutedCustomToolRestoreBlockRewrite } from "../responses-custom-tool-repair";
276
298
  import { restoreRoutedToolSearchCallsInJson } from "../../responses/tool-search-compat";
277
299
  import { createRoutedToolSearchRestoreBlockRewrite } from "../responses-tool-search-repair";
300
+ import {
301
+ createRoutedNamespaceCallRestoreRewrite,
302
+ NamespaceToolCollisionError,
303
+ restoreRoutedNamespaceCallsInJson,
304
+ type RoutedNamespaceToolAliases,
305
+ } from "../../responses/namespace-tool-compat";
278
306
  import {
279
307
  collectDeclaredWireToolNames,
280
308
  createUndeclaredToolCallGuardBlockRewrite,
@@ -325,6 +353,62 @@ export function codexLogAccountId(authCtx: CodexAuthContext): string | null {
325
353
  return authCtx.kind === "pool" || authCtx.kind === "main-pool" ? authCtx.accountId : null;
326
354
  }
327
355
 
356
+ type ContinuationOwnerRead =
357
+ | { kind: "missing" }
358
+ | { kind: "invalid" }
359
+ | { kind: "valid"; owner: OcxProviderContinuationOwner };
360
+
361
+ function readProviderContinuationOwner(
362
+ state: OcxProviderContinuationState | undefined,
363
+ ): ContinuationOwnerRead {
364
+ if (!state || state.__ocxOwner === undefined) return { kind: "missing" };
365
+ const owner = state.__ocxOwner;
366
+ if (!isValidProviderContinuationOwner(owner)) return { kind: "invalid" };
367
+ return { kind: "valid", owner: { ...owner } };
368
+ }
369
+
370
+ function providerContinuationPayload(
371
+ state: OcxProviderContinuationState | undefined,
372
+ ): OcxProviderContinuationState | undefined {
373
+ if (!state) return undefined;
374
+ const cloned = structuredClone(state);
375
+ delete cloned.__ocxOwner;
376
+ return Object.keys(cloned).length > 0 ? cloned : undefined;
377
+ }
378
+
379
+ function bindProviderContinuationForRoute(
380
+ parsed: OcxParsedRequest,
381
+ currentOwner: OcxProviderContinuationOwner | undefined,
382
+ ): void {
383
+ const candidate = parsed._providerContinuationCandidate;
384
+ const storedOwner = readProviderContinuationOwner(candidate);
385
+ const mayRestore = storedOwner.kind === "valid"
386
+ && !!currentOwner
387
+ && sameProviderContinuationOwner(storedOwner.owner, currentOwner);
388
+ const restored = mayRestore ? providerContinuationPayload(candidate) : undefined;
389
+ if (restored) parsed._providerContinuation = restored;
390
+ else delete parsed._providerContinuation;
391
+ const cursorConversationId = restored?.cursor?.conversationId;
392
+ if (cursorConversationId) parsed._cursorConversationId = cursorConversationId;
393
+ else delete parsed._cursorConversationId;
394
+ if (currentOwner) parsed._providerContinuationOwner = { ...currentOwner };
395
+ else delete parsed._providerContinuationOwner;
396
+ }
397
+
398
+ function providerContinuationDestinationIdentity(
399
+ parsed: OcxParsedRequest,
400
+ provider: OcxProviderConfig,
401
+ ): string | undefined {
402
+ const kiroContext = parsed._kiroAuthContext;
403
+ return reasoningReplayDestinationIdentity(JSON.stringify([
404
+ provider.baseUrl.trim().replace(/\/+$/, ""),
405
+ provider.responsesPath ?? "",
406
+ kiroContext?.profileArn ?? "",
407
+ kiroContext?.apiRegion ?? "",
408
+ kiroContext?.ssoRegion ?? "",
409
+ ]));
410
+ }
411
+
328
412
  function bindRouteReasoningReplayScope(args: {
329
413
  parsed: OcxParsedRequest;
330
414
  providerName: string;
@@ -393,20 +477,202 @@ function bindRouteReasoningReplayScope(args: {
393
477
  );
394
478
  }
395
479
  const providerDestinationIdentity = reasoningReplayDestinationIdentity(provider.baseUrl);
480
+ const replayIdentity: OcxReasoningReplayIdentity | undefined = credentialIdentity && providerDestinationIdentity
481
+ ? {
482
+ providerName,
483
+ providerDestinationIdentity,
484
+ providerDestinationDurableIdentity: durableReplayDestinationIdentity(provider.baseUrl),
485
+ adapterName,
486
+ modelId: parsed.modelId,
487
+ credentialIdentity,
488
+ ...(credentialDurableIdentity ? { credentialDurableIdentity } : {}),
489
+ }
490
+ : undefined;
491
+ const continuationDestinationIdentity = providerContinuationDestinationIdentity(parsed, provider);
492
+ const continuationOwner = providerContinuationOwnerFromReplayIdentity(
493
+ replayIdentity && continuationDestinationIdentity
494
+ ? { ...replayIdentity, providerDestinationIdentity: continuationDestinationIdentity }
495
+ : undefined,
496
+ );
497
+ if (adapterName === "cursor") {
498
+ // The final route owner is authoritative for Cursor and supersedes the account-derived
499
+ // seed assigned before route binding. A Cursor conversation must be scoped to the exact
500
+ // provider/destination/adapter/model/credential that serves it.
501
+ if (continuationOwner) parsed._cursorIdentityScope = providerContinuationRouteScope(continuationOwner);
502
+ else if (!parsed._cursorIdentityScope?.startsWith("cursor-unowned:")) {
503
+ // Prevent the adapter's token-only fallback from recreating a provider-private id after the
504
+ // route owner failed closed. The sentinel is per parsed request and contains no credential.
505
+ parsed._cursorIdentityScope = `cursor-unowned:${randomUUID()}`;
506
+ }
507
+ }
396
508
  bindReasoningReplayScope(
397
509
  parsed._reasoningReplayScope,
398
- credentialIdentity && providerDestinationIdentity
399
- ? {
400
- providerName,
401
- providerDestinationIdentity,
402
- providerDestinationDurableIdentity: durableReplayDestinationIdentity(provider.baseUrl),
403
- adapterName,
404
- modelId: parsed.modelId,
405
- credentialIdentity,
406
- ...(credentialDurableIdentity ? { credentialDurableIdentity } : {}),
407
- }
408
- : undefined,
510
+ replayIdentity,
409
511
  );
512
+ // Keep this sticky for the whole outbound request: a later auth/key rebind may compare equal
513
+ // after the first mismatch, but it cannot make history minted by the prior route decodable.
514
+ if (reasoningReplayServingIdentityChanged(parsed._reasoningReplayScope)) {
515
+ parsed._stripReasoningEncryptedContent = true;
516
+ }
517
+ bindProviderContinuationForRoute(parsed, continuationOwner);
518
+ }
519
+
520
+ function adapterResponseReachedServingTerminal(
521
+ events: readonly AdapterEvent[],
522
+ response: Readonly<Record<string, unknown>>,
523
+ ): boolean {
524
+ return (response.status === "completed" || response.status === "incomplete")
525
+ && events.some(event => event.type === "done" || event.type === "incomplete");
526
+ }
527
+
528
+ const OPAQUE_RESPONSES_INPUT_TYPES = new Set([
529
+ "reasoning",
530
+ "compaction",
531
+ "compaction_summary",
532
+ "context_compaction",
533
+ ]);
534
+
535
+ function outboundResponsesBodyCarriesOpaqueBlob(bodyText: string | undefined): boolean {
536
+ if (!bodyText) return false;
537
+ try {
538
+ const body = JSON.parse(bodyText) as unknown;
539
+ if (!body || typeof body !== "object" || Array.isArray(body)) return false;
540
+ const input = (body as { input?: unknown }).input;
541
+ if (!Array.isArray(input)) return false;
542
+ return input.some(item => {
543
+ if (!item || typeof item !== "object" || Array.isArray(item)) return false;
544
+ const candidate = item as { type?: unknown; encrypted_content?: unknown };
545
+ return typeof candidate.type === "string"
546
+ && OPAQUE_RESPONSES_INPUT_TYPES.has(candidate.type)
547
+ && typeof candidate.encrypted_content === "string"
548
+ && candidate.encrypted_content.length > 0;
549
+ });
550
+ } catch {
551
+ return false;
552
+ }
553
+ }
554
+
555
+ function isSelfIdentifiedOpaqueBlobRejection(bodyText: string): boolean {
556
+ try {
557
+ const payload = JSON.parse(bodyText) as unknown;
558
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
559
+ const record = payload as { code?: unknown; error?: unknown };
560
+
561
+ if (record.error && typeof record.error === "object" && !Array.isArray(record.error)) {
562
+ const error = record.error as { type?: unknown; code?: unknown; message?: unknown };
563
+ if (error.type === "invalid_request_error") {
564
+ if (error.code === "invalid_encrypted_content") return true;
565
+ if (
566
+ (error.code === null || error.code === undefined)
567
+ && typeof error.message === "string"
568
+ && error.message.startsWith("The encrypted content ")
569
+ && error.message.endsWith(
570
+ " could not be verified. Reason: Encrypted content could not be decrypted or parsed.",
571
+ )
572
+ ) return true;
573
+ }
574
+ }
575
+
576
+ if (record.code !== "invalid-argument" || typeof record.error !== "string") return false;
577
+ return record.error.startsWith("Could not decode the compaction blob")
578
+ || record.error.startsWith("Could not decrypt the provided encrypted_content");
579
+ } catch {
580
+ return false;
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Whether an upstream Responses 4xx authoritatively rejected opaque replay state.
586
+ *
587
+ * The outbound-body check is intentional: the inbound transcript may contain a proxy envelope or
588
+ * compaction blob that the adapter already lowered, in which case a replay would be byte-identical.
589
+ * OpenAI usually exposes a dedicated nested code; ChatGPT also emits one exact code-less
590
+ * unverifiable-ciphertext message. xAI's code is generic, so its two concrete decoder error
591
+ * identities are also required. Unrelated error prose must never gain a hidden resend.
592
+ */
593
+ export function shouldAttemptOpaqueBlobRecovery(args: {
594
+ status: number;
595
+ adapterName: string;
596
+ outboundBody?: string;
597
+ errorBody: string;
598
+ alreadyAttempted: boolean;
599
+ }): boolean {
600
+ return args.status >= 400
601
+ && args.status < 500
602
+ && args.adapterName === "openai-responses"
603
+ && !args.alreadyAttempted
604
+ && outboundResponsesBodyCarriesOpaqueBlob(args.outboundBody)
605
+ && isSelfIdentifiedOpaqueBlobRejection(args.errorBody);
606
+ }
607
+
608
+ async function opaqueBlobRejectionBodyForRecovery(
609
+ response: Response,
610
+ outboundBody: string | undefined,
611
+ adapterName: string,
612
+ alreadyAttempted: boolean,
613
+ signal: AbortSignal,
614
+ ): Promise<string | undefined> {
615
+ if (
616
+ response.status < 400
617
+ || response.status >= 500
618
+ || adapterName !== "openai-responses"
619
+ || alreadyAttempted
620
+ || !outboundResponsesBodyCarriesOpaqueBlob(outboundBody)
621
+ ) return undefined;
622
+ try {
623
+ const body = await readBoundedResponseBody(response.clone(), { signal });
624
+ return body.displaySafe && !body.truncated ? body.text : undefined;
625
+ } catch {
626
+ return undefined;
627
+ }
628
+ }
629
+
630
+ function prepareOpaqueBlobRecovery(parsed: OcxParsedRequest): void {
631
+ parsed._stripReasoningEncryptedContent = true;
632
+ }
633
+
634
+ type OpaqueBlobRecoveryGuard = { attempted: boolean };
635
+
636
+ type OpaqueBlobRecoveryResult =
637
+ | { kind: "skipped" }
638
+ | { kind: "recovered"; response: Response }
639
+ | { kind: "failed"; response: Response };
640
+
641
+ async function attemptOpaqueBlobRecovery(
642
+ args: {
643
+ response: Response;
644
+ outboundBody?: string;
645
+ adapterName: string;
646
+ parsed: OcxParsedRequest;
647
+ guard: OpaqueBlobRecoveryGuard;
648
+ signal: AbortSignal;
649
+ },
650
+ rebuild: (kind: AttemptRecoveryKind) => Promise<Response | { failed: Response }>,
651
+ ): Promise<OpaqueBlobRecoveryResult> {
652
+ const errorBody = await opaqueBlobRejectionBodyForRecovery(
653
+ args.response,
654
+ args.outboundBody,
655
+ args.adapterName,
656
+ args.guard.attempted,
657
+ args.signal,
658
+ );
659
+ if (errorBody === undefined || !shouldAttemptOpaqueBlobRecovery({
660
+ status: args.response.status,
661
+ adapterName: args.adapterName,
662
+ outboundBody: args.outboundBody,
663
+ errorBody,
664
+ alreadyAttempted: args.guard.attempted,
665
+ })) {
666
+ return { kind: "skipped" };
667
+ }
668
+
669
+ args.guard.attempted = true;
670
+ prepareOpaqueBlobRecovery(args.parsed);
671
+ try { void args.response.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
672
+ const result = await rebuild("opaque-blob-rejection");
673
+ return "failed" in result
674
+ ? { kind: "failed", response: result.failed }
675
+ : { kind: "recovered", response: result };
410
676
  }
411
677
 
412
678
  function nonEmptyProviderApiKey(provider: OcxProviderConfig): string | undefined {
@@ -521,6 +787,11 @@ interface CodexPoolAccountRetryArgs {
521
787
  connectMs: number;
522
788
  passthroughEstimate?: number;
523
789
  stream: boolean;
790
+ onResponse?: (
791
+ response: Response,
792
+ authCtx: Extract<CodexAuthContext, { kind: "pool" | "main-pool" }>,
793
+ request: Awaited<ReturnType<ReturnType<typeof resolveAdapter>["buildRequest"]>>,
794
+ ) => void;
524
795
  }
525
796
 
526
797
  type CodexPoolAccountRetryResult =
@@ -761,6 +1032,7 @@ async function retryCodexPoolOnAlternateAccount(
761
1032
  route.provider.authMode === "forward",
762
1033
  );
763
1034
  retrySendCount += 1;
1035
+ args.onResponse?.(upstreamResponse, retryAuthCtx, request);
764
1036
  if (!retrySameConfirmedAccount || retrySendCount >= maxRetrySends) break;
765
1037
  if (!await shouldRetryCodexPoolAccountModel400(
766
1038
  upstreamResponse,
@@ -943,6 +1215,12 @@ export interface HandleResponsesOptions {
943
1215
  stripClaudeMainAuthForNoncanonicalForward?: boolean;
944
1216
  /** Internal recursion guard; callers outside this module must not set it. */
945
1217
  comboAttempt?: boolean;
1218
+ /** Internal combo handoff for one parent-validated continuation snapshot. */
1219
+ comboReplaySnapshot?: {
1220
+ sourceBody: unknown;
1221
+ previousResponseInputExpanded: boolean;
1222
+ providerContinuation: OcxProviderContinuationState | undefined;
1223
+ };
946
1224
  /** Internal combo handoff: allow a later same-provider model after a reset-derived 429/402. */
947
1225
  deferCodexResetDerivedCooldown?: boolean;
948
1226
  /** 030-owned handoff when a child consumed the original failure under bounds. */
@@ -1141,7 +1419,7 @@ function unreadableEncryptedAgentTaskResponse(): Response {
1141
1419
  }
1142
1420
 
1143
1421
  type ResponsesAuthResolution =
1144
- | { ok: true; authCtx: CodexAuthContext; headers: Headers }
1422
+ | { ok: true; authCtx: CodexAuthContext; headers: Headers; substituteMainCredential: boolean }
1145
1423
  | { ok: false; response: Response };
1146
1424
 
1147
1425
  /**
@@ -1205,6 +1483,7 @@ async function resolveResponsesCodexAuth(
1205
1483
  ok: true,
1206
1484
  authCtx,
1207
1485
  headers: materializeCodexUpstreamAuth(req.headers, authCtx, { substituteMainCredential }),
1486
+ substituteMainCredential,
1208
1487
  };
1209
1488
  } catch (err) {
1210
1489
  if (err instanceof CodexAccountCooldownError) {
@@ -1435,7 +1714,12 @@ export async function handleComboResponses(
1435
1714
  // Expand previous_response_id before image policy and child dispatch so a
1436
1715
  // continuation that only references prior images still fails closed when
1437
1716
  // imageInput is disabled (and so targets see the full replayed input).
1438
- const body = expandPreviousResponseInput(rawBody);
1717
+ const inboundClientThreadId = req.headers.get("x-codex-parent-thread-id")?.trim() || undefined;
1718
+ const body = expandPreviousResponseInput(rawBody, inboundClientThreadId);
1719
+ const scopeMismatch = previousResponseScopeMismatch(body);
1720
+ if (scopeMismatch) {
1721
+ console.warn("[opencodex] dropped a previous_response_id with a mismatched client task scope; continuing fresh");
1722
+ }
1439
1723
  if (previousResponseReplayFailure(body)) {
1440
1724
  return formatErrorResponse(
1441
1725
  400,
@@ -1462,11 +1746,14 @@ export async function handleComboResponses(
1462
1746
  if (combo.imageInput === "disabled" && comboRequestHasImageInput(body)) {
1463
1747
  return formatErrorResponse(400, "invalid_request_error", `Combo "${comboId}" does not accept image input`);
1464
1748
  }
1465
- // Expansion already materialised prior input. Drop the id so the child
1466
- // handleResponses path does not expand again and double-prepend history.
1467
- if (body !== rawBody && body && typeof body === "object" && !Array.isArray(body)) {
1468
- delete (body as Record<string, unknown>).previous_response_id;
1469
- }
1749
+ const comboReplaySnapshot = {
1750
+ sourceBody: body,
1751
+ previousResponseInputExpanded: body !== rawBody
1752
+ && typeof (body as { previous_response_id?: unknown }).previous_response_id === "string",
1753
+ providerContinuation: !scopeMismatch && body !== rawBody && requestedPreviousId
1754
+ ? previousResponseProviderState(requestedPreviousId)
1755
+ : undefined,
1756
+ };
1470
1757
  const adoptFailedChildLog = (childLog: RequestLogContext): void => {
1471
1758
  // Attempts remain the complete physical history; the logical row mirrors the most recent
1472
1759
  // failed target so an exhausted combo still has useful top-level reasoning diagnostics.
@@ -1573,6 +1860,7 @@ export async function handleComboResponses(
1573
1860
  response = await handleResponses(childRequest, config, childLog, {
1574
1861
  ...options,
1575
1862
  comboAttempt: true,
1863
+ comboReplaySnapshot,
1576
1864
  deferCodexResetDerivedCooldown,
1577
1865
  // Attempt-relative TTFT is recorded HERE (not via childLog.firstOutputMs — a later
1578
1866
  // Object.assign(logCtx, childLog) would overwrite the request-relative value).
@@ -1842,19 +2130,24 @@ async function handleResponsesInner(
1842
2130
  );
1843
2131
  const inboundClientThreadId = req.headers.get("x-codex-parent-thread-id")?.trim() || undefined;
1844
2132
  const originalBody = body;
1845
- body = expandPreviousResponseInput(body, inboundClientThreadId);
1846
- if (previousResponseScopeMismatch(body)) {
1847
- console.warn("[opencodex] dropped a previous_response_id with a mismatched client task scope; continuing fresh");
1848
- }
1849
- if (previousResponseReplayFailure(body)) {
1850
- return formatErrorResponse(
1851
- 400,
1852
- "previous_response_not_found",
1853
- "Continuation state is unavailable or corrupt; resend the full conversation without previous_response_id.",
1854
- );
2133
+ if (options.comboReplaySnapshot) {
2134
+ copyPreviousResponseReplayProvenance(options.comboReplaySnapshot.sourceBody, body);
2135
+ } else {
2136
+ body = expandPreviousResponseInput(body, inboundClientThreadId);
2137
+ if (previousResponseScopeMismatch(body)) {
2138
+ console.warn("[opencodex] dropped a previous_response_id with a mismatched client task scope; continuing fresh");
2139
+ }
2140
+ if (previousResponseReplayFailure(body)) {
2141
+ return formatErrorResponse(
2142
+ 400,
2143
+ "previous_response_not_found",
2144
+ "Continuation state is unavailable or corrupt; resend the full conversation without previous_response_id.",
2145
+ );
2146
+ }
1855
2147
  }
1856
- const previousResponseInputExpanded = body !== originalBody
1857
- && typeof (body as { previous_response_id?: unknown }).previous_response_id === "string";
2148
+ const previousResponseInputExpanded = options.comboReplaySnapshot?.previousResponseInputExpanded
2149
+ ?? (body !== originalBody
2150
+ && typeof (body as { previous_response_id?: unknown }).previous_response_id === "string");
1858
2151
 
1859
2152
  // Spawn-message compatibility (both directions): agent_message task payloads ride in
1860
2153
  // encrypted_content slots as plaintext. Rewrite them to input_text on the RAW body BEFORE
@@ -1877,8 +2170,10 @@ async function handleResponsesInner(
1877
2170
  parsed = parseRequest(body);
1878
2171
  toolBridgeMaps = buildToolBridgeMaps(parsed, translatorBudget);
1879
2172
  if (previousResponseInputExpanded) parsed._previousResponseInputExpanded = true;
1880
- parsed._providerContinuation = previousResponseProviderState(parsed.previousResponseId);
1881
- parsed._cursorConversationId = parsed._providerContinuation?.cursor?.conversationId;
2173
+ const providerContinuationCandidate = options.comboReplaySnapshot
2174
+ ? options.comboReplaySnapshot.providerContinuation
2175
+ : previousResponseProviderState(parsed.previousResponseId);
2176
+ if (providerContinuationCandidate) parsed._providerContinuationCandidate = providerContinuationCandidate;
1882
2177
  if (inboundClientThreadId) {
1883
2178
  parsed._clientThreadId = inboundClientThreadId;
1884
2179
  parsed._reasoningReplayScope = { clientThreadId: inboundClientThreadId };
@@ -2077,6 +2372,8 @@ async function handleResponsesInner(
2077
2372
  const kept: Array<keyof OcxParsedRequest> = [
2078
2373
  "_previousResponseInputExpanded",
2079
2374
  "_providerContinuation",
2375
+ "_providerContinuationCandidate",
2376
+ "_providerContinuationOwner",
2080
2377
  "_cursorConversationId",
2081
2378
  "_clientThreadId",
2082
2379
  "_reasoningReplayScope",
@@ -2217,11 +2514,13 @@ async function handleResponsesInner(
2217
2514
  pendingHostAdmissionLease = admission.lease;
2218
2515
  }
2219
2516
 
2517
+ let substituteMainCredential = false;
2220
2518
  {
2221
2519
  const finalAuth = await resolveResponsesCodexAuth(req, config, route, options);
2222
2520
  if (!finalAuth.ok) return finalAuth.response;
2223
2521
  authCtx = finalAuth.authCtx;
2224
2522
  selectedForwardHeaders = finalAuth.headers;
2523
+ substituteMainCredential = finalAuth.substituteMainCredential;
2225
2524
  }
2226
2525
 
2227
2526
  route.provider = applyCodexAuthContextToProvider(route.provider, authCtx, route.codexAccountMode);
@@ -2230,8 +2529,9 @@ async function handleResponsesInner(
2230
2529
  ? `${route.providerName}-${route.codexAccountNamespace}`
2231
2530
  : formatCodexProviderForLog(route.providerName, codexLogAccountId(authCtx), config);
2232
2531
  logCtx.accountLogLabel = codexAuthContextLogLabel(authCtx, config);
2233
- // Prefer Codex pool account as the Cursor thread namespace when present. Cursor routes without
2234
- // codexAccountMode still get a credential-derived scope inside the Cursor adapter.
2532
+ // Seed an account-derived scope before final adapter binding. Cursor never treats it as
2533
+ // authoritative: bindRouteReasoningReplayScope replaces it with the exact route owner or a
2534
+ // per-request fail-closed sentinel after the final provider and credential are known.
2235
2535
  const identityScope = codexLogAccountId(authCtx);
2236
2536
  if (identityScope) parsed._cursorIdentityScope = identityScope;
2237
2537
  subagentFallbackAccountId = authCtx.kind === "pool" || authCtx.kind === "main-pool"
@@ -2345,6 +2645,9 @@ async function handleResponsesInner(
2345
2645
  codexAuthContext: authCtx,
2346
2646
  forwardHeaders: selectedForwardHeaders,
2347
2647
  });
2648
+ if (!logCtx.conversationId && parsed._cursorConversationId) {
2649
+ logCtx.conversationId = normalizeLogConversationId(parsed._cursorConversationId);
2650
+ }
2348
2651
  logCtx.providerAdapter = adapter.name;
2349
2652
  // Ordinary requests receive one durable attempt only after their final initial
2350
2653
  // adapter is resolved. Combo children own their attempt and retries keep it.
@@ -2445,24 +2748,19 @@ async function handleResponsesInner(
2445
2748
  emitted?: OcxProviderContinuationState,
2446
2749
  ): OcxProviderContinuationState | undefined => {
2447
2750
  const cursorConversationId = parsed._cursorConversationId;
2448
- const inherited = parsed._providerContinuation;
2449
- if (!emitted && !inherited && !cursorConversationId) return undefined;
2450
- return {
2451
- ...(inherited ?? {}),
2452
- ...(emitted ?? {}),
2453
- ...((inherited?.kiro || emitted?.kiro)
2454
- ? { kiro: { ...(inherited?.kiro ?? {}), ...(emitted?.kiro ?? {}) } }
2455
- : {}),
2456
- ...(cursorConversationId
2457
- ? {
2458
- cursor: {
2459
- ...(inherited?.cursor ?? {}),
2460
- ...(emitted?.cursor ?? {}),
2461
- conversationId: cursorConversationId,
2462
- },
2463
- }
2464
- : {}),
2465
- };
2751
+ const inherited = providerContinuationPayload(parsed._providerContinuation);
2752
+ const emittedPayload = providerContinuationPayload(emitted);
2753
+ if (!emittedPayload && !inherited && !cursorConversationId) return undefined;
2754
+ const merged = mergeProviderContinuationPayload(
2755
+ inherited ?? {},
2756
+ emittedPayload ?? {},
2757
+ ) as OcxProviderContinuationState;
2758
+ if (cursorConversationId) {
2759
+ merged.cursor = { ...(merged.cursor ?? {}), conversationId: cursorConversationId };
2760
+ }
2761
+ return parsed._providerContinuationOwner
2762
+ ? { ...merged, __ocxOwner: { ...parsed._providerContinuationOwner } }
2763
+ : merged;
2466
2764
  };
2467
2765
 
2468
2766
  // Remote compaction v2 on a ROUTED model: Codex sent `compaction_trigger` and requires exactly
@@ -2475,6 +2773,9 @@ async function handleResponsesInner(
2475
2773
  // message, and leave Codex fataling on a missing compaction item (#422).
2476
2774
  const routedCompaction = parsed._compactionRequest === true
2477
2775
  && !isCanonicalOpenAiForwardProvider(route.provider);
2776
+ const commitReasoningReplayServingRoute = (): void => {
2777
+ commitReasoningReplayServingIdentity(parsed._reasoningReplayScope);
2778
+ };
2478
2779
  if (routedCompaction) {
2479
2780
  delete parsed.context.tools;
2480
2781
  delete parsed._webSearch;
@@ -2492,6 +2793,11 @@ async function handleResponsesInner(
2492
2793
  parsed.context.messages.push({ role: "user", content: COMPACT_PROMPT, timestamp: Date.now() });
2493
2794
  }
2494
2795
 
2796
+ let routedNamespaceToolAliases: RoutedNamespaceToolAliases = new Map();
2797
+ const refreshRoutedNamespaceToolAliases = (builtRequest: AdapterRequest): void => {
2798
+ routedNamespaceToolAliases = builtRequest.convertedRoutedNamespaceToolAliases ?? new Map();
2799
+ };
2800
+
2495
2801
  if ("passthrough" in adapter && adapter.passthrough && !routedCompaction) {
2496
2802
  let hostAdmissionLease = pendingHostAdmissionLease;
2497
2803
  pendingHostAdmissionLease = null;
@@ -2527,11 +2833,21 @@ async function handleResponsesInner(
2527
2833
  request = await adapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
2528
2834
  } catch (error) {
2529
2835
  releaseCodexAuthContextProbeLease(authCtx);
2836
+ // A tool catalog this proxy cannot lower onto one wire namespace is a client input error, and
2837
+ // the rotation-rebuild and bridged paths already answer 400 for the identical throw. Rethrowing
2838
+ // it here escaped every catch up to the Bun handler, so the same request produced an
2839
+ // unstructured 500 — and no request log — depending only on whether a rotation ran first.
2840
+ if (error instanceof NamespaceToolCollisionError) {
2841
+ return formatErrorResponse(400, "invalid_request_error", redactSecretString(error.message));
2842
+ }
2530
2843
  throw error;
2531
2844
  }
2532
- if (route.provider.authMode !== "forward") {
2845
+ if (!isCanonicalOpenAiForwardProvider(route.provider)) {
2533
2846
  for (const name of request.convertedRoutedCustomToolNames ?? []) {
2534
- if (toolBridgeMaps.freeformToolNames.has(name)) routedCustomToolNames.add(name);
2847
+ if (
2848
+ toolBridgeMaps.freeformToolNames.has(name)
2849
+ || toolBridgeMaps.toolNsMap.get(name)?.freeform === true
2850
+ ) routedCustomToolNames.add(name);
2535
2851
  }
2536
2852
  }
2537
2853
  for (const name of request.convertedRoutedToolSearchNames ?? []) {
@@ -2540,6 +2856,7 @@ async function handleResponsesInner(
2540
2856
  // would incorrectly disable restoration for the exact ambiguous-name case the alias fixes.
2541
2857
  routedToolSearchNames.add(name);
2542
2858
  }
2859
+ refreshRoutedNamespaceToolAliases(request);
2543
2860
  // #1700: the bridged paths refuse a call to a tool the request never declared
2544
2861
  // (`declaredToolNames`, src/bridge.ts). The passthrough had no equivalent, so a routed
2545
2862
  // provider's top-level `apply_patch` — which under Codex code mode exists only as a nested
@@ -2554,14 +2871,17 @@ async function handleResponsesInner(
2554
2871
  // with no tools and Copilot answers with a `custom_tool_call` for `apply_patch`. Policing an
2555
2872
  // empty catalog truncates that turn. An unreadable body lands here too, since it yields no
2556
2873
  // names either.
2557
- const outboundRequestBody = (() => {
2874
+ const parseOutboundRequestBody = (bodyText: string): Record<string, unknown> | undefined => {
2558
2875
  try {
2559
- const body = JSON.parse(request.body) as unknown;
2560
- return body && typeof body === "object" && !Array.isArray(body) ? body : undefined;
2876
+ const body = JSON.parse(bodyText) as unknown;
2877
+ return body && typeof body === "object" && !Array.isArray(body)
2878
+ ? body as Record<string, unknown>
2879
+ : undefined;
2561
2880
  } catch {
2562
2881
  return undefined;
2563
2882
  }
2564
- })();
2883
+ };
2884
+ let outboundRequestBody = parseOutboundRequestBody(request.body);
2565
2885
  const declaredWireToolNames = collectDeclaredWireToolNames(outboundRequestBody);
2566
2886
  // Union with the caller's own catalog, because the outbound body is not always a complete
2567
2887
  // record of it: hosted-tool preference REPLACES a client tool with its hosted form, so a
@@ -2643,7 +2963,7 @@ async function handleResponsesInner(
2643
2963
  }
2644
2964
  hostAdmissionLease = null;
2645
2965
  };
2646
- const passthroughEstimate = typeof request.usageLog?.inputTokens === "number"
2966
+ let passthroughEstimate = typeof request.usageLog?.inputTokens === "number"
2647
2967
  ? request.usageLog.inputTokens
2648
2968
  : undefined;
2649
2969
  if (passthroughEstimate !== undefined) {
@@ -2730,11 +3050,89 @@ async function handleResponsesInner(
2730
3050
  request.releaseBodyObservation?.();
2731
3051
  }
2732
3052
 
3053
+ const opaqueBlobRecoveryGuard: OpaqueBlobRecoveryGuard = { attempted: false };
3054
+ let oauth401ReplayAttempted = false;
3055
+ const rateLimitPolicy = rateLimitRetryPolicyFor(route.provider);
3056
+ let rateLimitRetries = 0;
3057
+ const rebuildAndRefetch = async (
3058
+ recovery: AttemptRecoveryKind,
3059
+ ): Promise<Response | { failed: Response }> => {
3060
+ const retryAdapter = resolveAdapter(
3061
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
3062
+ config.cacheRetention,
3063
+ );
3064
+ if (!("passthrough" in retryAdapter) || !retryAdapter.passthrough) {
3065
+ upstream.abort();
3066
+ return { failed: formatErrorResponse(502, "upstream_error", "Recovery changed the provider wire unexpectedly") };
3067
+ }
3068
+ try {
3069
+ request = await retryAdapter.buildRequest(parsed, {
3070
+ headers: selectedForwardHeaders,
3071
+ translatorBudget,
3072
+ });
3073
+ refreshRoutedNamespaceToolAliases(request);
3074
+ recordAdapterReasoning(logCtx, request);
3075
+ recordAdapterTier(logCtx, request);
3076
+ } catch (err) {
3077
+ upstream.abort();
3078
+ if (options.abortSignal?.aborted) return { failed: clientCancelledResponse() };
3079
+ const msg = err instanceof Error ? err.message : String(err);
3080
+ return { failed: formatErrorResponse(400, "invalid_request_error", redactSecretString(msg)) };
3081
+ }
3082
+ passthroughEstimate = typeof request.usageLog?.inputTokens === "number"
3083
+ ? request.usageLog.inputTokens
3084
+ : undefined;
3085
+ if (passthroughEstimate !== undefined) logCtx.usageLogInputTokens = passthroughEstimate;
3086
+ outboundRequestBody = parseOutboundRequestBody(request.body);
3087
+ logCtx.providerAdapter = retryAdapter.name;
3088
+ sealRequestAttemptIdentity(
3089
+ logCtx.activeAttempt,
3090
+ logCtx.provider,
3091
+ retryAdapter.name,
3092
+ logCtx.accountLogLabel,
3093
+ );
3094
+ try {
3095
+ return await fetchWithTransientRetry(
3096
+ innerRecovery => {
3097
+ noteAttemptSend(logCtx.activeAttempt, passthroughEstimate, innerRecovery ?? recovery);
3098
+ return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
3099
+ method: request.method,
3100
+ headers: request.headers,
3101
+ body: request.body,
3102
+ }, innerRecovery), upstream.signal, connectMs, parsed.stream,
3103
+ providerFetch(route.provider, options.codexWsRuntimeIdentity, {
3104
+ providerName: route.providerName,
3105
+ modelId: route.modelId,
3106
+ }),
3107
+ route.provider.authMode === "forward")
3108
+ .then(response => {
3109
+ settleObservedHostResponse();
3110
+ return response;
3111
+ });
3112
+ },
3113
+ { abortSignal: upstream.signal, label: safeHostLabel(request.url) },
3114
+ );
3115
+ } catch (err) {
3116
+ return { failed: transportFailureResponse(err) };
3117
+ } finally {
3118
+ request.releaseBodyObservation?.();
3119
+ }
3120
+ };
3121
+
3122
+ // Keep recovery kinds in sync with the generic `recovery:` loop below.
3123
+ passthroughRecovery: for (;;) {
3124
+
2733
3125
  // Native Responses providers return before the generic adapter recovery loop below. Keep
2734
3126
  // their OAuth contract identical: one pre-stream 401 forces a credential refresh and one
2735
3127
  // rebuilt replay. xAI's current subscription models use this branch now that their official
2736
3128
  // Grok CLI catalog declares the Responses backend.
2737
- if (upstreamResponse.status === 401 && isOAuth401ReplayProvider && sentOAuthSnapshot) {
3129
+ if (
3130
+ upstreamResponse.status === 401
3131
+ && isOAuth401ReplayProvider
3132
+ && sentOAuthSnapshot
3133
+ && !oauth401ReplayAttempted
3134
+ ) {
3135
+ oauth401ReplayAttempted = true;
2738
3136
  try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
2739
3137
  let refreshed: OAuthAccessSnapshot;
2740
3138
  try {
@@ -2786,6 +3184,7 @@ async function handleResponsesInner(
2786
3184
  headers: selectedForwardHeaders,
2787
3185
  translatorBudget,
2788
3186
  });
3187
+ refreshRoutedNamespaceToolAliases(request);
2789
3188
  recordAdapterReasoning(logCtx, request);
2790
3189
  recordAdapterTier(logCtx, request);
2791
3190
  } catch (err) {
@@ -2828,8 +3227,6 @@ async function handleResponsesInner(
2828
3227
  // immediately with no same-key replay. Pre-stream only — nothing has been relayed yet, so
2829
3228
  // the replay is lossless (same invariant as the recovery loop). Forward/OAuth providers
2830
3229
  // keep their pool logic below (rateLimitRetryPolicyFor returns null for them).
2831
- const rateLimitPolicy = rateLimitRetryPolicyFor(route.provider);
2832
- let rateLimitRetries = 0;
2833
3230
  while (
2834
3231
  upstreamResponse.status === 429
2835
3232
  && rateLimitPolicy !== null
@@ -2884,6 +3281,29 @@ async function handleResponsesInner(
2884
3281
  }
2885
3282
  }
2886
3283
 
3284
+ const captureAffinityResponse = (
3285
+ response: Response,
3286
+ captureAuthCtx: CodexAuthContext = authCtx,
3287
+ captureRequest: Awaited<ReturnType<typeof adapter.buildRequest>> = request,
3288
+ credentialSubstituted = substituteMainCredential
3289
+ || captureAuthCtx.kind === "pool"
3290
+ || captureAuthCtx.kind === "main-pool",
3291
+ ): void => {
3292
+ if (!isCanonicalOpenAiForwardProvider(route.provider)) return;
3293
+ captureCodexAffinityDiagnostic({
3294
+ inboundHeaders: req.headers,
3295
+ outboundHeaders: captureRequest.headers,
3296
+ authKind: captureAuthCtx.kind,
3297
+ accountMode: route.codexAccountMode,
3298
+ fixedAccount: isFixedCodexAccount(captureAuthCtx),
3299
+ credentialSubstituted,
3300
+ accountGatedModel: ACCOUNT_GATED_NATIVE_OPENAI_MODELS.has(route.modelId),
3301
+ wireModelNormalized: parsed.modelId !== route.modelId,
3302
+ status: response.status,
3303
+ });
3304
+ };
3305
+ captureAffinityResponse(upstreamResponse);
3306
+
2887
3307
  if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
2888
3308
  let poolRetryOutcome: number | undefined;
2889
3309
  if (await shouldRetryCodexPoolAccountModel400(
@@ -2912,6 +3332,9 @@ async function handleResponsesInner(
2912
3332
  connectMs,
2913
3333
  passthroughEstimate,
2914
3334
  stream: parsed.stream,
3335
+ onResponse: (response, retryAuthCtx, retryRequest) => {
3336
+ captureAffinityResponse(response, retryAuthCtx, retryRequest, true);
3337
+ },
2915
3338
  });
2916
3339
  if (retry.kind === "transport") {
2917
3340
  authCtx = retry.authCtx;
@@ -2920,6 +3343,7 @@ async function handleResponsesInner(
2920
3343
  if (retry.kind === "retried") {
2921
3344
  authCtx = retry.authCtx;
2922
3345
  request = retry.request;
3346
+ refreshRoutedNamespaceToolAliases(request);
2923
3347
  upstreamResponse = retry.upstreamResponse;
2924
3348
  selectedForwardHeaders = retry.selectedForwardHeaders;
2925
3349
  // Keep subagent quota-failure health keyed to the account that actually served.
@@ -2927,6 +3351,25 @@ async function handleResponsesInner(
2927
3351
  }
2928
3352
  }
2929
3353
  }
3354
+ // The deterministic route record cannot classify history it never observed (restart, expiry,
3355
+ // eviction, or an older transcript). Inspect only a bounded clone of a 4xx whose exact outbound
3356
+ // Responses body still carries opaque state, then rebuild once through the ordinary adapter
3357
+ // sanitation path. A second rejection falls through unchanged because the guard stays armed.
3358
+ const opaqueBlobRecovery = await attemptOpaqueBlobRecovery({
3359
+ response: upstreamResponse,
3360
+ outboundBody: request.body,
3361
+ adapterName: adapter.name,
3362
+ parsed,
3363
+ guard: opaqueBlobRecoveryGuard,
3364
+ signal: upstream.signal,
3365
+ }, rebuildAndRefetch);
3366
+ if (opaqueBlobRecovery.kind === "failed") return opaqueBlobRecovery.response;
3367
+ if (opaqueBlobRecovery.kind === "recovered") {
3368
+ upstreamResponse = opaqueBlobRecovery.response;
3369
+ continue passthroughRecovery;
3370
+ }
3371
+ break;
3372
+ }
2930
3373
  const headers = sanitizePassthroughHeaders(upstreamResponse.headers);
2931
3374
  const resolvedModel = headers.get("openai-model")?.trim();
2932
3375
  if (resolvedModel) logCtx.resolvedModel = resolvedModel;
@@ -3050,6 +3493,10 @@ async function handleResponsesInner(
3050
3493
  // devlog/_fin/260731_macos_rss_retention/100_darwin_eager_optin.md).
3051
3494
  // The bundled known-bad runtime remains on tee by default on both platforms.
3052
3495
  if (isEventStream && upstreamResponse.body) {
3496
+ // For streamed passthrough, a successful terminal response means non-error upstream status
3497
+ // before relay starts. Waiting for SSE completion would retain request state across the whole
3498
+ // stream; a later body failure does not undo that this destination accepted and served the turn.
3499
+ commitReasoningReplayServingRoute();
3053
3500
  const terminalRepairPolicy = providerModelResponsesTerminalRepair(
3054
3501
  route.providerName,
3055
3502
  route.provider,
@@ -3074,6 +3521,9 @@ async function handleResponsesInner(
3074
3521
  // Compose opt-in payload rewrites into one parse/stringify pass (image-gen restore first).
3075
3522
  const payloadRewrites = [
3076
3523
  createImageGenCallRestoreRewrite(imageGenCallAliases),
3524
+ routedNamespaceToolAliases.size > 0
3525
+ ? createRoutedNamespaceCallRestoreRewrite(routedNamespaceToolAliases)
3526
+ : undefined,
3077
3527
  hasResponsesItemIdRepair(repairConfig)
3078
3528
  ? createResponsesItemIdPayloadRewrite(repairConfig!, translatorBudget)
3079
3529
  : undefined,
@@ -3290,8 +3740,12 @@ async function handleResponsesInner(
3290
3740
  const text = bounded.text;
3291
3741
  inspectResponseLogJson(logCtx, text);
3292
3742
  const clientJson = (() => {
3293
- const restored = restoreRoutedCustomCallsInJson(
3743
+ const restoredNamespace = restoreRoutedNamespaceCallsInJson(
3294
3744
  restoreImageGenCallsInJson(text, imageGenCallAliases),
3745
+ routedNamespaceToolAliases,
3746
+ );
3747
+ const restored = restoreRoutedCustomCallsInJson(
3748
+ restoredNamespace,
3295
3749
  routedCustomToolNames,
3296
3750
  );
3297
3751
  const restoredToolSearch = restoreRoutedToolSearchCallsInJson(
@@ -3328,6 +3782,7 @@ async function handleResponsesInner(
3328
3782
  return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared));
3329
3783
  }
3330
3784
  }
3785
+ commitReasoningReplayServingRoute();
3331
3786
  if (rememberPassthroughResponseChecked) {
3332
3787
  try {
3333
3788
  rememberPassthroughResponseChecked(
@@ -3409,6 +3864,9 @@ async function handleResponsesInner(
3409
3864
  headers,
3410
3865
  });
3411
3866
  }
3867
+ // An unclassified passthrough body is relayed directly and has no bounded completion observer;
3868
+ // use the same non-error-status success boundary as SSE instead of retaining per-stream state.
3869
+ commitReasoningReplayServingRoute();
3412
3870
  const body = relayWithAbort(upstreamResponse.body, upstream);
3413
3871
  const turnAc = new AbortController();
3414
3872
  const tracked = body ? trackStreamLifetime(body, turnAc, undefined, options.turnAdmissionLease) : null;
@@ -3554,13 +4012,15 @@ async function handleResponsesInner(
3554
4012
  retryOn429Policy: rateLimitRetryPolicyFor(route.provider),
3555
4013
  ...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
3556
4014
  ...(options.forceEmptyResponseId ? { forceEmptyResponseId: true } : {}),
3557
- onCompletedResponse: (response, providerState) =>
4015
+ onCompletedResponse: (response, providerState) => {
4016
+ commitReasoningReplayServingRoute();
3558
4017
  rememberResponseState(
3559
4018
  parsed._rawBody,
3560
4019
  response,
3561
4020
  continuationStateForResponse(providerState),
3562
4021
  responseStateOptions(adapterNeedsForcedContinuation(adapter.name)),
3563
- ),
4022
+ );
4023
+ },
3564
4024
  });
3565
4025
  if (imgResponse.body) {
3566
4026
  const imgTurnAc = new AbortController();
@@ -3586,6 +4046,11 @@ async function handleResponsesInner(
3586
4046
  backend: wsPlan.backend,
3587
4047
  forwardProvider: wsPlan.forwardSidecar?.provider,
3588
4048
  anthropicSidecar: wsPlan.anthropicSidecar,
4049
+ xaiSidecar: wsPlan.xaiSidecar,
4050
+ geminiSidecar: wsPlan.geminiSidecar,
4051
+ xaiSearchOptions: wsPlan.xaiSearchOptions,
4052
+ // The exa key never rides the plan: read it from config at unpack time (L9).
4053
+ ...(wsPlan.exaConfigured ? { exaApiKey: config.webSearchSidecar?.exaApiKey } : {}),
3589
4054
  hostedTool: wsPlan.hostedTool,
3590
4055
  selectedForwardHeaders: wsPlan.forwardSidecar?.headers ?? selectedForwardHeaders,
3591
4056
  settings: wsPlan.settings,
@@ -3633,6 +4098,7 @@ async function handleResponsesInner(
3633
4098
  return rotatedAdapter;
3634
4099
  },
3635
4100
  retryOn429Policy: rateLimitRetryPolicyFor(route.provider),
4101
+ onCompletedResponse: commitReasoningReplayServingRoute,
3636
4102
  });
3637
4103
  // Register the sidecar stream as an active turn so drainAndShutdown waits for (or aborts)
3638
4104
  // in-flight web-search turns instead of skipping them during graceful shutdown.
@@ -3796,15 +4262,17 @@ async function handleResponsesInner(
3796
4262
  if (logCtx.activeAttempt) logCtx.activeAttempt.usage = usage;
3797
4263
  }
3798
4264
  },
3799
- ...(routedCompaction ? {} : {
3800
- onCompletedResponse: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) =>
4265
+ onCompletedResponse: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) => {
4266
+ commitReasoningReplayServingRoute();
4267
+ if (!routedCompaction) {
3801
4268
  rememberResponseState(
3802
4269
  parsed._rawBody,
3803
4270
  response,
3804
4271
  continuationStateForResponse(providerState),
3805
4272
  responseStateOptions(adapterNeedsForcedContinuation(adapter.name)),
3806
- ),
3807
- }),
4273
+ );
4274
+ }
4275
+ },
3808
4276
  },
3809
4277
  );
3810
4278
  const bridgeTurnAc = new AbortController();
@@ -3867,6 +4335,9 @@ async function handleResponsesInner(
3867
4335
  // buildResponseJSON; bound the durability window before the JSON becomes
3868
4336
  // externally visible.
3869
4337
  await awaitThoughtSignatureDurability();
4338
+ if (adapterResponseReachedServingTerminal(events, json)) {
4339
+ commitReasoningReplayServingRoute();
4340
+ }
3870
4341
  return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
3871
4342
  }
3872
4343
 
@@ -3889,6 +4360,7 @@ async function handleResponsesInner(
3889
4360
  let inputTokenEstimate: number | undefined;
3890
4361
  try {
3891
4362
  initialRequest = await activeAdapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
4363
+ refreshRoutedNamespaceToolAliases(initialRequest);
3892
4364
  recordAdapterReasoning(logCtx, initialRequest);
3893
4365
  recordAdapterTier(logCtx, initialRequest);
3894
4366
  inputTokenEstimate = typeof initialRequest.usageLog?.inputTokens === "number"
@@ -3977,12 +4449,14 @@ async function handleResponsesInner(
3977
4449
  // main request clear a 413 must not be forgotten on the very next continuation build.
3978
4450
  let imageTierBias = 0;
3979
4451
  if (!upstreamResponse.ok) {
3980
- // Recovery loop: multi-key 429 failover + at most ONE anthropic 413 tightened retry
4452
+ // Recovery loop: multi-key 429 failover + at most ONE opaque-state rebuild and ONE
4453
+ // anthropic 413 tightened retry
3981
4454
  // (devlog/260714_image_normalization_pipeline/030). One mutable activeAdapter serves
3982
4455
  // both paths so a 429→413 sequence never rebuilds against a stale pre-rotation
3983
4456
  // adapter, and imageTierBias — once armed — rides EVERY subsequent rebuild so a
3984
4457
  // 413→429 rotation cannot silently undo the tightening.
3985
4458
  let imageRetryAttempted = false;
4459
+ const opaqueBlobRecoveryGuard: OpaqueBlobRecoveryGuard = { attempted: false };
3986
4460
  let oauth401ReplayAttempted = false;
3987
4461
  /**
3988
4462
  * Rebuild the request from the current parsed input (and any image-tier bias) and refetch
@@ -4019,6 +4493,7 @@ async function handleResponsesInner(
4019
4493
  sameTargetParsed = parsed;
4020
4494
  sameTargetToken = transportToken;
4021
4495
  }
4496
+ refreshRoutedNamespaceToolAliases(retryRequest);
4022
4497
  const retryEstimate = typeof retryRequest.usageLog?.inputTokens === "number"
4023
4498
  ? retryRequest.usageLog.inputTokens
4024
4499
  : undefined;
@@ -4060,6 +4535,7 @@ async function handleResponsesInner(
4060
4535
  return { failed: formatErrorResponse(502, "upstream_error", msg) };
4061
4536
  }
4062
4537
  };
4538
+ // Keep recovery kinds in sync with the native Responses `passthroughRecovery:` loop above.
4063
4539
  recovery: for (;;) {
4064
4540
  if (
4065
4541
  upstreamResponse.status === 401
@@ -4215,6 +4691,27 @@ async function handleResponsesInner(
4215
4691
  break;
4216
4692
  }
4217
4693
  }
4694
+ // Unknown provenance is deliberately fail-soft in pre-flight: after a restart, TTL expiry,
4695
+ // or LRU eviction, a valid same-backend blob must survive. A decoder's own 4xx identity is
4696
+ // the missing authoritative signal. Rebuild once through the same sanitation path used by a
4697
+ // known route switch; invalidating is mandatory because `parsed` mutates in place and the
4698
+ // same-target cache would otherwise replay the rejected bytes verbatim.
4699
+ const opaqueBlobRecovery = await attemptOpaqueBlobRecovery({
4700
+ response: upstreamResponse,
4701
+ outboundBody: sameTargetRequest?.body,
4702
+ adapterName: activeAdapter.name,
4703
+ parsed,
4704
+ guard: opaqueBlobRecoveryGuard,
4705
+ signal: upstream.signal,
4706
+ }, recovery => {
4707
+ invalidateSameTargetRequest();
4708
+ return rebuildAndRefetch(recovery);
4709
+ });
4710
+ if (opaqueBlobRecovery.kind === "failed") return opaqueBlobRecovery.response;
4711
+ if (opaqueBlobRecovery.kind === "recovered") {
4712
+ upstreamResponse = opaqueBlobRecovery.response;
4713
+ continue recovery;
4714
+ }
4218
4715
  // Anthropic 413 request_too_large: rebuild once with every image one tier lower
4219
4716
  // (spiral guard: single attempt). The biased response re-enters the 429 check above.
4220
4717
  if (shouldAttemptImageTierRetry({
@@ -4475,6 +4972,14 @@ async function handleResponsesInner(
4475
4972
  resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
4476
4973
  config.cacheRetention,
4477
4974
  );
4975
+ bindRouteReasoningReplayScope({
4976
+ parsed: nextParsed,
4977
+ providerName: route.providerName,
4978
+ provider: route.provider,
4979
+ adapterName: activeAdapter.name,
4980
+ });
4981
+ // Response persistence closes over the outer parsed request; keep its owner binding in
4982
+ // sync with the terminal-guard clone that builds the rotated continuation request.
4478
4983
  bindRouteReasoningReplayScope({
4479
4984
  parsed,
4480
4985
  providerName: route.providerName,
@@ -4636,18 +5141,20 @@ async function handleResponsesInner(
4636
5141
  if (logCtx.activeAttempt) logCtx.activeAttempt.usage = usage;
4637
5142
  }
4638
5143
  },
4639
- // Compaction turns must NOT enter the continuation cache: _rawBody still holds the full
4640
- // PRE-compaction history, and a later previous_response_id expansion would rehydrate the
4641
- // giant stale chain Codex just replaced.
4642
- ...(routedCompaction ? {} : {
4643
- onCompletedResponse: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) =>
5144
+ onCompletedResponse: (response: Record<string, unknown>, providerState?: OcxProviderContinuationState) => {
5145
+ commitReasoningReplayServingRoute();
5146
+ // Compaction turns must NOT enter the continuation cache: _rawBody still holds the full
5147
+ // PRE-compaction history, and a later previous_response_id expansion would rehydrate the
5148
+ // giant stale chain Codex just replaced.
5149
+ if (!routedCompaction) {
4644
5150
  rememberResponseState(
4645
5151
  parsed._rawBody,
4646
5152
  response,
4647
5153
  continuationStateForResponse(providerState),
4648
5154
  responseStateOptions(activeAdapter.name === "kiro"),
4649
- ),
4650
- }),
5155
+ );
5156
+ }
5157
+ },
4651
5158
  },
4652
5159
  );
4653
5160
  const bridgeTurnAc = new AbortController();
@@ -4722,6 +5229,9 @@ async function handleResponsesInner(
4722
5229
  }
4723
5230
  // #1926 gap 2: same buffered-path durability bound as the primary branch.
4724
5231
  await awaitThoughtSignatureDurability();
5232
+ if (adapterResponseReachedServingTerminal(events, json)) {
5233
+ commitReasoningReplayServingRoute();
5234
+ }
4725
5235
  return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json" } });
4726
5236
  }
4727
5237