@yansigit/opencodex 2.32.0 → 2.33.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 (108) hide show
  1. package/README.md +2 -2
  2. package/gui/dist/assets/index-DKLr4LTE.js +102 -0
  3. package/gui/dist/assets/index-DrSQdTRd.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +6 -5
  6. package/src/adapters/anthropic.ts +20 -6
  7. package/src/adapters/azure.ts +20 -4
  8. package/src/adapters/base.ts +3 -1
  9. package/src/adapters/command-code.ts +40 -7
  10. package/src/adapters/cursor/live-transport.ts +1 -1
  11. package/src/adapters/cursor/protobuf-events.ts +158 -7
  12. package/src/adapters/cursor/protobuf-request.ts +33 -15
  13. package/src/adapters/cursor/request-builder.ts +4 -3
  14. package/src/adapters/cursor/tool-definitions.ts +27 -1
  15. package/src/adapters/cursor/types.ts +4 -3
  16. package/src/adapters/cursor.ts +9 -0
  17. package/src/adapters/google-antigravity-replay.ts +2 -2
  18. package/src/adapters/google-antigravity-wire.ts +7 -0
  19. package/src/adapters/google-errors.ts +6 -2
  20. package/src/adapters/google-http.ts +30 -7
  21. package/src/adapters/google-truncation.ts +5 -0
  22. package/src/adapters/google-wire-compiler.ts +38 -6
  23. package/src/adapters/google.ts +148 -26
  24. package/src/adapters/kiro-tools.ts +20 -9
  25. package/src/adapters/openai-chat.ts +9 -0
  26. package/src/adapters/openai-responses.ts +1 -1
  27. package/src/bridge.ts +112 -9
  28. package/src/claude/context-windows.ts +16 -9
  29. package/src/cli/doctor.ts +2 -2
  30. package/src/cli/index.ts +9 -2
  31. package/src/cli/provider.ts +6 -0
  32. package/src/cli/status.ts +23 -0
  33. package/src/codex/auth-api.ts +4 -2
  34. package/src/codex/autostart-health.ts +16 -0
  35. package/src/codex/catalog/aggregation.ts +12 -12
  36. package/src/codex/catalog/effort.ts +18 -3
  37. package/src/codex/catalog/metadata.ts +27 -1
  38. package/src/codex/catalog/model-metadata.ts +39 -12
  39. package/src/codex/catalog/parsing.ts +38 -27
  40. package/src/codex/catalog/provider-fetch.ts +198 -133
  41. package/src/codex/catalog/sync.ts +1 -1
  42. package/src/codex/convergence.ts +5 -0
  43. package/src/codex/shim.ts +56 -3
  44. package/src/config/provider-validation.ts +37 -0
  45. package/src/config.ts +78 -2
  46. package/src/generated/compatibility-version.json +120 -96
  47. package/src/images/loop.ts +37 -6
  48. package/src/lib/azure-identity.ts +154 -0
  49. package/src/lib/debug.ts +42 -0
  50. package/src/lib/errors.ts +14 -0
  51. package/src/lib/provider-outbound.ts +45 -33
  52. package/src/lib/provider-tls-profile.ts +309 -0
  53. package/src/lib/proxy-env.ts +49 -0
  54. package/src/lib/redact.ts +10 -1
  55. package/src/oauth/antigravity-routing.ts +282 -236
  56. package/src/oauth/callback-server.ts +22 -2
  57. package/src/oauth/command-code.ts +5 -16
  58. package/src/oauth/google-antigravity.ts +42 -5
  59. package/src/oauth/index.ts +15 -3
  60. package/src/oauth/kimi.ts +9 -1
  61. package/src/oauth/open-browser-choice.ts +26 -0
  62. package/src/oauth/store.ts +6 -0
  63. package/src/providers/antigravity-quota.ts +3 -1
  64. package/src/providers/api-keys.ts +2 -1
  65. package/src/providers/auto-compact-budget.ts +65 -0
  66. package/src/providers/derive.ts +4 -0
  67. package/src/providers/key-failover.ts +5 -1
  68. package/src/providers/openai-tiers.ts +5 -0
  69. package/src/providers/provider-id-rewrite.ts +1 -0
  70. package/src/providers/quota.ts +59 -13
  71. package/src/providers/registry.ts +3 -1
  72. package/src/providers/request-pacing.ts +33 -6
  73. package/src/providers/xai-transport.ts +21 -0
  74. package/src/responses/google-provider-options.ts +36 -0
  75. package/src/responses/namespace-tool-compat.ts +84 -4
  76. package/src/responses/parser.ts +11 -0
  77. package/src/responses/provider-opaque-metadata.ts +3 -3
  78. package/src/responses/schema.ts +37 -0
  79. package/src/responses/state.ts +94 -4
  80. package/src/router.ts +8 -2
  81. package/src/server/auth-cors.ts +28 -0
  82. package/src/server/images.ts +19 -35
  83. package/src/server/management/agent-settings-routes.ts +205 -15
  84. package/src/server/management/combo-routes.ts +6 -0
  85. package/src/server/management/config-routes.ts +31 -5
  86. package/src/server/management/model-rows.ts +4 -0
  87. package/src/server/management/oauth-account-routes.ts +25 -4
  88. package/src/server/management/provider-routes.ts +113 -15
  89. package/src/server/management/routing-profile-routes.ts +3 -0
  90. package/src/server/request-log.ts +21 -0
  91. package/src/server/responses/agent-task-recovery.ts +1 -1
  92. package/src/server/responses/compact.ts +30 -1
  93. package/src/server/responses/core.ts +359 -153
  94. package/src/server/responses/empty-completion-guard.ts +35 -6
  95. package/src/server/responses/fetch-helpers.ts +18 -5
  96. package/src/server/responses/v2-native-parent-override.ts +59 -0
  97. package/src/server/responses/ws-upstream.ts +75 -2
  98. package/src/server/responses-undeclared-tool-guard.ts +90 -8
  99. package/src/service.ts +1 -1
  100. package/src/types/config.ts +16 -1
  101. package/src/types/provider.ts +16 -0
  102. package/src/types/request.ts +28 -0
  103. package/src/types/tools.ts +27 -0
  104. package/src/types.ts +6 -0
  105. package/src/web-search/gemini-executor.ts +6 -4
  106. package/src/web-search/loop.ts +42 -6
  107. package/gui/dist/assets/index-BG43zwVe.js +0 -102
  108. package/gui/dist/assets/index-CiSI-jrP.css +0 -1
@@ -1,6 +1,6 @@
1
1
  import type { Server } from "bun";
2
2
  import { randomUUID } from "node:crypto";
3
- import { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type ResponsesTerminalStatus } from "../../bridge";
3
+ import { adapterEventDiagnosticDetails, bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse, type BridgeDiagnosticContext, type BridgeDiagnosticSequence, type ResponsesTerminalStatus } from "../../bridge";
4
4
  import { formatPassthroughUpstreamError } from "./passthrough-error";
