@yansigit/opencodex 2.32.0 → 2.33.1

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