5
5
  import {
6
6
  createResponsesFieldBackfillBlockRewrite,
@@ -14,6 +14,8 @@ import {
14
14
  resolveEnvValue,
15
15
  } from "../../config";
16
16
  import { parseRequest } from "../../responses/parser";
17
+ import { googleProviderOptionsRouteError } from "../../responses/google-provider-options";
18
+ import type { ProviderAdapter } from "../../adapters/base";
17
19
  import {
18
20
  bindReasoningReplayScope,
19
21
  commitReasoningReplayServingIdentity,
@@ -70,9 +72,12 @@ import {
70
72
  pickComboTarget,
71
73
  targetKey,
72
74
  } from "../../combos";
73
- import { isInjectionDebugEnabled } from "../../lib/debug-settings";
75
+ import { isDebugEnabled, isInjectionDebugEnabled } from "../../lib/debug-settings";
76
+ import { debugStreamDiagnostic } from "../../lib/debug";
74
77
  import { injectionDebugLog } from "../../lib/injection-debug-log";
78
+ import { OcxRequestValidationError } from "../../lib/errors";
75
79
  import { resolveClientRetryAfter } from "../../lib/retry-after";
80
+ import { antigravityOAuthDestinationConfigError, isAntigravityOAuthProvider } from "../../lib/provider-tls-profile";
76
81
  import { enrichOpenCodeZenRateLimitMessage } from "../../providers/opencode-zen-rate-limit";
77
82
  import { modelInList, namespacedToolName } from "../../types";
78
83
  import type {
@@ -90,6 +95,7 @@ import {
90
95
  forceRefreshOAuthAccessSnapshot,
91
96
  getOAuthCredentialApiBaseUrl,
92
97
  getValidAccessTokenForAccount,
98
+ getValidAccessTokenSnapshotForAccount,
93
99
  getValidAccessTokenSnapshot,
94
100
  publicOAuthAuthenticationErrorMessage,
95
101
  type OAuthAccessSnapshot,
@@ -108,12 +114,12 @@ import {
108
114
  rotateAnthropicAccountOn429,
109
115
  } from "../../oauth/anthropic-routing";
110
116
  import {
111
- antigravity429StickWaitMs,
112
117
  antigravitySessionKeyFromParts,
113
- bindAntigravityProject,
114
118
  bindAntigravitySessionAffinity,
119
+ recordAntigravityCooldown,
120
+ recordAntigravitySyntheticFailure,
115
121
  resolveAntigravityAccountForSession,
116
- rotateAntigravityAccountOn429,
122
+ retryableAntigravity429DelayMs,
117
123
  } from "../../oauth/antigravity-routing";
118
124
  import {
119
125
  CURSOR_POOL_MAX_FAILOVERS_PER_REQUEST,
@@ -170,6 +176,7 @@ import {
170
176
  fetchWithResetRetry,
171
177
  fetchWithTransientRetry,
172
178
  prepareSameTarget429Wait,
179
+ sleepWithAbort,
173
180
  } from "../../lib/upstream-retry";
174
181
  import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "../auth-cors";
175
182
  import type { DataPlaneAdmission } from "../auth-cors";
@@ -211,7 +218,7 @@ import {
211
218
  rotateProviderTransportOn429,
212
219
  } from "../../providers/key-failover";
213
220
  import { shouldAttemptImageTierRetry } from "../image-retry";
214
- import { resolveProviderTransport } from "../../providers/xai-transport";
221
+ import { isXaiResponsesDestination, resolveProviderTransport } from "../../providers/xai-transport";
215
222
  import type { WsData } from "../ws-bridge";
216
223
  import { codexAccountSelectionForTurn, registerTurn, trackStreamLifetime, unregisterTurn } from "../lifecycle";
217
224
  import { redactSecretString, sanitizeLogMetadataString } from "../../lib/redact";
@@ -290,6 +297,7 @@ import { createResponsesModelPayloadRewrite, rewriteResponsesModelJson } from ".
290
297
  import type { EffectiveSubagentRoster, SpawnAgentSurface } from "../../codex/catalog";
291
298
 
292
299
  import { buildToolBridgeMaps, collabSurface, injectDeveloperMessage, multiAgentGuidanceText } from "./collaboration";
300
+ import { decideV2NativeParentOverride } from "./v2-native-parent-override";
293
301
  import { mapCodexAuthContextErrorToResponse } from "./codex-auth-error";
294
302
  import { hasUnreadableEncryptedAgentTask, looksLikeBackendCiphertext, sanitizeEncryptedContentInPlace } from "./encrypted-payload";
295
303
  import { fetchWithHeaderTimeout, providerFetch, safeHostLabel, safeOriginLabel } from "./fetch-helpers";
@@ -328,12 +336,14 @@ import {
328
336
  import {
329
337
  collectDeclaredNamelessClientCallTypes,
330
338
  collectDeclaredWireToolNames,
339
+ collectProviderExecutedCallTypes,
331
340
  createUndeclaredToolCallGuardBlockRewrite,
332
341
  currentTurnWireToolCatalogBody,
333
342
  hasExplicitWireToolCatalog,
334
343
  undeclaredToolCallMessage,
335
344
  undeclaredToolCallName,
336
345
  undeclaredToolCallNameInResponse,
346
+ type ProviderExecutedCallType,
337
347
  } from "../responses-undeclared-tool-guard";
338
348
  import { createGithubCopilotResponsesBlockRewrite } from "../github-copilot-responses-repair";
339
349
  import { responsesJsonToSseStream } from "../responses-json-events";
@@ -349,6 +359,34 @@ import { preflightComboStreamResponse } from "./combo-stream-preflight";
349
359
  // already-committed event boundary and can replay custom adapter work.
350
360
  const runTurnAdapterSseResponses = new WeakSet<Response>();
351
361
 
362
+ function diagnoseAdapterEvents(
363
+ events: AsyncIterable<AdapterEvent>,
364
+ adapterName: string,
365
+ requestId: string | undefined,
366
+ logCtx: RequestLogContext,
367
+ state: BridgeDiagnosticSequence,
368
+ ): AsyncIterable<AdapterEvent> {
369
+ if (!requestId) return events;
370
+ return (async function* () {
371
+ for await (const event of events) {
372
+ const attempt = logCtx.activeAttempt;
373
+ debugStreamDiagnostic(
374
+ {
375
+ requestId,
376
+ adapterName,
377
+ ...(attempt?.ordinal !== undefined ? { attempt: attempt.ordinal } : {}),
378
+ ...(attempt?.recoveryKinds.at(-1) !== undefined ? { recovery: attempt.recoveryKinds.at(-1) } : {}),
379
+ },
380
+ "adapter",
381
+ ++state.value,
382
+ event.type,
383
+ adapterEventDiagnosticDetails(event),
384
+ );
385
+ yield event;
386
+ }
387
+ })();
388
+ }
389
+
352
390
  /**
353
391
  * Adapters whose continuation state must survive Codex's store:false requests.
354
392
  */
@@ -2256,6 +2294,7 @@ async function handleResponsesInner(
2256
2294
  let toolBridgeMaps: ReturnType<typeof buildToolBridgeMaps>;
2257
2295
  try {
2258
2296
  parsed = parseRequest(body);
2297
+ parsed._promptCacheKeyIsSharedCohort = options.promptCacheKeyIsSharedCohort === true;
2259
2298
  toolBridgeMaps = buildToolBridgeMaps(parsed, translatorBudget);
2260
2299
  if (previousResponseInputExpanded) parsed._previousResponseInputExpanded = true;
2261
2300
  const providerContinuationCandidate = options.comboReplaySnapshot
@@ -2381,6 +2420,27 @@ async function handleResponsesInner(
2381
2420
  return formatErrorResponse(404, "invalid_request_error", err instanceof Error ? err.message : String(err));
2382
2421
  }
2383
2422
 
2423
+ const parentOverride = decideV2NativeParentOverride({
2424
+ kind: "responses",
2425
+ config,
2426
+ headers: req.headers,
2427
+ parsed,
2428
+ sourceRoute: route,
2429
+ comboAttempt: options.comboAttempt,
2430
+ targetEvidence: evidenceFromBody(parsed._rawBody),
2431
+ });
2432
+ if (parentOverride.kind === "reject") {
2433
+ if (parentOverride.trace) logCtx.routeDecision = parentOverride.trace as typeof logCtx.routeDecision;
2434
+ return formatErrorResponse(404, "invalid_request_error", parentOverride.message);
2435
+ }
2436
+ if (parentOverride.kind === "override") {
2437
+ route = parentOverride.route;
2438
+ parsed.modelId = route.modelId;
2439
+ if (parsed._rawBody && typeof parsed._rawBody === "object") {
2440
+ (parsed._rawBody as { model?: string }).model = route.modelId;
2441
+ }
2442
+ }
2443
+
2384
2444
  const hasUnexpandedPreviousResponse = !!parsed.previousResponseId
2385
2445
  && parsed._previousResponseInputExpanded !== true;
2386
2446
  // Exact account selectors are isolated from Pool-wide quota work. A canonical replay miss must
@@ -2504,6 +2564,7 @@ async function handleResponsesInner(
2504
2564
  "_providerContinuationOwner",
2505
2565
  "_cursorConversationId",
2506
2566
  "_clientThreadId",
2567
+ "_promptCacheKeyIsSharedCohort",
2507
2568
  "_reasoningReplayScope",
2508
2569
  "_cursorIsolateConversation",
2509
2570
  ];
@@ -2668,14 +2729,28 @@ async function handleResponsesInner(
2668
2729
 
2669
2730
  // OAuth providers: swap in a fresh access token (auto-refreshed) as the Bearer key, so the
2670
2731
  // existing openai-chat / anthropic adapters authenticate with no change.
2671
- const isOAuth401ReplayProvider = (route.providerName === "xai" || route.providerName === "github-copilot" || route.providerName === "kiro")
2672
- && route.provider.authMode === "oauth";
2732
+ const isAntigravityOAuth = isAntigravityOAuthProvider(route.providerName, route.provider);
2733
+ if (route.providerName === "google-antigravity") {
2734
+ const antigravityConfigError = antigravityOAuthDestinationConfigError(route.providerName, route.provider);
2735
+ if (antigravityConfigError) {
2736
+ return formatErrorResponse(400, "invalid_request_error", antigravityConfigError);
2737
+ }
2738
+ }
2739
+ const isOAuth401ReplayProvider = isAntigravityOAuth
2740
+ || ((route.providerName === "xai" || route.providerName === "github-copilot" || route.providerName === "kiro")
2741
+ && route.provider.authMode === "oauth");
2673
2742
  let sentOAuthSnapshot: OAuthAccessSnapshot | undefined;
2674
2743
  let replayOAuthCredentialSnapshot: Pick<OAuthAccessSnapshot, "accountId" | "generation"> | undefined;
2675
2744
  let anthropicPoolAccountId: string | null = null;
2676
2745
  let anthropicPoolFailovers = 0;
2677
- let antigravityAccountId: string | undefined;
2678
- let antigravityFailovers = 0;
2746
+ let antigravityAccountId: string | null = null;
2747
+ let antigravitySessionKey: string | null = null;
2748
+ let antigravity429RetryAttempted = false;
2749
+ const observeAntigravityProviderError = isAntigravityOAuth
2750
+ ? (error: { code?: string; status?: number; message?: string }) => {
2751
+ if (antigravityAccountId) recordAntigravitySyntheticFailure(antigravityAccountId, error);
2752
+ }
2753
+ : undefined;
2679
2754
  let cursorPoolAccountId: string | null = null;
2680
2755
  let cursorPoolFailovers = 0;
2681
2756
  const oauthSessionKeyParts = {
@@ -2688,18 +2763,16 @@ async function handleResponsesInner(
2688
2763
  const anthropicSessionKey = route.providerName === "anthropic" && route.provider.authMode === "oauth"
2689
2764
  ? anthropicSessionKeyFromParts(oauthSessionKeyParts)
2690
2765
  : null;
2691
- const antigravitySessionKey = route.providerName === "google-antigravity"
2692
- && route.provider.authMode === "oauth"
2693
- && route.provider.googleMode === "cloud-code-assist"
2694
- ? antigravitySessionKeyFromParts(oauthSessionKeyParts)
2695
- : null;
2766
+ if (isAntigravityOAuth) {
2767
+ antigravitySessionKey = antigravitySessionKeyFromParts(oauthSessionKeyParts);
2768
+ }
2696
2769
  const cursorSessionKey = route.providerName === "cursor"
2697
2770
  && route.provider.authMode === "oauth"
2698
2771
  ? cursorSessionKeyFromParts({
2699
2772
  clientThreadId: typeof parsed._clientThreadId === "string" ? parsed._clientThreadId : null,
2700
2773
  })
2701
2774
  : null;
2702
- if (route.provider.authMode === "oauth") {
2775
+ if (route.provider.authMode === "oauth" || isAntigravityOAuth) {
2703
2776
  try {
2704
2777
  if (route.providerName === "anthropic" && isAnthropicAccountPoolEnabled(config)) {
2705
2778
  const selection = resolveAnthropicAccountForSession(anthropicSessionKey, config);
@@ -2721,50 +2794,6 @@ async function handleResponsesInner(
2721
2794
  promoteAnthropicActiveAccount(selection.accountId);
2722
2795
  route.provider = { ...route.provider, apiKey: accessToken };
2723
2796
  logCtx.provider = formatAnthropicProviderForLog("anthropic", selection.accountId, config);
2724
- } else if (
2725
- route.providerName === "google-antigravity"
2726
- && route.provider.googleMode === "cloud-code-assist"
2727
- ) {
2728
- const selection = resolveAntigravityAccountForSession(antigravitySessionKey);
2729
- if (!selection.accountId) {
2730
- if (selection.reason === "all-cooled") {
2731
- return formatErrorResponse(
2732
- 429,
2733
- "rate_limit_error",
2734
- "All Google Antigravity OAuth accounts are temporarily unavailable",
2735
- );
2736
- }
2737
- return formatErrorResponse(
2738
- 401,
2739
- "authentication_error",
2740
- "No eligible Google Antigravity OAuth account available",
2741
- );
2742
- }
2743
- let resolved = await getValidAccessTokenSnapshot(route.providerName);
2744
- if (selection.accountId !== resolved.accountId) {
2745
- const accessToken = await getValidAccessTokenForAccount("google-antigravity", selection.accountId);
2746
- const nextCredential = getAccountCredential("google-antigravity", selection.accountId);
2747
- resolved = {
2748
- ...resolved,
2749
- accountId: selection.accountId,
2750
- accessToken,
2751
- // Always replace; omitting a missing id would keep the previous account's projectId.
2752
- projectId: nextCredential?.projectId,
2753
- };
2754
- }
2755
- replayOAuthCredentialSnapshot = {
2756
- accountId: resolved.accountId,
2757
- generation: resolved.generation,
2758
- };
2759
- if (selection.reason === "failover") replayOAuthCredentialSnapshot = undefined;
2760
- route.provider = { ...route.provider, apiKey: resolved.accessToken };
2761
- antigravityAccountId = selection.accountId;
2762
- bindAntigravitySessionAffinity(antigravitySessionKey, selection.accountId);
2763
- const bound = bindAntigravityProject(route.provider, resolved.projectId);
2764
- if (!bound.ok) {
2765
- return formatErrorResponse(bound.status, bound.type, bound.message);
2766
- }
2767
- route.provider = bound.provider;
2768
2797
  } else if (
2769
2798
  route.providerName === "cursor"
2770
2799
  && route.provider.authMode === "oauth"
@@ -2796,7 +2825,28 @@ async function handleResponsesInner(
2796
2825
  route.provider = { ...route.provider, apiKey: resolved.accessToken };
2797
2826
  logCtx.provider = formatCursorProviderForLog("cursor", selection.accountId);
2798
2827
  } else {
2799
- const resolved = await getValidAccessTokenSnapshot(route.providerName);
2828
+ let resolved: OAuthAccessSnapshot;
2829
+ if (isAntigravityOAuth) {
2830
+ const selection = resolveAntigravityAccountForSession(antigravitySessionKey);
2831
+ if (!selection.accountId) {
2832
+ return formatErrorResponse(401, "authentication_error", "Selected Antigravity OAuth account is unavailable");
2833
+ }
2834
+ if (selection.cooldownUntil !== undefined) {
2835
+ const retryAfter = Math.max(1, Math.ceil((selection.cooldownUntil - Date.now()) / 1000));
2836
+ if (selection.cooldownKind === "geoblock") {
2837
+ return formatErrorResponse(403, "permission_error", "Selected Antigravity OAuth account is in local cooldown after upstream location block");
2838
+ }
2839
+ return formatErrorResponse(429, "rate_limit_error", "Selected Antigravity OAuth account is in local cooldown after upstream rate limit", { retryAfter: String(retryAfter) });
2840
+ }
2841
+ if (selection.reason === "active-needs-reauth") {
2842
+ return formatErrorResponse(401, "authentication_error", "Selected Antigravity OAuth account needs reauthentication");
2843
+ }
2844
+ antigravityAccountId = selection.accountId;
2845
+ resolved = await getValidAccessTokenSnapshotForAccount(route.providerName, selection.accountId);
2846
+ bindAntigravitySessionAffinity(antigravitySessionKey, selection.accountId);
2847
+ } else {
2848
+ resolved = await getValidAccessTokenSnapshot(route.providerName);
2849
+ }
2800
2850
  replayOAuthCredentialSnapshot = {
2801
2851
  accountId: resolved.accountId,
2802
2852
  generation: resolved.generation,
@@ -2808,6 +2858,14 @@ async function handleResponsesInner(
2808
2858
  // Only genuinely accountless adapter calls leave the context undefined and use local/env fallback.
2809
2859
  parsed._kiroAuthContext = { ...(resolved.kiro ?? {}) };
2810
2860
  }
2861
+ // Antigravity (cloud-code-assist) must use the project paired with this exact token
2862
+ // snapshot. A configured project may belong to another account and is never a fallback.
2863
+ if (isAntigravityOAuth) {
2864
+ if (!resolved.projectId) {
2865
+ return formatErrorResponse(400, "invalid_request_error", "Antigravity project unavailable — re-run `ocx login google-antigravity`");
2866
+ }
2867
+ route.provider = { ...route.provider, project: resolved.projectId };
2868
+ }
2811
2869
  }
2812
2870
  } catch (err) {
2813
2871
  if (err instanceof UnsupportedOAuthProviderError) {
@@ -2847,6 +2905,27 @@ async function handleResponsesInner(
2847
2905
  delete logCtx.accountLogLabel;
2848
2906
  }
2849
2907
  const adapter = resolveAdapter(adapterProvider, config.cacheRetention);
2908
+ const googleOptionsError = googleProviderOptionsRouteError(parsed, {
2909
+ providerName: route.providerName,
2910
+ provider: adapterProvider,
2911
+ adapterName: adapter.name,
2912
+ });
2913
+ if (googleOptionsError) {
2914
+ return formatErrorResponse(400, "invalid_request_error", googleOptionsError);
2915
+ }
2916
+ const assertGoogleOptionsRoute = (
2917
+ candidate: Pick<ProviderAdapter, "name" | "validateRequest">,
2918
+ provider: OcxProviderConfig,
2919
+ requestParsed: OcxParsedRequest = parsed,
2920
+ ): void => {
2921
+ const message = googleProviderOptionsRouteError(requestParsed, {
2922
+ providerName: route.providerName,
2923
+ provider,
2924
+ adapterName: candidate.name,
2925
+ });
2926
+ if (message) throw new OcxRequestValidationError(message);
2927
+ if (requestParsed.options.providerOptions?.google) candidate.validateRequest?.(requestParsed);
2928
+ };
2850
2929
  bindRouteReasoningReplayScope({
2851
2930
  parsed,
2852
2931
  providerName: route.providerName,
@@ -2861,6 +2940,7 @@ async function handleResponsesInner(
2861
2940
  }
2862
2941
  logCtx.providerAdapter = adapter.name;
2863
2942
  try {
2943
+ assertGoogleOptionsRoute(adapter, adapterProvider);
2864
2944
  adapter.validateRequest?.(parsed);
2865
2945
  } catch (err) {
2866
2946
  return formatErrorResponse(
@@ -2901,6 +2981,23 @@ async function handleResponsesInner(
2901
2981
  );
2902
2982
  if (passiveSubjectId) logCtx.activeAttempt.labRouteSubjectId = passiveSubjectId;
2903
2983
  }
2984
+ const diagnosticRequestId = isDebugEnabled() ? randomUUID() : undefined;
2985
+ const adapterDiagnosticState: BridgeDiagnosticSequence = { value: 0 };
2986
+ const diagnosticContext: BridgeDiagnosticContext | undefined = diagnosticRequestId
2987
+ ? { requestId: diagnosticRequestId, adapterName: adapter.name, sequence: adapterDiagnosticState }
2988
+ : undefined;
2989
+ const noteDiagnosticAttempt = (
2990
+ attempt: RequestLogContext["activeAttempt"],
2991
+ inputEstimate: number | undefined,
2992
+ recovery?: AttemptRecoveryKind,
2993
+ adapterName?: string,
2994
+ ): void => {
2995
+ noteAttemptSend(attempt, inputEstimate, recovery);
2996
+ if (!diagnosticContext) return;
2997
+ diagnosticContext.attempt = attempt?.ordinal;
2998
+ diagnosticContext.recovery = recovery;
2999
+ if (adapterName) diagnosticContext.adapterName = adapterName;
3000
+ };
2904
3001
  const isPassthrough = "passthrough" in adapter && !!adapter.passthrough;
2905
3002
 
2906
3003
  if (adapter.name === "kiro" && parsed.previousResponseId && !parsed._previousResponseInputExpanded) {
@@ -3070,8 +3167,14 @@ async function handleResponsesInner(
3070
3167
  const clientDeclaredNamelessCallTypes = collectDeclaredNamelessClientCallTypes(
3071
3168
  clientToolAuthorizationBody,
3072
3169
  );
3170
+ // Hosted calls the PROVIDER runs itself. Gated on the destination actually being xAI, so a
3171
+ // declaration alone cannot buy the exemption on some other upstream that never serves it.
3172
+ const providerExecutedCallTypes = isXaiResponsesDestination(route.provider)
3173
+ ? collectProviderExecutedCallTypes(clientToolAuthorizationBody)
3174
+ : new Set<ProviderExecutedCallType>();
3073
3175
  let request: Awaited<ReturnType<typeof adapter.buildRequest>>;
3074
3176
  try {
3177
+ assertGoogleOptionsRoute(adapter, adapterProvider);
3075
3178
  request = await adapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
3076
3179
  } catch (error) {
3077
3180
  releaseCodexAuthContextProbeLease(authCtx);
@@ -3181,6 +3284,9 @@ async function handleResponsesInner(
3181
3284
  // rejection is sticky for the whole turn, set from every parsed payload on the inspection side.
3182
3285
  let inspectionSawUndeclaredTool = false;
3183
3286
  const noteInspectedPayload = (payload: unknown) => {
3287
+ if (isAntigravityOAuth && antigravityAccountId) {
3288
+ recordAntigravitySyntheticFailure(antigravityAccountId, payload);
3289
+ }
3184
3290
  // Gated on the same flag as the guard itself: with no readable catalog (or a forward-auth
3185
3291
  // provider) every name looks undeclared, and flipping this would stop recording continuation
3186
3292
  // state for exactly the passthrough traffic the guard deliberately stands down for.
@@ -3189,6 +3295,7 @@ async function handleResponsesInner(
3189
3295
  payload,
3190
3296
  declaredWireToolNames,
3191
3297
  declaredNamelessClientCallTypes,
3298
+ providerExecutedCallTypes,
3192
3299
  ) !== undefined) {
3193
3300
  inspectionSawUndeclaredTool = true;
3194
3301
  }
@@ -3202,6 +3309,7 @@ async function handleResponsesInner(
3202
3309
  response,
3203
3310
  declaredWireToolNames,
3204
3311
  declaredNamelessClientCallTypes,
3312
+ providerExecutedCallTypes,
3205
3313
  ) !== undefined
3206
3314
  ) {
3207
3315
  return;
@@ -3310,7 +3418,7 @@ async function handleResponsesInner(
3310
3418
  // Body is a replayable string; nothing has streamed to the client yet.
3311
3419
  upstreamResponse = await fetchWithTransientRetry(
3312
3420
  recovery => {
3313
- noteAttemptSend(logCtx.activeAttempt, passthroughEstimate, recovery);
3421
+ noteDiagnosticAttempt(logCtx.activeAttempt, passthroughEstimate, recovery, route.provider.adapter);
3314
3422
  return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
3315
3423
  method: request.method,
3316
3424
  headers: request.headers,
@@ -3351,10 +3459,17 @@ async function handleResponsesInner(
3351
3459
  upstream.abort();
3352
3460
  return { failed: formatErrorResponse(502, "upstream_error", "Recovery changed the provider wire unexpectedly") };
3353
3461
  }
3462
+ try {
3463
+ assertGoogleOptionsRoute(retryAdapter, route.provider);
3464
+ } catch (err) {
3465
+ upstream.abort();
3466
+ return { failed: formatErrorResponse(400, "invalid_request_error", redactSecretString(err instanceof Error ? err.message : String(err))) };
3467
+ }
3354
3468
  try {
3355
3469
  request = await retryAdapter.buildRequest(parsed, {
3356
3470
  headers: selectedForwardHeaders,
3357
3471
  translatorBudget,
3472
+ ...(observeAntigravityProviderError ? { onProviderError: observeAntigravityProviderError } : {}),
3358
3473
  });
3359
3474
  refreshRoutedNamespaceToolAliases(request);
3360
3475
  recordAdapterReasoning(logCtx, request);
@@ -3380,7 +3495,7 @@ async function handleResponsesInner(
3380
3495
  try {
3381
3496
  return await fetchWithTransientRetry(
3382
3497
  innerRecovery => {
3383
- noteAttemptSend(logCtx.activeAttempt, passthroughEstimate, innerRecovery ?? recovery);
3498
+ noteDiagnosticAttempt(logCtx.activeAttempt, passthroughEstimate, innerRecovery ?? recovery, retryAdapter.name);
3384
3499
  return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
3385
3500
  method: request.method,
3386
3501
  headers: request.headers,
@@ -3428,6 +3543,14 @@ async function handleResponsesInner(
3428
3543
  releaseCodexAuthContextProbeLease(authCtx);
3429
3544
  return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
3430
3545
  }
3546
+ if (isAntigravityOAuth && refreshed.accountId !== antigravityAccountId) {
3547
+ upstream.abort();
3548
+ return formatErrorResponse(401, "authentication_error", "Antigravity OAuth account changed during refresh");
3549
+ }
3550
+ if (isAntigravityOAuth && !refreshed.projectId) {
3551
+ upstream.abort();
3552
+ return formatErrorResponse(400, "invalid_request_error", "Antigravity project unavailable — re-run `ocx login google-antigravity`");
3553
+ }
3431
3554
  sentOAuthSnapshot = refreshed;
3432
3555
  replayOAuthCredentialSnapshot = {
3433
3556
  accountId: refreshed.accountId,
@@ -3436,6 +3559,9 @@ async function handleResponsesInner(
3436
3559
  if (route.providerName === "kiro") {
3437
3560
  parsed._kiroAuthContext = { ...(refreshed.kiro ?? {}) };
3438
3561
  }
3562
+ if (isAntigravityOAuth) {
3563
+ route.provider = { ...route.provider, project: refreshed.projectId };
3564
+ }
3439
3565
  const refreshedProvider = resolveProviderTransport(
3440
3566
  route.providerName,
3441
3567
  { ...route.provider, apiKey: refreshed.accessToken },
@@ -3466,9 +3592,11 @@ async function handleResponsesInner(
3466
3592
  logCtx.accountLogLabel,
3467
3593
  );
3468
3594
  try {
3595
+ assertGoogleOptionsRoute(refreshedAdapter, refreshedProvider);
3469
3596
  request = await refreshedAdapter.buildRequest(parsed, {
3470
3597
  headers: selectedForwardHeaders,
3471
3598
  translatorBudget,
3599
+ ...(observeAntigravityProviderError ? { onProviderError: observeAntigravityProviderError } : {}),
3472
3600
  });
3473
3601
  refreshRoutedNamespaceToolAliases(request);
3474
3602
  recordAdapterReasoning(logCtx, request);
@@ -3483,7 +3611,7 @@ async function handleResponsesInner(
3483
3611
  try {
3484
3612
  upstreamResponse = await fetchWithTransientRetry(
3485
3613
  recovery => {
3486
- noteAttemptSend(logCtx.activeAttempt, passthroughEstimate, recovery ?? "oauth-401");
3614
+ noteDiagnosticAttempt(logCtx.activeAttempt, passthroughEstimate, recovery ?? "oauth-401", refreshedAdapter.name);
3487
3615
  return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
3488
3616
  method: request.method,
3489
3617
  headers: request.headers,
@@ -3545,7 +3673,7 @@ async function handleResponsesInner(
3545
3673
  recovery => {
3546
3674
  // The first send of every replay is itself a rate-limit retry; inner transient-5xx
3547
3675
  // recoveries keep their own label (recovery is provided for those).
3548
- noteAttemptSend(logCtx.activeAttempt, passthroughEstimate, recovery ?? "rate-limit-429");
3676
+ noteDiagnosticAttempt(logCtx.activeAttempt, passthroughEstimate, recovery ?? "rate-limit-429", route.provider.adapter);
3549
3677
  return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
3550
3678
  method: request.method,
3551
3679
  headers: request.headers,
@@ -3853,6 +3981,7 @@ async function handleResponsesInner(
3853
3981
  ? createUndeclaredToolCallGuardBlockRewrite(
3854
3982
  declaredWireToolNames,
3855
3983
  declaredNamelessClientCallTypes,
3984
+ providerExecutedCallTypes,
3856
3985
  )
3857
3986
  : undefined,
3858
3987
  ].filter((rewrite): rewrite is NonNullable<typeof rewrite> => rewrite !== undefined);
@@ -4074,6 +4203,7 @@ async function handleResponsesInner(
4074
4203
  JSON.parse(clientJson),
4075
4204
  declaredWireToolNames,
4076
4205
  declaredNamelessClientCallTypes,
4206
+ providerExecutedCallTypes,
4077
4207
  );
4078
4208
  } catch {
4079
4209
  return undefined;
@@ -4271,7 +4401,8 @@ async function handleResponsesInner(
4271
4401
  ...(vidPlan ? { videoPlan: vidPlan } : {}),
4272
4402
  forwardHeaders: selectedForwardHeaders,
4273
4403
  onAttemptSend: (recovery?: AttemptRecoveryKind) =>
4274
- noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery),
4404
+ noteDiagnosticAttempt(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery, adapter.name),
4405
+ ...(diagnosticContext ? { diagnostic: diagnosticContext } : {}),
4275
4406
  abortSignal: options.abortSignal,
4276
4407
  maxRounds: imgPlan && vidPlan
4277
4408
  ? clampImageMaxRounds(Math.min(config.images?.maxRounds ?? 3, config.images?.videoMaxRounds ?? 2))
@@ -4286,6 +4417,7 @@ async function handleResponsesInner(
4286
4417
  recordAdapterReasoning(logCtx, request);
4287
4418
  recordAdapterTier(logCtx, request);
4288
4419
  },
4420
+ validateAdapter: (requestParsed, candidate) => assertGoogleOptionsRoute(candidate, route.provider, requestParsed),
4289
4421
  ...(vidPlan?.timeoutMs ? { videoTimeoutMs: vidPlan.timeoutMs } : {}),
4290
4422
  onUsage: usage => {
4291
4423
  // Cursor may assign _cursorConversationId inside the image loop's first runTurn;
@@ -4375,8 +4507,10 @@ async function handleResponsesInner(
4375
4507
  recordAdapterReasoning(logCtx, request);
4376
4508
  recordAdapterTier(logCtx, request);
4377
4509
  },
4510
+ validateAdapter: (requestParsed, candidate) => assertGoogleOptionsRoute(candidate, route.provider, requestParsed),
4378
4511
  onAttemptSend: (recovery?: AttemptRecoveryKind) =>
4379
- noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery),
4512
+ noteDiagnosticAttempt(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery, adapter.name),
4513
+ ...(diagnosticContext ? { diagnostic: diagnosticContext } : {}),
4380
4514
  onUsage: usage => {
4381
4515
  logCtx.usageFromBridge = true;
4382
4516
  if (usage) {
@@ -4465,10 +4599,14 @@ async function handleResponsesInner(
4465
4599
  pacingSlotAcquired = false,
4466
4600
  ): Promise<void> => {
4467
4601
  try {
4602
+ if (diagnosticContext) {
4603
+ diagnosticContext.recovery = recovery;
4604
+ diagnosticContext.attempt = logCtx.activeAttempt?.ordinal;
4605
+ }
4468
4606
  if (!pacingSlotAcquired) {
4469
4607
  await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
4470
4608
  }
4471
- noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery);
4609
+ noteDiagnosticAttempt(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery, adapter.name);
4472
4610
  const runTurnProviderFetch = providerFetch(
4473
4611
  route.provider,
4474
4612
  options.codexWsRuntimeIdentity,
@@ -4522,13 +4660,15 @@ async function handleResponsesInner(
4522
4660
  onBacklogExceeded: () => runTurnAbort.abort(),
4523
4661
  });
4524
4662
  void runTurnAttempt(retryQueue, "empty-completion");
4525
- return retryQueue.stream();
4663
+ return diagnoseAdapterEvents(retryQueue.stream(), adapter.name, diagnosticRequestId, logCtx, adapterDiagnosticState);
4526
4664
  };
4527
4665
 
4528
4666
  const { toolNsMap, declaredToolNames, toolParameterSchemas, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
4529
4667
  if (parsed.stream) {
4530
4668
  void runTurn();
4531
- let eventSource: AsyncIterable<AdapterEvent> = queue.stream();
4669
+ let eventSource: AsyncIterable<AdapterEvent> = diagnoseAdapterEvents(
4670
+ queue.stream(), adapter.name, diagnosticRequestId, logCtx, adapterDiagnosticState,
4671
+ );
4532
4672
  if (options.comboAttempt) {
4533
4673
  const preflight = await preflightAdapterEvents(eventSource);
4534
4674
  if (preflight.error || preflight.empty) {
@@ -4566,6 +4706,7 @@ async function handleResponsesInner(
4566
4706
  // grok-build's strict decoder dies on the typed response.heartbeat frame; its
4567
4707
  // eventsource layer tolerates comment keep-alives. Codex needs the opposite.
4568
4708
  ...(logCtx.surface === "grok" ? { heartbeatStyle: "comment" as const } : {}),
4709
+ ...(diagnosticContext ? { diagnostic: diagnosticContext } : {}),
4569
4710
  onUsage: usage => {
4570
4711
  // Raw adapter usage, pre wire-normalization: the bridged SSE now always carries
4571
4712
  // zero-default detail objects, so provenance must come from here (cache_detail_missing).
@@ -4674,7 +4815,12 @@ async function handleResponsesInner(
4674
4815
  let initialRequest: AdapterRequest | undefined;
4675
4816
  let inputTokenEstimate: number | undefined;
4676
4817
  try {
4677
- initialRequest = await activeAdapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
4818
+ assertGoogleOptionsRoute(activeAdapter, route.provider);
4819
+ initialRequest = await activeAdapter.buildRequest(parsed, {
4820
+ headers: selectedForwardHeaders,
4821
+ translatorBudget,
4822
+ ...(observeAntigravityProviderError ? { onProviderError: observeAntigravityProviderError } : {}),
4823
+ });
4678
4824
  refreshRoutedNamespaceToolAliases(initialRequest);
4679
4825
  recordAdapterReasoning(logCtx, initialRequest);
4680
4826
  recordAdapterTier(logCtx, initialRequest);
@@ -4711,7 +4857,7 @@ async function handleResponsesInner(
4711
4857
  let upstreamResponse: Response;
4712
4858
  try {
4713
4859
  if (activeAdapter.fetchResponse) {
4714
- noteAttemptSend(logCtx.activeAttempt, inputTokenEstimate);
4860
+ noteDiagnosticAttempt(logCtx.activeAttempt, inputTokenEstimate, undefined, activeAdapter.name);
4715
4861
  await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, upstream.signal);
4716
4862
  upstreamResponse = await activeAdapter.fetchResponse(builtInitialRequest, {
4717
4863
  abortSignal: upstream.signal,
@@ -4720,6 +4866,7 @@ async function handleResponsesInner(
4720
4866
  executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
4721
4867
  providerName: route.providerName,
4722
4868
  modelId: route.modelId,
4869
+ pacingSlotAcquired: true,
4723
4870
  }),
4724
4871
  ...(antigravityAccountId ? { accountId: antigravityAccountId } : {}),
4725
4872
  });
@@ -4731,7 +4878,7 @@ async function handleResponsesInner(
4731
4878
  const fetchWithRetryPolicy = route.provider.adapter === "google" ? fetchWithTransientRetry : fetchWithResetRetry;
4732
4879
  upstreamResponse = await fetchWithRetryPolicy(
4733
4880
  recovery => {
4734
- noteAttemptSend(logCtx.activeAttempt, inputTokenEstimate, recovery);
4881
+ noteDiagnosticAttempt(logCtx.activeAttempt, inputTokenEstimate, recovery, activeAdapter.name);
4735
4882
  return fetchWithHeaderTimeout(builtInitialRequest.url, applyUpstreamRecoveryInit({
4736
4883
  method: builtInitialRequest.method,
4737
4884
  headers: builtInitialRequest.headers,
@@ -4782,6 +4929,13 @@ async function handleResponsesInner(
4782
4929
  const rebuildAndRefetch = async (
4783
4930
  recovery: AttemptRecoveryKind,
4784
4931
  ): Promise<Response | { failed: Response }> => {
4932
+ try {
4933
+ assertGoogleOptionsRoute(activeAdapter, route.provider);
4934
+ } catch (err) {
4935
+ cleanupUpstreamAbort();
4936
+ upstream.abort();
4937
+ return { failed: formatErrorResponse(400, "invalid_request_error", redactSecretString(err instanceof Error ? err.message : String(err))) };
4938
+ }
4785
4939
  let retryRequest: AdapterRequest;
4786
4940
  if (sameTargetRequest !== undefined && sameTargetParsed === parsed && sameTargetToken === transportToken) {
4787
4941
  // Same target (key/adapter/parsed/tier unchanged): replay the exact cached request.
@@ -4792,6 +4946,7 @@ async function handleResponsesInner(
4792
4946
  headers: selectedForwardHeaders,
4793
4947
  translatorBudget,
4794
4948
  ...(imageTierBias > 0 ? { imageTierBias } : {}),
4949
+ ...(observeAntigravityProviderError ? { onProviderError: observeAntigravityProviderError } : {}),
4795
4950
  });
4796
4951
  recordAdapterReasoning(logCtx, retryRequest);
4797
4952
  recordAdapterTier(logCtx, retryRequest);
@@ -4816,7 +4971,7 @@ async function handleResponsesInner(
4816
4971
  if (retryEstimate !== undefined) logCtx.usageLogInputTokens = retryEstimate;
4817
4972
  logCtx.providerAdapter = activeAdapter.name;
4818
4973
  sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
4819
- noteAttemptSend(logCtx.activeAttempt, retryEstimate, recovery);
4974
+ noteDiagnosticAttempt(logCtx.activeAttempt, retryEstimate, recovery, activeAdapter.name);
4820
4975
  try {
4821
4976
  try {
4822
4977
  if (activeAdapter.fetchResponse) {
@@ -4828,6 +4983,7 @@ async function handleResponsesInner(
4828
4983
  executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
4829
4984
  providerName: route.providerName,
4830
4985
  modelId: route.modelId,
4986
+ pacingSlotAcquired: true,
4831
4987
  }),
4832
4988
  ...(antigravityAccountId ? { accountId: antigravityAccountId } : {}),
4833
4989
  });
@@ -4869,6 +5025,14 @@ async function handleResponsesInner(
4869
5025
  cleanupUpstreamAbort();
4870
5026
  return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
4871
5027
  }
5028
+ if (isAntigravityOAuth && refreshed.accountId !== antigravityAccountId) {
5029
+ cleanupUpstreamAbort();
5030
+ return formatErrorResponse(401, "authentication_error", "Antigravity OAuth account changed during refresh");
5031
+ }
5032
+ if (isAntigravityOAuth && !refreshed.projectId) {
5033
+ cleanupUpstreamAbort();
5034
+ return formatErrorResponse(400, "invalid_request_error", "Antigravity project unavailable — re-run `ocx login google-antigravity`");
5035
+ }
4872
5036
  sentOAuthSnapshot = refreshed;
4873
5037
  replayOAuthCredentialSnapshot = {
4874
5038
  accountId: refreshed.accountId,
@@ -4877,6 +5041,9 @@ async function handleResponsesInner(
4877
5041
  if (route.providerName === "kiro") {
4878
5042
  parsed._kiroAuthContext = { ...(refreshed.kiro ?? {}) };
4879
5043
  }
5044
+ if (isAntigravityOAuth) {
5045
+ route.provider = { ...route.provider, project: refreshed.projectId };
5046
+ }
4880
5047
  const refreshedProvider = resolveProviderTransport(
4881
5048
  route.providerName,
4882
5049
  { ...route.provider, apiKey: refreshed.accessToken },
@@ -4972,6 +5139,37 @@ async function handleResponsesInner(
4972
5139
  upstreamResponse = result;
4973
5140
  }
4974
5141
 
5142
+ // Antigravity never rotates accounts. Record the account cooldown and allow only one
5143
+ // short, abort-aware replay on the same credential; longer Retry-After values surface to
5144
+ // the client so a conversation cannot churn through accounts or requests.
5145
+ if (upstreamResponse.status === 403 && isAntigravityOAuth && antigravityAccountId) {
5146
+ recordAntigravityCooldown(antigravityAccountId, upstreamResponse.headers.get("retry-after"), Date.now(), "geoblock");
5147
+ }
5148
+ if (upstreamResponse.status === 429 && isAntigravityOAuth && antigravityAccountId) {
5149
+ recordAntigravityCooldown(antigravityAccountId, upstreamResponse.headers.get("retry-after"));
5150
+ const retryAfter = retryableAntigravity429DelayMs(upstreamResponse.headers.get("retry-after"));
5151
+ if (!antigravity429RetryAttempted && retryAfter !== null) {
5152
+ antigravity429RetryAttempted = true;
5153
+ try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already closed */ }
5154
+ try {
5155
+ await sleepWithAbort(retryAfter, upstream.signal);
5156
+ } catch {
5157
+ cleanupUpstreamAbort();
5158
+ upstream.abort();
5159
+ return clientCancelledResponse();
5160
+ }
5161
+ if (options.abortSignal?.aborted || upstream.signal.aborted) {
5162
+ cleanupUpstreamAbort();
5163
+ upstream.abort();
5164
+ return clientCancelledResponse();
5165
+ }
5166
+ const result = await rebuildAndRefetch("rate-limit-429");
5167
+ if ("failed" in result) return result.failed;
5168
+ upstreamResponse = result;
5169
+ continue recovery;
5170
+ }
5171
+ }
5172
+
4975
5173
  // Opt-in Anthropic OAuth account pool (#294): cool the failed account and retry
4976
5174
  // with another eligible OAuth account (bounded per request). Disabled by default.
4977
5175
  while (
@@ -5008,75 +5206,6 @@ async function handleResponsesInner(
5008
5206
  break;
5009
5207
  }
5010
5208
  }
5011
- // Antigravity OAuth accounts use the same bounded pre-stream carousel as Anthropic, but
5012
- // their process-local routing module also excludes accounts cooled by quota/rate-limit
5013
- // responses. Geoblocked 403s intentionally never enter this loop.
5014
- while (
5015
- upstreamResponse.status === 429
5016
- && route.providerName === "google-antigravity"
5017
- && route.provider.googleMode === "cloud-code-assist"
5018
- && antigravityAccountId
5019
- && antigravityFailovers < 3
5020
- ) {
5021
- const stickWaitMs = antigravity429StickWaitMs(antigravityAccountId);
5022
- if (stickWaitMs !== null) {
5023
- await Bun.sleep(stickWaitMs);
5024
- try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
5025
- invalidateSameTargetRequest();
5026
- activeAdapter = resolveAdapter(
5027
- resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
5028
- config.cacheRetention,
5029
- );
5030
- bindRouteReasoningReplayScope({
5031
- parsed,
5032
- providerName: route.providerName,
5033
- provider: route.provider,
5034
- adapterName: activeAdapter.name,
5035
- codexAuthContext: authCtx,
5036
- forwardHeaders: selectedForwardHeaders,
5037
- });
5038
- const result = await rebuildAndRefetch("rate-limit-429");
5039
- if ("failed" in result) return result.failed;
5040
- upstreamResponse = result;
5041
- continue;
5042
- }
5043
- const nextAccountId = rotateAntigravityAccountOn429(antigravityAccountId, antigravitySessionKey);
5044
- if (!nextAccountId) break;
5045
- try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
5046
- try {
5047
- const accessToken = await getValidAccessTokenForAccount("google-antigravity", nextAccountId);
5048
- const nextCredential = getAccountCredential("google-antigravity", nextAccountId);
5049
- const bound = bindAntigravityProject(
5050
- { ...route.provider, apiKey: accessToken },
5051
- nextCredential?.projectId,
5052
- );
5053
- if (!bound.ok) {
5054
- return formatErrorResponse(bound.status, bound.type, bound.message);
5055
- }
5056
- antigravityAccountId = nextAccountId;
5057
- antigravityFailovers += 1;
5058
- route.provider = bound.provider;
5059
- replayOAuthCredentialSnapshot = undefined;
5060
- invalidateSameTargetRequest();
5061
- activeAdapter = resolveAdapter(
5062
- resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
5063
- config.cacheRetention,
5064
- );
5065
- bindRouteReasoningReplayScope({
5066
- parsed,
5067
- providerName: route.providerName,
5068
- provider: route.provider,
5069
- adapterName: activeAdapter.name,
5070
- codexAuthContext: authCtx,
5071
- forwardHeaders: selectedForwardHeaders,
5072
- });
5073
- const result = await rebuildAndRefetch("rate-limit-429");
5074
- if ("failed" in result) return result.failed;
5075
- upstreamResponse = result;
5076
- } catch {
5077
- break;
5078
- }
5079
- }
5080
5209
  if (
5081
5210
  upstreamResponse.status === 402
5082
5211
  && cursorPoolAccountId
@@ -5296,6 +5425,16 @@ async function handleResponsesInner(
5296
5425
  nextParsed: OcxParsedRequest,
5297
5426
  initialRecoveryKind?: AttemptRecoveryKind,
5298
5427
  ): AsyncGenerator<AdapterEvent> {
5428
+ const requestValidationEvent = (error: unknown): AdapterEvent | undefined => {
5429
+ if (!(error instanceof OcxRequestValidationError)) return undefined;
5430
+ return {
5431
+ type: "error",
5432
+ status: error.status,
5433
+ errorType: "invalid_request_error",
5434
+ code: "invalid_request_error",
5435
+ message: error.message,
5436
+ };
5437
+ };
5299
5438
  let response: Response | undefined;
5300
5439
  // One-shot recovery label for the next top-of-loop continuation send after a failover rotation.
5301
5440
  let nextContinuationRecoveryKind: AttemptRecoveryKind | undefined = initialRecoveryKind;
@@ -5308,6 +5447,7 @@ async function handleResponsesInner(
5308
5447
  */
5309
5448
  const fetchContinuation = async (recoveryKind?: AttemptRecoveryKind): Promise<Response> => {
5310
5449
  let continuationRequest: AdapterRequest | undefined;
5450
+ assertGoogleOptionsRoute(activeAdapter, route.provider, nextParsed);
5311
5451
  if (sameTargetRequest !== undefined && sameTargetParsed === nextParsed && sameTargetToken === transportToken) {
5312
5452
  // Same target (key/adapter/parsed/tier unchanged): replay the exact cached request.
5313
5453
  continuationRequest = sameTargetRequest;
@@ -5317,6 +5457,7 @@ async function handleResponsesInner(
5317
5457
  headers: selectedForwardHeaders,
5318
5458
  translatorBudget,
5319
5459
  ...(imageTierBias > 0 ? { imageTierBias } : {}),
5460
+ ...(observeAntigravityProviderError ? { onProviderError: observeAntigravityProviderError } : {}),
5320
5461
  });
5321
5462
  recordAdapterReasoning(logCtx, continuationRequest);
5322
5463
  recordAdapterTier(logCtx, continuationRequest);
@@ -5342,8 +5483,13 @@ async function handleResponsesInner(
5342
5483
  // Optional recovery label for same-target / failover continuation sends.
5343
5484
  const replayKind: AttemptRecoveryKind | undefined = recoveryKind;
5344
5485
  try {
5486
+ if (diagnosticContext) {
5487
+ diagnosticContext.recovery = replayKind;
5488
+ diagnosticContext.attempt = logCtx.activeAttempt?.ordinal;
5489
+ diagnosticContext.adapterName = activeAdapter.name;
5490
+ }
5345
5491
  if (activeAdapter.fetchResponse) {
5346
- noteAttemptSend(logCtx.activeAttempt, continuationEstimate, replayKind);
5492
+ noteDiagnosticAttempt(logCtx.activeAttempt, continuationEstimate, replayKind, activeAdapter.name);
5347
5493
  await waitForProviderRequestSlot(route.providerName, route.provider, nextParsed.modelId, upstream.signal);
5348
5494
  return await activeAdapter.fetchResponse(builtContinuationRequest, {
5349
5495
  abortSignal: upstream.signal,
@@ -5352,6 +5498,7 @@ async function handleResponsesInner(
5352
5498
  executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
5353
5499
  providerName: route.providerName,
5354
5500
  modelId: nextParsed.modelId,
5501
+ pacingSlotAcquired: true,
5355
5502
  }),
5356
5503
  ...(antigravityAccountId ? { accountId: antigravityAccountId } : {}),
5357
5504
  });
@@ -5361,7 +5508,7 @@ async function handleResponsesInner(
5361
5508
  const fetchContinuationWithRetryPolicy = route.provider.adapter === "google" ? fetchWithTransientRetry : fetchWithResetRetry;
5362
5509
  return await fetchContinuationWithRetryPolicy(
5363
5510
  recovery => {
5364
- noteAttemptSend(logCtx.activeAttempt, continuationEstimate, recovery ?? replayKind);
5511
+ noteDiagnosticAttempt(logCtx.activeAttempt, continuationEstimate, recovery ?? replayKind, activeAdapter.name);
5365
5512
  return fetchWithHeaderTimeout(
5366
5513
  builtContinuationRequest.url,
5367
5514
  applyUpstreamRecoveryInit({
@@ -5390,7 +5537,10 @@ async function handleResponsesInner(
5390
5537
  nextContinuationRecoveryKind = undefined;
5391
5538
  response = await fetchContinuation(recoveryKind);
5392
5539
  } catch (error) {
5393
- if (options.abortSignal?.aborted || upstream.signal.aborted) {
5540
+ const validationEvent = requestValidationEvent(error);
5541
+ if (validationEvent) {
5542
+ yield validationEvent;
5543
+ } else if (options.abortSignal?.aborted || upstream.signal.aborted) {
5394
5544
  yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
5395
5545
  } else {
5396
5546
  yield { type: "error", message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
@@ -5436,7 +5586,10 @@ async function handleResponsesInner(
5436
5586
  try {
5437
5587
  response = await fetchContinuation("rate-limit-429");
5438
5588
  } catch (error) {
5439
- if (options.abortSignal?.aborted || upstream.signal.aborted) {
5589
+ const validationEvent = requestValidationEvent(error);
5590
+ if (validationEvent) {
5591
+ yield validationEvent;
5592
+ } else if (options.abortSignal?.aborted || upstream.signal.aborted) {
5440
5593
  yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
5441
5594
  } else {
5442
5595
  yield { type: "error", message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
@@ -5445,6 +5598,42 @@ async function handleResponsesInner(
5445
5598
  }
5446
5599
  }
5447
5600
 
5601
+ if (response.status === 403 && isAntigravityOAuth && antigravityAccountId) {
5602
+ recordAntigravityCooldown(antigravityAccountId, response.headers.get("retry-after"), Date.now(), "geoblock");
5603
+ }
5604
+ if (response.status === 429 && isAntigravityOAuth && antigravityAccountId) {
5605
+ recordAntigravityCooldown(antigravityAccountId, response.headers.get("retry-after"));
5606
+ const retryAfter = retryableAntigravity429DelayMs(response.headers.get("retry-after"));
5607
+ if (!antigravity429RetryAttempted && retryAfter !== null) {
5608
+ antigravity429RetryAttempted = true;
5609
+ try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
5610
+ try {
5611
+ await sleepWithAbort(retryAfter, upstream.signal);
5612
+ } catch {
5613
+ if (options.abortSignal?.aborted || upstream.signal.aborted) {
5614
+ yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
5615
+ } else {
5616
+ yield { type: "error", message: "Provider continuation failed: retry wait interrupted" };
5617
+ }
5618
+ return;
5619
+ }
5620
+ if (options.abortSignal?.aborted || upstream.signal.aborted) {
5621
+ yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
5622
+ return;
5623
+ }
5624
+ try {
5625
+ response = await fetchContinuation("rate-limit-429");
5626
+ } catch (error) {
5627
+ if (options.abortSignal?.aborted || upstream.signal.aborted) {
5628
+ yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
5629
+ } else {
5630
+ yield { type: "error", message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
5631
+ }
5632
+ return;
5633
+ }
5634
+ }
5635
+ }
5636
+
5448
5637
  if (response.status === 429 && hasKeyPoolFailover(route.provider)) {
5449
5638
  const rotated = rotateProviderTransportOn429(config, route.providerName, route.provider, {
5450
5639
  retryAfter: response.headers.get("retry-after"),
@@ -5651,7 +5840,13 @@ async function handleResponsesInner(
5651
5840
  const detachContinuationBodyGuard = cancelBodyOnAbort(response.body, upstream.signal);
5652
5841
  try {
5653
5842
  if (nextParsed.stream) {
5654
- yield* activeAdapter.parseStream(response, translatorBudget, logCtx.activeTierMetadata);
5843
+ yield* diagnoseAdapterEvents(
5844
+ activeAdapter.parseStream(response, translatorBudget, logCtx.activeTierMetadata),
5845
+ activeAdapter.name,
5846
+ diagnosticRequestId,
5847
+ logCtx,
5848
+ adapterDiagnosticState,
5849
+ );
5655
5850
  } else if (activeAdapter.parseResponse) {
5656
5851
  yield* await activeAdapter.parseResponse(response, translatorBudget, logCtx.activeTierMetadata);
5657
5852
  } else {
@@ -5661,7 +5856,10 @@ async function handleResponsesInner(
5661
5856
  detachContinuationBodyGuard();
5662
5857
  }
5663
5858
  } catch (error) {
5664
- if (options.abortSignal?.aborted) {
5859
+ const validationEvent = requestValidationEvent(error);
5860
+ if (validationEvent) {
5861
+ yield validationEvent;
5862
+ } else if (options.abortSignal?.aborted) {
5665
5863
  yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
5666
5864
  } else {
5667
5865
  yield { type: "error", message: `Provider continuation parse failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
@@ -5683,11 +5881,18 @@ async function handleResponsesInner(
5683
5881
  };
5684
5882
 
5685
5883
  if (parsed.stream) {
5686
- const initialEventStream = activeAdapter.parseStream(
5687
- upstreamResponse,
5688
- translatorBudget,
5689
- logCtx.activeTierMetadata,
5884
+ const initialEventStream = diagnoseAdapterEvents(
5885
+ activeAdapter.parseStream(upstreamResponse, translatorBudget, logCtx.activeTierMetadata),
5886
+ activeAdapter.name,
5887
+ diagnosticRequestId,
5888
+ logCtx,
5889
+ adapterDiagnosticState,
5690
5890
  );
5891
+ if (diagnosticContext) {
5892
+ diagnosticContext.adapterName = activeAdapter.name;
5893
+ diagnosticContext.attempt = logCtx.activeAttempt?.ordinal;
5894
+ diagnosticContext.recovery = logCtx.activeAttempt?.recoveryKinds.at(-1);
5895
+ }
5691
5896
  const eventStream = terminalGuardEnabled
5692
5897
  ? guardTerminalEventStream({
5693
5898
  parsed,
@@ -5723,6 +5928,7 @@ async function handleResponsesInner(
5723
5928
  ...(routedCompaction ? { compaction: true } : {}),
5724
5929
  // Same grok-surface split as the runTurn branch above.
5725
5930
  ...(logCtx.surface === "grok" ? { heartbeatStyle: "comment" as const } : {}),
5931
+ ...(diagnosticContext ? { diagnostic: diagnosticContext } : {}),
5726
5932
  onUsage: usage => {
5727
5933
  // Raw adapter usage, pre wire-normalization (see the runTurn branch above).
5728
5934
  logCtx.usageFromBridge = true;