@yansigit/opencodex 2.31.3 → 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.
- package/README.md +2 -2
- package/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-DKLr4LTE.js +102 -0
- package/gui/dist/assets/index-DrSQdTRd.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +6 -5
- package/src/adapters/anthropic.ts +25 -13
- package/src/adapters/azure.ts +20 -4
- package/src/adapters/base.ts +5 -1
- package/src/adapters/command-code.ts +42 -10
- package/src/adapters/cursor/live-models.ts +8 -0
- package/src/adapters/cursor/live-transport.ts +1 -1
- package/src/adapters/cursor/native-exec-desktop.ts +16 -0
- package/src/adapters/cursor/protobuf-events.ts +158 -7
- package/src/adapters/cursor/protobuf-request.ts +33 -15
- package/src/adapters/cursor/request-builder.ts +4 -3
- package/src/adapters/cursor/tool-definitions.ts +27 -1
- package/src/adapters/cursor/types.ts +4 -3
- package/src/adapters/cursor.ts +9 -0
- package/src/adapters/google-antigravity-replay.ts +2 -2
- package/src/adapters/google-antigravity-wire.ts +7 -0
- package/src/adapters/google-errors.ts +6 -2
- package/src/adapters/google-http.ts +30 -7
- package/src/adapters/google-truncation.ts +5 -0
- package/src/adapters/google-wire-compiler.ts +38 -6
- package/src/adapters/google.ts +154 -33
- package/src/adapters/kiro-tools.ts +20 -9
- package/src/adapters/kiro.ts +0 -3
- package/src/adapters/openai-chat.ts +9 -0
- package/src/adapters/openai-responses.ts +4 -1
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-web-search.ts +7 -2
- package/src/bridge.ts +133 -24
- package/src/claude/context-windows.ts +16 -9
- package/src/cli/dispatch.ts +50 -2
- package/src/cli/doctor.ts +26 -13
- package/src/cli/help.ts +4 -3
- package/src/cli/index.ts +20 -6
- package/src/cli/models.ts +13 -3
- package/src/cli/observe.ts +20 -5
- package/src/cli/provider.ts +8 -1
- package/src/cli/registry.ts +7 -5
- package/src/cli/status.ts +25 -1
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +134 -0
- package/src/codex/app-server-processes.ts +3 -1
- package/src/codex/auth-api.ts +4 -2
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +12 -0
- package/src/codex/catalog/effort.ts +42 -12
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/model-metadata.ts +593 -0
- package/src/codex/catalog/parsing.ts +71 -27
- package/src/codex/catalog/provider-fetch.ts +189 -33
- package/src/codex/catalog/sync.ts +6 -5
- package/src/codex/convergence.ts +5 -0
- package/src/codex/desktop-app-restart.ts +342 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject.ts +49 -21
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/shim.ts +56 -3
- package/src/combos/failover.ts +27 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +81 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +308 -0
- package/src/config/provider-validation.ts +214 -0
- package/src/config.ts +153 -814
- package/src/generated/compatibility-version.json +232 -148
- package/src/images/loop.ts +37 -6
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +141 -0
- package/src/integrations/ownership.ts +10 -0
- package/src/integrations/state.ts +44 -5
- package/src/integrations/writer.ts +6 -0
- package/src/lib/azure-identity.ts +154 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/debug.ts +42 -0
- package/src/lib/errors.ts +14 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/provider-outbound.ts +45 -33
- package/src/lib/provider-tls-profile.ts +309 -0
- package/src/lib/proxy-env.ts +49 -0
- package/src/lib/redact.ts +10 -1
- package/src/lib/state-store-registrations.ts +2 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/antigravity-routing.ts +282 -236
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/command-code.ts +5 -16
- package/src/oauth/google-antigravity.ts +42 -5
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +15 -3
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +6 -0
- package/src/providers/antigravity-quota.ts +3 -1
- package/src/providers/api-keys.ts +2 -1
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/derive.ts +4 -0
- package/src/providers/key-failover.ts +5 -1
- package/src/providers/openai-tiers.ts +5 -0
- package/src/providers/provider-id-rewrite.ts +1 -0
- package/src/providers/quota.ts +59 -13
- package/src/providers/registry.ts +4 -2
- package/src/providers/request-pacing.ts +33 -6
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/custom-tool-compat.ts +132 -38
- package/src/responses/google-provider-options.ts +36 -0
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +14 -2
- package/src/responses/provider-opaque-metadata.ts +3 -3
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +37 -0
- package/src/responses/state.ts +94 -4
- package/src/router.ts +8 -2
- package/src/server/auth-cors.ts +37 -7
- package/src/server/images.ts +19 -35
- package/src/server/index.ts +102 -21
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +206 -16
- package/src/server/management/combo-routes.ts +6 -0
- package/src/server/management/config-routes.ts +35 -6
- package/src/server/management/context.ts +1 -1
- package/src/server/management/logs-usage-routes.ts +27 -6
- package/src/server/management/model-routes.ts +8 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +25 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +113 -15
- package/src/server/management/routing-profile-routes.ts +3 -0
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +2 -1
- package/src/server/request-log-conversation.ts +30 -0
- package/src/server/request-log.ts +21 -0
- package/src/server/responses/agent-task-recovery.ts +1 -1
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/combo-stream-preflight.ts +171 -0
- package/src/server/responses/compact.ts +36 -22
- package/src/server/responses/core.ts +584 -247
- package/src/server/responses/empty-completion-guard.ts +35 -6
- package/src/server/responses/fetch-helpers.ts +20 -102
- package/src/server/responses/v2-native-parent-override.ts +59 -0
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +41 -5
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/service.ts +9 -5
- package/src/types/config.ts +16 -1
- package/src/types/provider.ts +16 -0
- package/src/types/request.ts +34 -1
- package/src/types/tools.ts +114 -11
- package/src/types.ts +7 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/log.ts +16 -8
- package/src/usage/summary.ts +201 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/gemini-executor.ts +6 -4
- package/src/web-search/loop.ts +42 -6
- package/gui/dist/assets/index-CGoDO3uO.css +0 -1
- package/gui/dist/assets/index-Cxt5fZMP.js +0 -102
|
@@ -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,
|
|
@@ -22,8 +24,10 @@ import {
|
|
|
22
24
|
durableReplayDestinationIdentity,
|
|
23
25
|
durableReplayCredentialIdentity,
|
|
24
26
|
reasoningReplayKeyCredentialIdentity,
|
|
27
|
+
reasoningReplayOpaqueBlobRejectionMemoized,
|
|
25
28
|
reasoningReplayOAuthCredentialIdentity,
|
|
26
29
|
reasoningReplayServingIdentityChanged,
|
|
30
|
+
rememberReasoningReplayOpaqueBlobRejection,
|
|
27
31
|
} from "../../responses/reasoning-replay-cache";
|
|
28
32
|
import { awaitThoughtSignatureDurability, thoughtSignatureReplaySalt } from "../../responses/thought-signature-replay";
|
|
29
33
|
import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
|
|
@@ -68,9 +72,12 @@ import {
|
|
|
68
72
|
pickComboTarget,
|
|
69
73
|
targetKey,
|
|
70
74
|
} from "../../combos";
|
|
71
|
-
import { isInjectionDebugEnabled } from "../../lib/debug-settings";
|
|
75
|
+
import { isDebugEnabled, isInjectionDebugEnabled } from "../../lib/debug-settings";
|
|
76
|
+
import { debugStreamDiagnostic } from "../../lib/debug";
|
|
72
77
|
import { injectionDebugLog } from "../../lib/injection-debug-log";
|
|
78
|
+
import { OcxRequestValidationError } from "../../lib/errors";
|
|
73
79
|
import { resolveClientRetryAfter } from "../../lib/retry-after";
|
|
80
|
+
import { antigravityOAuthDestinationConfigError, isAntigravityOAuthProvider } from "../../lib/provider-tls-profile";
|
|
74
81
|
import { enrichOpenCodeZenRateLimitMessage } from "../../providers/opencode-zen-rate-limit";
|
|
75
82
|
import { modelInList, namespacedToolName } from "../../types";
|
|
76
83
|
import type {
|
|
@@ -88,6 +95,7 @@ import {
|
|
|
88
95
|
forceRefreshOAuthAccessSnapshot,
|
|
89
96
|
getOAuthCredentialApiBaseUrl,
|
|
90
97
|
getValidAccessTokenForAccount,
|
|
98
|
+
getValidAccessTokenSnapshotForAccount,
|
|
91
99
|
getValidAccessTokenSnapshot,
|
|
92
100
|
publicOAuthAuthenticationErrorMessage,
|
|
93
101
|
type OAuthAccessSnapshot,
|
|
@@ -106,12 +114,12 @@ import {
|
|
|
106
114
|
rotateAnthropicAccountOn429,
|
|
107
115
|
} from "../../oauth/anthropic-routing";
|
|
108
116
|
import {
|
|
109
|
-
antigravity429StickWaitMs,
|
|
110
117
|
antigravitySessionKeyFromParts,
|
|
111
|
-
bindAntigravityProject,
|
|
112
118
|
bindAntigravitySessionAffinity,
|
|
119
|
+
recordAntigravityCooldown,
|
|
120
|
+
recordAntigravitySyntheticFailure,
|
|
113
121
|
resolveAntigravityAccountForSession,
|
|
114
|
-
|
|
122
|
+
retryableAntigravity429DelayMs,
|
|
115
123
|
} from "../../oauth/antigravity-routing";
|
|
116
124
|
import {
|
|
117
125
|
CURSOR_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
@@ -133,16 +141,12 @@ import {
|
|
|
133
141
|
applyCodexAuthContextToProvider,
|
|
134
142
|
codexPoolAffinityKey,
|
|
135
143
|
CodexAccountCooldownError,
|
|
136
|
-
codexMainProfileDrainingResponse,
|
|
137
|
-
cooldownErrorResponse,
|
|
138
144
|
CodexAuthContextError,
|
|
139
|
-
CodexDirectAuthenticationError,
|
|
140
145
|
CodexMainProfileDrainingError,
|
|
141
146
|
CodexPoolAuthenticationError,
|
|
142
147
|
CodexThreadAffinityExpiredError,
|
|
143
148
|
headersForCodexAuthContext,
|
|
144
149
|
materializeCodexUpstreamAuth,
|
|
145
|
-
CodexMainSubstitutionUnavailableError,
|
|
146
150
|
isCodexAuthContextUsable,
|
|
147
151
|
resolveCodexAuthContext,
|
|
148
152
|
codexProbeLeaseId,
|
|
@@ -172,6 +176,7 @@ import {
|
|
|
172
176
|
fetchWithResetRetry,
|
|
173
177
|
fetchWithTransientRetry,
|
|
174
178
|
prepareSameTarget429Wait,
|
|
179
|
+
sleepWithAbort,
|
|
175
180
|
} from "../../lib/upstream-retry";
|
|
176
181
|
import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "../auth-cors";
|
|
177
182
|
import type { DataPlaneAdmission } from "../auth-cors";
|
|
@@ -213,7 +218,7 @@ import {
|
|
|
213
218
|
rotateProviderTransportOn429,
|
|
214
219
|
} from "../../providers/key-failover";
|
|
215
220
|
import { shouldAttemptImageTierRetry } from "../image-retry";
|
|
216
|
-
import { resolveProviderTransport } from "../../providers/xai-transport";
|
|
221
|
+
import { isXaiResponsesDestination, resolveProviderTransport } from "../../providers/xai-transport";
|
|
217
222
|
import type { WsData } from "../ws-bridge";
|
|
218
223
|
import { codexAccountSelectionForTurn, registerTurn, trackStreamLifetime, unregisterTurn } from "../lifecycle";
|
|
219
224
|
import { redactSecretString, sanitizeLogMetadataString } from "../../lib/redact";
|
|
@@ -245,6 +250,7 @@ import {
|
|
|
245
250
|
import {
|
|
246
251
|
conversationIdFromResponsesRequest,
|
|
247
252
|
normalizeLogConversationId,
|
|
253
|
+
reasoningReplayConversationIdFromResponsesRequest,
|
|
248
254
|
sessionIdHeaderFromRequest,
|
|
249
255
|
} from "../request-log-conversation";
|
|
250
256
|
import type { AttemptRecoveryKind } from "../../usage/log";
|
|
@@ -291,6 +297,8 @@ import { createResponsesModelPayloadRewrite, rewriteResponsesModelJson } from ".
|
|
|
291
297
|
import type { EffectiveSubagentRoster, SpawnAgentSurface } from "../../codex/catalog";
|
|
292
298
|
|
|
293
299
|
import { buildToolBridgeMaps, collabSurface, injectDeveloperMessage, multiAgentGuidanceText } from "./collaboration";
|
|
300
|
+
import { decideV2NativeParentOverride } from "./v2-native-parent-override";
|
|
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";
|
|
296
304
|
import { classifyTransportFailureKind, transportErrorCode } from "../../lib/upstream-reachability";
|
|
@@ -326,11 +334,16 @@ import {
|
|
|
326
334
|
type RoutedNamespaceToolAliases,
|
|
327
335
|
} from "../../responses/namespace-tool-compat";
|
|
328
336
|
import {
|
|
337
|
+
collectDeclaredNamelessClientCallTypes,
|
|
329
338
|
collectDeclaredWireToolNames,
|
|
339
|
+
collectProviderExecutedCallTypes,
|
|
330
340
|
createUndeclaredToolCallGuardBlockRewrite,
|
|
341
|
+
currentTurnWireToolCatalogBody,
|
|
342
|
+
hasExplicitWireToolCatalog,
|
|
331
343
|
undeclaredToolCallMessage,
|
|
332
344
|
undeclaredToolCallName,
|
|
333
345
|
undeclaredToolCallNameInResponse,
|
|
346
|
+
type ProviderExecutedCallType,
|
|
334
347
|
} from "../responses-undeclared-tool-guard";
|
|
335
348
|
import { createGithubCopilotResponsesBlockRewrite } from "../github-copilot-responses-repair";
|
|
336
349
|
import { responsesJsonToSseStream } from "../responses-json-events";
|
|
@@ -339,6 +352,40 @@ import {
|
|
|
339
352
|
emptyCompletionRetryEnabled,
|
|
340
353
|
guardEmptyCompletionEventStream,
|
|
341
354
|
} from "./empty-completion-guard";
|
|
355
|
+
import { preflightComboStreamResponse } from "./combo-stream-preflight";
|
|
356
|
+
|
|
357
|
+
// runTurn adapters own an event queue and perform their combo preflight before
|
|
358
|
+
// bridging. A second byte-stream reader would reinterpret that transport's
|
|
359
|
+
// already-committed event boundary and can replay custom adapter work.
|
|
360
|
+
const runTurnAdapterSseResponses = new WeakSet<Response>();
|
|
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
|
+
}
|
|
342
389
|
|
|
343
390
|
/**
|
|
344
391
|
* Adapters whose continuation state must survive Codex's store:false requests.
|
|
@@ -535,6 +582,9 @@ function bindRouteReasoningReplayScope(args: {
|
|
|
535
582
|
if (reasoningReplayServingIdentityChanged(parsed._reasoningReplayScope)) {
|
|
536
583
|
parsed._stripReasoningEncryptedContent = true;
|
|
537
584
|
}
|
|
585
|
+
if (reasoningReplayOpaqueBlobRejectionMemoized(parsed._reasoningReplayScope)) {
|
|
586
|
+
parsed._stripReasoningEncryptedContent = true;
|
|
587
|
+
}
|
|
538
588
|
bindProviderContinuationForRoute(parsed, continuationOwner);
|
|
539
589
|
}
|
|
540
590
|
|
|
@@ -648,6 +698,26 @@ async function opaqueBlobRejectionBodyForRecovery(
|
|
|
648
698
|
}
|
|
649
699
|
}
|
|
650
700
|
|
|
701
|
+
/**
|
|
702
|
+
* Materialize an upstream error body only when the bounded reader observed a complete,
|
|
703
|
+
* display-safe payload. Partial timeout and over-limit prefixes are attacker-controlled,
|
|
704
|
+
* so callers keep their existing status-only fallback instead.
|
|
705
|
+
*/
|
|
706
|
+
export async function readDisplaySafeErrorText(
|
|
707
|
+
response: Response,
|
|
708
|
+
signal: AbortSignal,
|
|
709
|
+
fallback: string,
|
|
710
|
+
): Promise<string> {
|
|
711
|
+
try {
|
|
712
|
+
const body = await readBoundedResponseBody(response, { signal });
|
|
713
|
+
return body.displaySafe ? body.text : fallback;
|
|
714
|
+
} catch {
|
|
715
|
+
// Preserve the former Response.text().catch(fallback) contract. Request-abort
|
|
716
|
+
// classification remains owned by the surrounding response pipeline.
|
|
717
|
+
return fallback;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
651
721
|
function prepareOpaqueBlobRecovery(parsed: OcxParsedRequest): void {
|
|
652
722
|
parsed._stripReasoningEncryptedContent = true;
|
|
653
723
|
}
|
|
@@ -688,9 +758,20 @@ async function attemptOpaqueBlobRecovery(
|
|
|
688
758
|
}
|
|
689
759
|
|
|
690
760
|
args.guard.attempted = true;
|
|
761
|
+
const rejectedScope = args.parsed._reasoningReplayScope
|
|
762
|
+
? {
|
|
763
|
+
clientThreadId: args.parsed._reasoningReplayScope.clientThreadId,
|
|
764
|
+
...(args.parsed._reasoningReplayScope.current
|
|
765
|
+
? { current: { ...args.parsed._reasoningReplayScope.current } }
|
|
766
|
+
: {}),
|
|
767
|
+
}
|
|
768
|
+
: undefined;
|
|
691
769
|
prepareOpaqueBlobRecovery(args.parsed);
|
|
692
770
|
try { void args.response.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
693
771
|
const result = await rebuild("opaque-blob-rejection");
|
|
772
|
+
if (!("failed" in result) && result.ok) {
|
|
773
|
+
rememberReasoningReplayOpaqueBlobRejection(rejectedScope);
|
|
774
|
+
}
|
|
694
775
|
return "failed" in result
|
|
695
776
|
? { kind: "failed", response: result.failed }
|
|
696
777
|
: { kind: "recovered", response: result };
|
|
@@ -1517,44 +1598,20 @@ async function resolveResponsesCodexAuth(
|
|
|
1517
1598
|
substituteMainCredential,
|
|
1518
1599
|
};
|
|
1519
1600
|
} catch (err) {
|
|
1520
|
-
if (err instanceof CodexAccountCooldownError) {
|
|
1521
|
-
return { ok: false, response: cooldownErrorResponse(err, Date.now(), route.codexAccountNamespace) };
|
|
1522
|
-
}
|
|
1523
|
-
if (err instanceof CodexMainProfileDrainingError) {
|
|
1524
|
-
return { ok: false, response: codexMainProfileDrainingResponse() };
|
|
1525
|
-
}
|
|
1526
|
-
if (err instanceof CodexThreadAffinityExpiredError) {
|
|
1527
|
-
return {
|
|
1528
|
-
ok: false,
|
|
1529
|
-
response: formatErrorResponse(409, "invalid_request_error", "Codex thread account affinity expired; start a new session"),
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1532
1601
|
if (err instanceof CodexAuthContextError) {
|
|
1533
1602
|
const safeAccountLabel = route.codexAccountNamespace
|
|
1534
1603
|
? `${route.providerName}-${route.codexAccountNamespace}`
|
|
1535
1604
|
: formatCodexProviderForLog(route.providerName, err.accountId, config);
|
|
1536
1605
|
console.error(`[codex-auth] Pool account ${safeAccountLabel} token failed; reauthentication required`);
|
|
1537
|
-
return {
|
|
1538
|
-
ok: false,
|
|
1539
|
-
response: formatErrorResponse(401, "authentication_error", "Selected Codex account needs reauthentication"),
|
|
1540
|
-
};
|
|
1541
|
-
}
|
|
1542
|
-
if (err instanceof CodexPoolAuthenticationError) {
|
|
1543
|
-
return { ok: false, response: formatErrorResponse(401, "authentication_error", err.message) };
|
|
1544
|
-
}
|
|
1545
|
-
if (err instanceof CodexDirectAuthenticationError) {
|
|
1546
|
-
return { ok: false, response: formatErrorResponse(401, "authentication_error", err.message) };
|
|
1547
1606
|
}
|
|
1548
1607
|
if (err instanceof ForwardAdmissionCredentialError) {
|
|
1549
1608
|
return { ok: false, response: formatErrorResponse(401, "authentication_error", err.message) };
|
|
1550
1609
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
};
|
|
1557
|
-
}
|
|
1610
|
+
const response = mapCodexAuthContextErrorToResponse(err, {
|
|
1611
|
+
accountSelector: route.codexAccountNamespace,
|
|
1612
|
+
now: Date.now(),
|
|
1613
|
+
});
|
|
1614
|
+
if (response) return { ok: false, response };
|
|
1558
1615
|
throw err;
|
|
1559
1616
|
}
|
|
1560
1617
|
}
|
|
@@ -1932,6 +1989,31 @@ export async function handleComboResponses(
|
|
|
1932
1989
|
return clientCancelledResponse();
|
|
1933
1990
|
}
|
|
1934
1991
|
|
|
1992
|
+
if (response.ok && !runTurnAdapterSseResponses.has(response)) {
|
|
1993
|
+
const nativePassthrough = isNativePassthroughSseResponse(response);
|
|
1994
|
+
const eagerRelay = isEagerRelaySseResponse(response);
|
|
1995
|
+
let preflight;
|
|
1996
|
+
try {
|
|
1997
|
+
preflight = await preflightComboStreamResponse(response, childLog);
|
|
1998
|
+
} catch (error) {
|
|
1999
|
+
callbackGate.discard();
|
|
2000
|
+
if (options.abortSignal?.aborted) {
|
|
2001
|
+
retainCancelledAttempt();
|
|
2002
|
+
return clientCancelledResponse();
|
|
2003
|
+
}
|
|
2004
|
+
throw error;
|
|
2005
|
+
}
|
|
2006
|
+
if (preflight.kind === "failed") {
|
|
2007
|
+
callbackGate.discard();
|
|
2008
|
+
terminalRecorder?.("failed", preflight.response.status);
|
|
2009
|
+
response = preflight.response;
|
|
2010
|
+
} else {
|
|
2011
|
+
response = preflight.response;
|
|
2012
|
+
if (nativePassthrough) markNativePassthroughSseResponse(response);
|
|
2013
|
+
if (eagerRelay) markEagerRelaySseResponse(response);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
|
|
1935
2017
|
if (response.ok) {
|
|
1936
2018
|
if (!attempt) return response;
|
|
1937
2019
|
sealRequestAttemptIdentity(
|
|
@@ -1989,7 +2071,7 @@ export async function handleComboResponses(
|
|
|
1989
2071
|
);
|
|
1990
2072
|
finishRequestAttempt(
|
|
1991
2073
|
attempt,
|
|
1992
|
-
response.status,
|
|
2074
|
+
failure.response.status,
|
|
1993
2075
|
Date.now() - (started ?? Date.now()),
|
|
1994
2076
|
failure.usage,
|
|
1995
2077
|
);
|
|
@@ -2004,7 +2086,7 @@ export async function handleComboResponses(
|
|
|
2004
2086
|
return lastFailure;
|
|
2005
2087
|
}
|
|
2006
2088
|
console.warn(
|
|
2007
|
-
`[combo] ${comboId}: ${targetKey(pick.target)} failed with ${response.status} after ${started === undefined ? 0 : Date.now() - started}ms`,
|
|
2089
|
+
`[combo] ${comboId}: ${targetKey(pick.target)} failed with ${failure.response.status} after ${started === undefined ? 0 : Date.now() - started}ms`,
|
|
2008
2090
|
);
|
|
2009
2091
|
const nextPick = advanceComboAfterFailure(config, pick, {
|
|
2010
2092
|
retryAfter: failure.retryAfter,
|
|
@@ -2212,6 +2294,7 @@ async function handleResponsesInner(
|
|
|
2212
2294
|
let toolBridgeMaps: ReturnType<typeof buildToolBridgeMaps>;
|
|
2213
2295
|
try {
|
|
2214
2296
|
parsed = parseRequest(body);
|
|
2297
|
+
parsed._promptCacheKeyIsSharedCohort = options.promptCacheKeyIsSharedCohort === true;
|
|
2215
2298
|
toolBridgeMaps = buildToolBridgeMaps(parsed, translatorBudget);
|
|
2216
2299
|
if (previousResponseInputExpanded) parsed._previousResponseInputExpanded = true;
|
|
2217
2300
|
const providerContinuationCandidate = options.comboReplaySnapshot
|
|
@@ -2220,7 +2303,6 @@ async function handleResponsesInner(
|
|
|
2220
2303
|
if (providerContinuationCandidate) parsed._providerContinuationCandidate = providerContinuationCandidate;
|
|
2221
2304
|
if (inboundClientThreadId) {
|
|
2222
2305
|
parsed._clientThreadId = inboundClientThreadId;
|
|
2223
|
-
parsed._reasoningReplayScope = { clientThreadId: inboundClientThreadId };
|
|
2224
2306
|
} else if (
|
|
2225
2307
|
options.inboundWire === "anthropic"
|
|
2226
2308
|
&& options.promptCacheKeyIsSharedCohort !== true
|
|
@@ -2256,15 +2338,31 @@ async function handleResponsesInner(
|
|
|
2256
2338
|
...(force ? { force: true } : {}),
|
|
2257
2339
|
...(parsed._clientThreadId ? { clientThreadId: parsed._clientThreadId } : {}),
|
|
2258
2340
|
});
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2341
|
+
const resolvedConversationId = conversationIdFromResponsesRequest({
|
|
2342
|
+
clientThreadId: parsed._clientThreadId,
|
|
2343
|
+
sessionIdHeader: sessionIdHeaderFromRequest(req.headers),
|
|
2344
|
+
threadIdHeader: req.headers.get("thread-id"),
|
|
2345
|
+
cursorConversationId: parsed._cursorConversationId,
|
|
2346
|
+
});
|
|
2347
|
+
// _clientThreadId remains the routing/continuation identity supplied by Codex. Replay state uses
|
|
2348
|
+
// a dedicated raw conversation namespace so mixed headers that carry the same identity still
|
|
2349
|
+
// match, and a shared/synthetic session_id cannot coalesce distinct thread/Cursor conversations.
|
|
2350
|
+
// Keep an Anthropic prompt_cache_key scope already bound above (#1735/#1926).
|
|
2351
|
+
if (!parsed._reasoningReplayScope) {
|
|
2352
|
+
const reasoningReplayConversationId = reasoningReplayConversationIdFromResponsesRequest({
|
|
2263
2353
|
clientThreadId: parsed._clientThreadId,
|
|
2264
|
-
sessionIdHeader: sessionIdHeaderFromRequest(req.headers),
|
|
2265
2354
|
threadIdHeader: req.headers.get("thread-id"),
|
|
2266
2355
|
cursorConversationId: parsed._cursorConversationId,
|
|
2356
|
+
sessionIdHeader: sessionIdHeaderFromRequest(req.headers),
|
|
2267
2357
|
});
|
|
2358
|
+
if (reasoningReplayConversationId) {
|
|
2359
|
+
parsed._reasoningReplayScope = { clientThreadId: reasoningReplayConversationId };
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
// Prefer a pre-populated id (routed Claude) over Responses headers that may be
|
|
2363
|
+
// absent or synthetically injected (session_id from prompt_cache_key).
|
|
2364
|
+
if (!logCtx.conversationId) {
|
|
2365
|
+
logCtx.conversationId = resolvedConversationId;
|
|
2268
2366
|
}
|
|
2269
2367
|
logCtx.requestedModel = parsed.modelId;
|
|
2270
2368
|
logCtx.requestedEffort = parsed.options.reasoning;
|
|
@@ -2322,6 +2420,27 @@ async function handleResponsesInner(
|
|
|
2322
2420
|
return formatErrorResponse(404, "invalid_request_error", err instanceof Error ? err.message : String(err));
|
|
2323
2421
|
}
|
|
2324
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
|
+
|
|
2325
2444
|
const hasUnexpandedPreviousResponse = !!parsed.previousResponseId
|
|
2326
2445
|
&& parsed._previousResponseInputExpanded !== true;
|
|
2327
2446
|
// Exact account selectors are isolated from Pool-wide quota work. A canonical replay miss must
|
|
@@ -2445,6 +2564,7 @@ async function handleResponsesInner(
|
|
|
2445
2564
|
"_providerContinuationOwner",
|
|
2446
2565
|
"_cursorConversationId",
|
|
2447
2566
|
"_clientThreadId",
|
|
2567
|
+
"_promptCacheKeyIsSharedCohort",
|
|
2448
2568
|
"_reasoningReplayScope",
|
|
2449
2569
|
"_cursorIsolateConversation",
|
|
2450
2570
|
];
|
|
@@ -2609,14 +2729,28 @@ async function handleResponsesInner(
|
|
|
2609
2729
|
|
|
2610
2730
|
// OAuth providers: swap in a fresh access token (auto-refreshed) as the Bearer key, so the
|
|
2611
2731
|
// existing openai-chat / anthropic adapters authenticate with no change.
|
|
2612
|
-
const
|
|
2613
|
-
|
|
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");
|
|
2614
2742
|
let sentOAuthSnapshot: OAuthAccessSnapshot | undefined;
|
|
2615
2743
|
let replayOAuthCredentialSnapshot: Pick<OAuthAccessSnapshot, "accountId" | "generation"> | undefined;
|
|
2616
2744
|
let anthropicPoolAccountId: string | null = null;
|
|
2617
2745
|
let anthropicPoolFailovers = 0;
|
|
2618
|
-
let antigravityAccountId: string |
|
|
2619
|
-
let
|
|
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;
|
|
2620
2754
|
let cursorPoolAccountId: string | null = null;
|
|
2621
2755
|
let cursorPoolFailovers = 0;
|
|
2622
2756
|
const oauthSessionKeyParts = {
|
|
@@ -2629,18 +2763,16 @@ async function handleResponsesInner(
|
|
|
2629
2763
|
const anthropicSessionKey = route.providerName === "anthropic" && route.provider.authMode === "oauth"
|
|
2630
2764
|
? anthropicSessionKeyFromParts(oauthSessionKeyParts)
|
|
2631
2765
|
: null;
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
? antigravitySessionKeyFromParts(oauthSessionKeyParts)
|
|
2636
|
-
: null;
|
|
2766
|
+
if (isAntigravityOAuth) {
|
|
2767
|
+
antigravitySessionKey = antigravitySessionKeyFromParts(oauthSessionKeyParts);
|
|
2768
|
+
}
|
|
2637
2769
|
const cursorSessionKey = route.providerName === "cursor"
|
|
2638
2770
|
&& route.provider.authMode === "oauth"
|
|
2639
2771
|
? cursorSessionKeyFromParts({
|
|
2640
2772
|
clientThreadId: typeof parsed._clientThreadId === "string" ? parsed._clientThreadId : null,
|
|
2641
2773
|
})
|
|
2642
2774
|
: null;
|
|
2643
|
-
if (route.provider.authMode === "oauth") {
|
|
2775
|
+
if (route.provider.authMode === "oauth" || isAntigravityOAuth) {
|
|
2644
2776
|
try {
|
|
2645
2777
|
if (route.providerName === "anthropic" && isAnthropicAccountPoolEnabled(config)) {
|
|
2646
2778
|
const selection = resolveAnthropicAccountForSession(anthropicSessionKey, config);
|
|
@@ -2662,50 +2794,6 @@ async function handleResponsesInner(
|
|
|
2662
2794
|
promoteAnthropicActiveAccount(selection.accountId);
|
|
2663
2795
|
route.provider = { ...route.provider, apiKey: accessToken };
|
|
2664
2796
|
logCtx.provider = formatAnthropicProviderForLog("anthropic", selection.accountId, config);
|
|
2665
|
-
} else if (
|
|
2666
|
-
route.providerName === "google-antigravity"
|
|
2667
|
-
&& route.provider.googleMode === "cloud-code-assist"
|
|
2668
|
-
) {
|
|
2669
|
-
const selection = resolveAntigravityAccountForSession(antigravitySessionKey);
|
|
2670
|
-
if (!selection.accountId) {
|
|
2671
|
-
if (selection.reason === "all-cooled") {
|
|
2672
|
-
return formatErrorResponse(
|
|
2673
|
-
429,
|
|
2674
|
-
"rate_limit_error",
|
|
2675
|
-
"All Google Antigravity OAuth accounts are temporarily unavailable",
|
|
2676
|
-
);
|
|
2677
|
-
}
|
|
2678
|
-
return formatErrorResponse(
|
|
2679
|
-
401,
|
|
2680
|
-
"authentication_error",
|
|
2681
|
-
"No eligible Google Antigravity OAuth account available",
|
|
2682
|
-
);
|
|
2683
|
-
}
|
|
2684
|
-
let resolved = await getValidAccessTokenSnapshot(route.providerName);
|
|
2685
|
-
if (selection.accountId !== resolved.accountId) {
|
|
2686
|
-
const accessToken = await getValidAccessTokenForAccount("google-antigravity", selection.accountId);
|
|
2687
|
-
const nextCredential = getAccountCredential("google-antigravity", selection.accountId);
|
|
2688
|
-
resolved = {
|
|
2689
|
-
...resolved,
|
|
2690
|
-
accountId: selection.accountId,
|
|
2691
|
-
accessToken,
|
|
2692
|
-
// Always replace; omitting a missing id would keep the previous account's projectId.
|
|
2693
|
-
projectId: nextCredential?.projectId,
|
|
2694
|
-
};
|
|
2695
|
-
}
|
|
2696
|
-
replayOAuthCredentialSnapshot = {
|
|
2697
|
-
accountId: resolved.accountId,
|
|
2698
|
-
generation: resolved.generation,
|
|
2699
|
-
};
|
|
2700
|
-
if (selection.reason === "failover") replayOAuthCredentialSnapshot = undefined;
|
|
2701
|
-
route.provider = { ...route.provider, apiKey: resolved.accessToken };
|
|
2702
|
-
antigravityAccountId = selection.accountId;
|
|
2703
|
-
bindAntigravitySessionAffinity(antigravitySessionKey, selection.accountId);
|
|
2704
|
-
const bound = bindAntigravityProject(route.provider, resolved.projectId);
|
|
2705
|
-
if (!bound.ok) {
|
|
2706
|
-
return formatErrorResponse(bound.status, bound.type, bound.message);
|
|
2707
|
-
}
|
|
2708
|
-
route.provider = bound.provider;
|
|
2709
2797
|
} else if (
|
|
2710
2798
|
route.providerName === "cursor"
|
|
2711
2799
|
&& route.provider.authMode === "oauth"
|
|
@@ -2737,7 +2825,28 @@ async function handleResponsesInner(
|
|
|
2737
2825
|
route.provider = { ...route.provider, apiKey: resolved.accessToken };
|
|
2738
2826
|
logCtx.provider = formatCursorProviderForLog("cursor", selection.accountId);
|
|
2739
2827
|
} else {
|
|
2740
|
-
|
|
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
|
+
}
|
|
2741
2850
|
replayOAuthCredentialSnapshot = {
|
|
2742
2851
|
accountId: resolved.accountId,
|
|
2743
2852
|
generation: resolved.generation,
|
|
@@ -2749,6 +2858,14 @@ async function handleResponsesInner(
|
|
|
2749
2858
|
// Only genuinely accountless adapter calls leave the context undefined and use local/env fallback.
|
|
2750
2859
|
parsed._kiroAuthContext = { ...(resolved.kiro ?? {}) };
|
|
2751
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
|
+
}
|
|
2752
2869
|
}
|
|
2753
2870
|
} catch (err) {
|
|
2754
2871
|
if (err instanceof UnsupportedOAuthProviderError) {
|
|
@@ -2788,6 +2905,27 @@ async function handleResponsesInner(
|
|
|
2788
2905
|
delete logCtx.accountLogLabel;
|
|
2789
2906
|
}
|
|
2790
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
|
+
};
|
|
2791
2929
|
bindRouteReasoningReplayScope({
|
|
2792
2930
|
parsed,
|
|
2793
2931
|
providerName: route.providerName,
|
|
@@ -2802,6 +2940,7 @@ async function handleResponsesInner(
|
|
|
2802
2940
|
}
|
|
2803
2941
|
logCtx.providerAdapter = adapter.name;
|
|
2804
2942
|
try {
|
|
2943
|
+
assertGoogleOptionsRoute(adapter, adapterProvider);
|
|
2805
2944
|
adapter.validateRequest?.(parsed);
|
|
2806
2945
|
} catch (err) {
|
|
2807
2946
|
return formatErrorResponse(
|
|
@@ -2842,6 +2981,23 @@ async function handleResponsesInner(
|
|
|
2842
2981
|
);
|
|
2843
2982
|
if (passiveSubjectId) logCtx.activeAttempt.labRouteSubjectId = passiveSubjectId;
|
|
2844
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
|
+
};
|
|
2845
3001
|
const isPassthrough = "passthrough" in adapter && !!adapter.passthrough;
|
|
2846
3002
|
|
|
2847
3003
|
if (adapter.name === "kiro" && parsed.previousResponseId && !parsed._previousResponseInputExpanded) {
|
|
@@ -2976,6 +3132,7 @@ async function handleResponsesInner(
|
|
|
2976
3132
|
? new Map<string, { namespace: string; name: string }>()
|
|
2977
3133
|
: imageGenToolCallAliases(toolBridgeMaps.toolNsMap, parsed._rawBody, translatorBudget);
|
|
2978
3134
|
const routedCustomToolNames = new Set<string>();
|
|
3135
|
+
const routedCustomToolRepairNames = new Set<string>();
|
|
2979
3136
|
const routedToolSearchNames = new Set<string>();
|
|
2980
3137
|
// Local continuation cache for the ChatGPT passthrough. Codex WS turns chain with
|
|
2981
3138
|
// previous_response_id, ocx converts them to internal HTTP requests, and the ChatGPT Codex
|
|
@@ -2998,8 +3155,26 @@ async function handleResponsesInner(
|
|
|
2998
3155
|
+ `(model ${parsed.modelId}); forwarding without it — earlier turns may be missing from this request`,
|
|
2999
3156
|
);
|
|
3000
3157
|
}
|
|
3158
|
+
// Preserve the caller's readable catalog boundary before provider-specific normalization can
|
|
3159
|
+
// remove an unsupported final entry (for example xAI cached-only web search).
|
|
3160
|
+
const replayedInputPrefixLength = parsed._replayPrefixLen ?? 0;
|
|
3161
|
+
const clientToolAuthorizationBody = currentTurnWireToolCatalogBody(
|
|
3162
|
+
parsed._rawBody,
|
|
3163
|
+
replayedInputPrefixLength,
|
|
3164
|
+
);
|
|
3165
|
+
const clientExplicitWireToolCatalog = hasExplicitWireToolCatalog(clientToolAuthorizationBody);
|
|
3166
|
+
const clientDeclaredWireToolNames = collectDeclaredWireToolNames(clientToolAuthorizationBody);
|
|
3167
|
+
const clientDeclaredNamelessCallTypes = collectDeclaredNamelessClientCallTypes(
|
|
3168
|
+
clientToolAuthorizationBody,
|
|
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>();
|
|
3001
3175
|
let request: Awaited<ReturnType<typeof adapter.buildRequest>>;
|
|
3002
3176
|
try {
|
|
3177
|
+
assertGoogleOptionsRoute(adapter, adapterProvider);
|
|
3003
3178
|
request = await adapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
|
|
3004
3179
|
} catch (error) {
|
|
3005
3180
|
releaseCodexAuthContextProbeLease(authCtx);
|
|
@@ -3019,6 +3194,12 @@ async function handleResponsesInner(
|
|
|
3019
3194
|
|| toolBridgeMaps.toolNsMap.get(name)?.freeform === true
|
|
3020
3195
|
) routedCustomToolNames.add(name);
|
|
3021
3196
|
}
|
|
3197
|
+
for (const name of request.routedCustomToolRepairNames ?? []) {
|
|
3198
|
+
if (
|
|
3199
|
+
toolBridgeMaps.freeformToolNames.has(name)
|
|
3200
|
+
|| toolBridgeMaps.toolNsMap.get(name)?.freeform === true
|
|
3201
|
+
) routedCustomToolRepairNames.add(name);
|
|
3202
|
+
}
|
|
3022
3203
|
}
|
|
3023
3204
|
for (const name of request.convertedRoutedToolSearchNames ?? []) {
|
|
3024
3205
|
// The adapter already keeps this set empty when tool_choice forbids the private search.
|
|
@@ -3034,13 +3215,13 @@ async function handleResponsesInner(
|
|
|
3034
3215
|
// call it cannot execute, and the turn showed a bare `aborted` with the file untouched.
|
|
3035
3216
|
// Forward auth is the canonical ChatGPT backend speaking Codex's own protocol rather than a
|
|
3036
3217
|
// routed provider, so it keeps passing through unguarded, as it does for the rewrites above.
|
|
3037
|
-
// The guard needs a catalog to compare against, so it stands down when the request
|
|
3038
|
-
//
|
|
3039
|
-
// request can legitimately omit `tools` entirely and still receive a
|
|
3040
|
-
// understands — `tests/github-copilot-stream-contract.test.ts` sends
|
|
3041
|
-
// with no tools and Copilot answers with a `custom_tool_call` for
|
|
3042
|
-
//
|
|
3043
|
-
//
|
|
3218
|
+
// The guard needs a catalog to compare against, so it stands down when the request omits one.
|
|
3219
|
+
// An explicit empty catalog is still authoritative: it declares that no client tools may be
|
|
3220
|
+
// called. A passthrough request can legitimately omit `tools` entirely and still receive a call
|
|
3221
|
+
// the client understands — `tests/github-copilot-stream-contract.test.ts` sends
|
|
3222
|
+
// `{model, input, stream}` with no tools and Copilot answers with a `custom_tool_call` for
|
|
3223
|
+
// `apply_patch`. Policing an absent catalog truncates that turn. An unreadable body lands there
|
|
3224
|
+
// too because the proxy cannot establish the caller's declared authorization boundary.
|
|
3044
3225
|
const parseOutboundRequestBody = (bodyText: string): Record<string, unknown> | undefined => {
|
|
3045
3226
|
try {
|
|
3046
3227
|
const body = JSON.parse(bodyText) as unknown;
|
|
@@ -3051,16 +3232,46 @@ async function handleResponsesInner(
|
|
|
3051
3232
|
return undefined;
|
|
3052
3233
|
}
|
|
3053
3234
|
};
|
|
3054
|
-
let outboundRequestBody
|
|
3055
|
-
const declaredWireToolNames =
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3235
|
+
let outboundRequestBody: Record<string, unknown> | undefined;
|
|
3236
|
+
const declaredWireToolNames = new Set<string>();
|
|
3237
|
+
const declaredNamelessClientCallTypes = new Set<string>();
|
|
3238
|
+
let undeclaredToolGuardActive = false;
|
|
3239
|
+
const refreshUndeclaredToolGuard = (builtRequest: AdapterRequest): void => {
|
|
3240
|
+
outboundRequestBody = parseOutboundRequestBody(builtRequest.body);
|
|
3241
|
+
declaredWireToolNames.clear();
|
|
3242
|
+
// With no replay prefix the full outbound body belongs to this turn and its normalized
|
|
3243
|
+
// aliases are authoritative. A continuation's outbound body still contains historical
|
|
3244
|
+
// catalogs (and may promote historical tool-search definitions), so it can never widen the
|
|
3245
|
+
// current caller snapshot captured above.
|
|
3246
|
+
if (replayedInputPrefixLength === 0) {
|
|
3247
|
+
for (const name of collectDeclaredWireToolNames(outboundRequestBody)) {
|
|
3248
|
+
declaredWireToolNames.add(name);
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
for (const name of clientDeclaredWireToolNames) declaredWireToolNames.add(name);
|
|
3252
|
+
declaredNamelessClientCallTypes.clear();
|
|
3253
|
+
if (replayedInputPrefixLength === 0) {
|
|
3254
|
+
for (const callType of collectDeclaredNamelessClientCallTypes(outboundRequestBody)) {
|
|
3255
|
+
declaredNamelessClientCallTypes.add(callType);
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
for (const callType of clientDeclaredNamelessCallTypes) {
|
|
3259
|
+
declaredNamelessClientCallTypes.add(callType);
|
|
3260
|
+
}
|
|
3261
|
+
// On an ordinary request these maps capture caller-catalog identities that normalization may
|
|
3262
|
+
// replace on the outbound wire (for example a client image tool becoming hosted). On replay,
|
|
3263
|
+
// however, the parsed maps also contain historical catalog entries, so only the bounded
|
|
3264
|
+
// current-turn wire snapshot above may authorize a call.
|
|
3265
|
+
if (replayedInputPrefixLength === 0) {
|
|
3266
|
+
for (const name of toolBridgeMaps.declaredToolNames) declaredWireToolNames.add(name);
|
|
3267
|
+
}
|
|
3268
|
+
undeclaredToolGuardActive = (
|
|
3269
|
+
declaredWireToolNames.size > 0
|
|
3270
|
+
|| clientDeclaredNamelessCallTypes.size > 0
|
|
3271
|
+
|| clientExplicitWireToolCatalog
|
|
3272
|
+
) && route.provider.authMode !== "forward";
|
|
3273
|
+
};
|
|
3274
|
+
refreshUndeclaredToolGuard(request);
|
|
3064
3275
|
// A refused turn must not seed `previous_response_id` replay. The inspection branch reads the
|
|
3065
3276
|
// untouched upstream stream, so it can still observe a `response.completed` the client never
|
|
3066
3277
|
// received; checking the payload itself rather than a flag shared with the client relay keeps
|
|
@@ -3073,11 +3284,19 @@ async function handleResponsesInner(
|
|
|
3073
3284
|
// rejection is sticky for the whole turn, set from every parsed payload on the inspection side.
|
|
3074
3285
|
let inspectionSawUndeclaredTool = false;
|
|
3075
3286
|
const noteInspectedPayload = (payload: unknown) => {
|
|
3287
|
+
if (isAntigravityOAuth && antigravityAccountId) {
|
|
3288
|
+
recordAntigravitySyntheticFailure(antigravityAccountId, payload);
|
|
3289
|
+
}
|
|
3076
3290
|
// Gated on the same flag as the guard itself: with no readable catalog (or a forward-auth
|
|
3077
3291
|
// provider) every name looks undeclared, and flipping this would stop recording continuation
|
|
3078
3292
|
// state for exactly the passthrough traffic the guard deliberately stands down for.
|
|
3079
3293
|
if (!undeclaredToolGuardActive || inspectionSawUndeclaredTool) return;
|
|
3080
|
-
if (undeclaredToolCallName(
|
|
3294
|
+
if (undeclaredToolCallName(
|
|
3295
|
+
payload,
|
|
3296
|
+
declaredWireToolNames,
|
|
3297
|
+
declaredNamelessClientCallTypes,
|
|
3298
|
+
providerExecutedCallTypes,
|
|
3299
|
+
) !== undefined) {
|
|
3081
3300
|
inspectionSawUndeclaredTool = true;
|
|
3082
3301
|
}
|
|
3083
3302
|
};
|
|
@@ -3086,7 +3305,12 @@ async function handleResponsesInner(
|
|
|
3086
3305
|
if (inspectionSawUndeclaredTool) return;
|
|
3087
3306
|
if (
|
|
3088
3307
|
undeclaredToolGuardActive
|
|
3089
|
-
&& undeclaredToolCallNameInResponse(
|
|
3308
|
+
&& undeclaredToolCallNameInResponse(
|
|
3309
|
+
response,
|
|
3310
|
+
declaredWireToolNames,
|
|
3311
|
+
declaredNamelessClientCallTypes,
|
|
3312
|
+
providerExecutedCallTypes,
|
|
3313
|
+
) !== undefined
|
|
3090
3314
|
) {
|
|
3091
3315
|
return;
|
|
3092
3316
|
}
|
|
@@ -3194,7 +3418,7 @@ async function handleResponsesInner(
|
|
|
3194
3418
|
// Body is a replayable string; nothing has streamed to the client yet.
|
|
3195
3419
|
upstreamResponse = await fetchWithTransientRetry(
|
|
3196
3420
|
recovery => {
|
|
3197
|
-
|
|
3421
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, passthroughEstimate, recovery, route.provider.adapter);
|
|
3198
3422
|
return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
|
|
3199
3423
|
method: request.method,
|
|
3200
3424
|
headers: request.headers,
|
|
@@ -3235,10 +3459,17 @@ async function handleResponsesInner(
|
|
|
3235
3459
|
upstream.abort();
|
|
3236
3460
|
return { failed: formatErrorResponse(502, "upstream_error", "Recovery changed the provider wire unexpectedly") };
|
|
3237
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
|
+
}
|
|
3238
3468
|
try {
|
|
3239
3469
|
request = await retryAdapter.buildRequest(parsed, {
|
|
3240
3470
|
headers: selectedForwardHeaders,
|
|
3241
3471
|
translatorBudget,
|
|
3472
|
+
...(observeAntigravityProviderError ? { onProviderError: observeAntigravityProviderError } : {}),
|
|
3242
3473
|
});
|
|
3243
3474
|
refreshRoutedNamespaceToolAliases(request);
|
|
3244
3475
|
recordAdapterReasoning(logCtx, request);
|
|
@@ -3253,7 +3484,7 @@ async function handleResponsesInner(
|
|
|
3253
3484
|
? request.usageLog.inputTokens
|
|
3254
3485
|
: undefined;
|
|
3255
3486
|
if (passthroughEstimate !== undefined) logCtx.usageLogInputTokens = passthroughEstimate;
|
|
3256
|
-
|
|
3487
|
+
refreshUndeclaredToolGuard(request);
|
|
3257
3488
|
logCtx.providerAdapter = retryAdapter.name;
|
|
3258
3489
|
sealRequestAttemptIdentity(
|
|
3259
3490
|
logCtx.activeAttempt,
|
|
@@ -3264,7 +3495,7 @@ async function handleResponsesInner(
|
|
|
3264
3495
|
try {
|
|
3265
3496
|
return await fetchWithTransientRetry(
|
|
3266
3497
|
innerRecovery => {
|
|
3267
|
-
|
|
3498
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, passthroughEstimate, innerRecovery ?? recovery, retryAdapter.name);
|
|
3268
3499
|
return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
|
|
3269
3500
|
method: request.method,
|
|
3270
3501
|
headers: request.headers,
|
|
@@ -3312,6 +3543,14 @@ async function handleResponsesInner(
|
|
|
3312
3543
|
releaseCodexAuthContextProbeLease(authCtx);
|
|
3313
3544
|
return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
|
|
3314
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
|
+
}
|
|
3315
3554
|
sentOAuthSnapshot = refreshed;
|
|
3316
3555
|
replayOAuthCredentialSnapshot = {
|
|
3317
3556
|
accountId: refreshed.accountId,
|
|
@@ -3320,6 +3559,9 @@ async function handleResponsesInner(
|
|
|
3320
3559
|
if (route.providerName === "kiro") {
|
|
3321
3560
|
parsed._kiroAuthContext = { ...(refreshed.kiro ?? {}) };
|
|
3322
3561
|
}
|
|
3562
|
+
if (isAntigravityOAuth) {
|
|
3563
|
+
route.provider = { ...route.provider, project: refreshed.projectId };
|
|
3564
|
+
}
|
|
3323
3565
|
const refreshedProvider = resolveProviderTransport(
|
|
3324
3566
|
route.providerName,
|
|
3325
3567
|
{ ...route.provider, apiKey: refreshed.accessToken },
|
|
@@ -3350,9 +3592,11 @@ async function handleResponsesInner(
|
|
|
3350
3592
|
logCtx.accountLogLabel,
|
|
3351
3593
|
);
|
|
3352
3594
|
try {
|
|
3595
|
+
assertGoogleOptionsRoute(refreshedAdapter, refreshedProvider);
|
|
3353
3596
|
request = await refreshedAdapter.buildRequest(parsed, {
|
|
3354
3597
|
headers: selectedForwardHeaders,
|
|
3355
3598
|
translatorBudget,
|
|
3599
|
+
...(observeAntigravityProviderError ? { onProviderError: observeAntigravityProviderError } : {}),
|
|
3356
3600
|
});
|
|
3357
3601
|
refreshRoutedNamespaceToolAliases(request);
|
|
3358
3602
|
recordAdapterReasoning(logCtx, request);
|
|
@@ -3363,10 +3607,11 @@ async function handleResponsesInner(
|
|
|
3363
3607
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3364
3608
|
return formatErrorResponse(400, "invalid_request_error", redactSecretString(msg));
|
|
3365
3609
|
}
|
|
3610
|
+
refreshUndeclaredToolGuard(request);
|
|
3366
3611
|
try {
|
|
3367
3612
|
upstreamResponse = await fetchWithTransientRetry(
|
|
3368
3613
|
recovery => {
|
|
3369
|
-
|
|
3614
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, passthroughEstimate, recovery ?? "oauth-401", refreshedAdapter.name);
|
|
3370
3615
|
return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
|
|
3371
3616
|
method: request.method,
|
|
3372
3617
|
headers: request.headers,
|
|
@@ -3428,7 +3673,7 @@ async function handleResponsesInner(
|
|
|
3428
3673
|
recovery => {
|
|
3429
3674
|
// The first send of every replay is itself a rate-limit retry; inner transient-5xx
|
|
3430
3675
|
// recoveries keep their own label (recovery is provided for those).
|
|
3431
|
-
|
|
3676
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, passthroughEstimate, recovery ?? "rate-limit-429", route.provider.adapter);
|
|
3432
3677
|
return fetchWithHeaderTimeout(request.url, applyUpstreamRecoveryInit({
|
|
3433
3678
|
method: request.method,
|
|
3434
3679
|
headers: request.headers,
|
|
@@ -3514,6 +3759,7 @@ async function handleResponsesInner(
|
|
|
3514
3759
|
authCtx = retry.authCtx;
|
|
3515
3760
|
request = retry.request;
|
|
3516
3761
|
refreshRoutedNamespaceToolAliases(request);
|
|
3762
|
+
refreshUndeclaredToolGuard(request);
|
|
3517
3763
|
upstreamResponse = retry.upstreamResponse;
|
|
3518
3764
|
selectedForwardHeaders = retry.selectedForwardHeaders;
|
|
3519
3765
|
// Keep subagent quota-failure health keyed to the account that actually served.
|
|
@@ -3552,13 +3798,21 @@ async function handleResponsesInner(
|
|
|
3552
3798
|
const passthroughCt = headers.get("content-type")?.toLowerCase();
|
|
3553
3799
|
const isEventStream = passthroughCt?.includes("text/event-stream")
|
|
3554
3800
|
|| (upstreamResponse.ok && !!upstreamResponse.body && !passthroughCt && parsed.stream);
|
|
3555
|
-
const
|
|
3801
|
+
const recordTerminalOutcome = codexForwardTerminalOutcomeRecorder(
|
|
3556
3802
|
config,
|
|
3557
3803
|
authCtx,
|
|
3558
3804
|
route.provider,
|
|
3559
3805
|
route.modelId,
|
|
3560
3806
|
logCtx,
|
|
3561
3807
|
);
|
|
3808
|
+
let terminalOutcomeRecorded = false;
|
|
3809
|
+
const terminalRecorder = recordTerminalOutcome
|
|
3810
|
+
? (status: ResponsesTerminalStatus, httpStatusOverride?: number): void => {
|
|
3811
|
+
if (terminalOutcomeRecorded) return;
|
|
3812
|
+
terminalOutcomeRecorded = true;
|
|
3813
|
+
recordTerminalOutcome(status, httpStatusOverride);
|
|
3814
|
+
}
|
|
3815
|
+
: undefined;
|
|
3562
3816
|
const terminalBodyWillRecord = !!terminalRecorder && upstreamResponse.ok && isEventStream;
|
|
3563
3817
|
// Capture quota from upstream response for multi-account tracking
|
|
3564
3818
|
if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
|
|
@@ -3635,19 +3889,13 @@ async function handleResponsesInner(
|
|
|
3635
3889
|
options.onConsumedComboFailure?.(failure);
|
|
3636
3890
|
return failure.response;
|
|
3637
3891
|
}
|
|
3638
|
-
// The
|
|
3639
|
-
//
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
statusText: upstreamResponse.statusText,
|
|
3646
|
-
headers,
|
|
3647
|
-
});
|
|
3648
|
-
} finally {
|
|
3649
|
-
detachPassthroughErrorGuard();
|
|
3650
|
-
}
|
|
3892
|
+
// The bounded reader owns the original body, deadline, abort settlement, and lock.
|
|
3893
|
+
// Unsafe partial data falls back to #452's non-empty status-only JSON.
|
|
3894
|
+
const errorText = await readDisplaySafeErrorText(upstreamResponse, upstream.signal, "");
|
|
3895
|
+
return formatPassthroughUpstreamError(upstreamResponse.status, errorText, {
|
|
3896
|
+
statusText: upstreamResponse.statusText,
|
|
3897
|
+
headers,
|
|
3898
|
+
});
|
|
3651
3899
|
}
|
|
3652
3900
|
|
|
3653
3901
|
// Bun#32111 workaround: passthrough SSE uses tee()+native relay to avoid the
|
|
@@ -3710,8 +3958,12 @@ async function handleResponsesInner(
|
|
|
3710
3958
|
payloadRewrites.length > 0
|
|
3711
3959
|
? payloadRewriteAsBlockRewrite(composeSsePayloadRewrites(...payloadRewrites))
|
|
3712
3960
|
: undefined,
|
|
3713
|
-
routedCustomToolNames.size > 0
|
|
3714
|
-
? createRoutedCustomToolRestoreBlockRewrite(
|
|
3961
|
+
routedCustomToolNames.size > 0 || routedCustomToolRepairNames.size > 0
|
|
3962
|
+
? createRoutedCustomToolRestoreBlockRewrite(
|
|
3963
|
+
routedCustomToolNames,
|
|
3964
|
+
translatorBudget,
|
|
3965
|
+
routedCustomToolRepairNames,
|
|
3966
|
+
)
|
|
3715
3967
|
: undefined,
|
|
3716
3968
|
routedToolSearchNames.size > 0
|
|
3717
3969
|
? createRoutedToolSearchRestoreBlockRewrite(routedToolSearchNames, translatorBudget)
|
|
@@ -3726,7 +3978,11 @@ async function handleResponsesInner(
|
|
|
3726
3978
|
// Last: every rewrite above can still rename or reshape a call item, so the guard must
|
|
3727
3979
|
// compare the names the client will actually receive against the declared catalog.
|
|
3728
3980
|
undeclaredToolGuardActive
|
|
3729
|
-
? createUndeclaredToolCallGuardBlockRewrite(
|
|
3981
|
+
? createUndeclaredToolCallGuardBlockRewrite(
|
|
3982
|
+
declaredWireToolNames,
|
|
3983
|
+
declaredNamelessClientCallTypes,
|
|
3984
|
+
providerExecutedCallTypes,
|
|
3985
|
+
)
|
|
3730
3986
|
: undefined,
|
|
3731
3987
|
].filter((rewrite): rewrite is NonNullable<typeof rewrite> => rewrite !== undefined);
|
|
3732
3988
|
const clientBlockRewrite = blockRewrites.length > 0
|
|
@@ -3916,6 +4172,7 @@ async function handleResponsesInner(
|
|
|
3916
4172
|
const restored = restoreRoutedCustomCallsInJson(
|
|
3917
4173
|
restoredNamespace,
|
|
3918
4174
|
routedCustomToolNames,
|
|
4175
|
+
routedCustomToolRepairNames,
|
|
3919
4176
|
);
|
|
3920
4177
|
const restoredToolSearch = restoreRoutedToolSearchCallsInJson(
|
|
3921
4178
|
restored,
|
|
@@ -3942,7 +4199,12 @@ async function handleResponsesInner(
|
|
|
3942
4199
|
if (undeclaredToolGuardActive) {
|
|
3943
4200
|
const undeclared = (() => {
|
|
3944
4201
|
try {
|
|
3945
|
-
return undeclaredToolCallNameInResponse(
|
|
4202
|
+
return undeclaredToolCallNameInResponse(
|
|
4203
|
+
JSON.parse(clientJson),
|
|
4204
|
+
declaredWireToolNames,
|
|
4205
|
+
declaredNamelessClientCallTypes,
|
|
4206
|
+
providerExecutedCallTypes,
|
|
4207
|
+
);
|
|
3946
4208
|
} catch {
|
|
3947
4209
|
return undefined;
|
|
3948
4210
|
}
|
|
@@ -4139,7 +4401,8 @@ async function handleResponsesInner(
|
|
|
4139
4401
|
...(vidPlan ? { videoPlan: vidPlan } : {}),
|
|
4140
4402
|
forwardHeaders: selectedForwardHeaders,
|
|
4141
4403
|
onAttemptSend: (recovery?: AttemptRecoveryKind) =>
|
|
4142
|
-
|
|
4404
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery, adapter.name),
|
|
4405
|
+
...(diagnosticContext ? { diagnostic: diagnosticContext } : {}),
|
|
4143
4406
|
abortSignal: options.abortSignal,
|
|
4144
4407
|
maxRounds: imgPlan && vidPlan
|
|
4145
4408
|
? clampImageMaxRounds(Math.min(config.images?.maxRounds ?? 3, config.images?.videoMaxRounds ?? 2))
|
|
@@ -4154,6 +4417,7 @@ async function handleResponsesInner(
|
|
|
4154
4417
|
recordAdapterReasoning(logCtx, request);
|
|
4155
4418
|
recordAdapterTier(logCtx, request);
|
|
4156
4419
|
},
|
|
4420
|
+
validateAdapter: (requestParsed, candidate) => assertGoogleOptionsRoute(candidate, route.provider, requestParsed),
|
|
4157
4421
|
...(vidPlan?.timeoutMs ? { videoTimeoutMs: vidPlan.timeoutMs } : {}),
|
|
4158
4422
|
onUsage: usage => {
|
|
4159
4423
|
// Cursor may assign _cursorConversationId inside the image loop's first runTurn;
|
|
@@ -4243,8 +4507,10 @@ async function handleResponsesInner(
|
|
|
4243
4507
|
recordAdapterReasoning(logCtx, request);
|
|
4244
4508
|
recordAdapterTier(logCtx, request);
|
|
4245
4509
|
},
|
|
4510
|
+
validateAdapter: (requestParsed, candidate) => assertGoogleOptionsRoute(candidate, route.provider, requestParsed),
|
|
4246
4511
|
onAttemptSend: (recovery?: AttemptRecoveryKind) =>
|
|
4247
|
-
|
|
4512
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery, adapter.name),
|
|
4513
|
+
...(diagnosticContext ? { diagnostic: diagnosticContext } : {}),
|
|
4248
4514
|
onUsage: usage => {
|
|
4249
4515
|
logCtx.usageFromBridge = true;
|
|
4250
4516
|
if (usage) {
|
|
@@ -4333,10 +4599,14 @@ async function handleResponsesInner(
|
|
|
4333
4599
|
pacingSlotAcquired = false,
|
|
4334
4600
|
): Promise<void> => {
|
|
4335
4601
|
try {
|
|
4602
|
+
if (diagnosticContext) {
|
|
4603
|
+
diagnosticContext.recovery = recovery;
|
|
4604
|
+
diagnosticContext.attempt = logCtx.activeAttempt?.ordinal;
|
|
4605
|
+
}
|
|
4336
4606
|
if (!pacingSlotAcquired) {
|
|
4337
4607
|
await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
|
|
4338
4608
|
}
|
|
4339
|
-
|
|
4609
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery, adapter.name);
|
|
4340
4610
|
const runTurnProviderFetch = providerFetch(
|
|
4341
4611
|
route.provider,
|
|
4342
4612
|
options.codexWsRuntimeIdentity,
|
|
@@ -4390,13 +4660,15 @@ async function handleResponsesInner(
|
|
|
4390
4660
|
onBacklogExceeded: () => runTurnAbort.abort(),
|
|
4391
4661
|
});
|
|
4392
4662
|
void runTurnAttempt(retryQueue, "empty-completion");
|
|
4393
|
-
return retryQueue.stream();
|
|
4663
|
+
return diagnoseAdapterEvents(retryQueue.stream(), adapter.name, diagnosticRequestId, logCtx, adapterDiagnosticState);
|
|
4394
4664
|
};
|
|
4395
4665
|
|
|
4396
4666
|
const { toolNsMap, declaredToolNames, toolParameterSchemas, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
|
|
4397
4667
|
if (parsed.stream) {
|
|
4398
4668
|
void runTurn();
|
|
4399
|
-
let eventSource: AsyncIterable<AdapterEvent> =
|
|
4669
|
+
let eventSource: AsyncIterable<AdapterEvent> = diagnoseAdapterEvents(
|
|
4670
|
+
queue.stream(), adapter.name, diagnosticRequestId, logCtx, adapterDiagnosticState,
|
|
4671
|
+
);
|
|
4400
4672
|
if (options.comboAttempt) {
|
|
4401
4673
|
const preflight = await preflightAdapterEvents(eventSource);
|
|
4402
4674
|
if (preflight.error || preflight.empty) {
|
|
@@ -4434,6 +4706,7 @@ async function handleResponsesInner(
|
|
|
4434
4706
|
// grok-build's strict decoder dies on the typed response.heartbeat frame; its
|
|
4435
4707
|
// eventsource layer tolerates comment keep-alives. Codex needs the opposite.
|
|
4436
4708
|
...(logCtx.surface === "grok" ? { heartbeatStyle: "comment" as const } : {}),
|
|
4709
|
+
...(diagnosticContext ? { diagnostic: diagnosticContext } : {}),
|
|
4437
4710
|
onUsage: usage => {
|
|
4438
4711
|
// Raw adapter usage, pre wire-normalization: the bridged SSE now always carries
|
|
4439
4712
|
// zero-default detail objects, so provenance must come from here (cache_detail_missing).
|
|
@@ -4458,9 +4731,11 @@ async function handleResponsesInner(
|
|
|
4458
4731
|
);
|
|
4459
4732
|
const bridgeTurnAc = new AbortController();
|
|
4460
4733
|
const trackedSse = trackStreamLifetime(sseStream, bridgeTurnAc, undefined, options.turnAdmissionLease);
|
|
4461
|
-
|
|
4734
|
+
const response = new Response(trackedSse, {
|
|
4462
4735
|
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no" },
|
|
4463
4736
|
});
|
|
4737
|
+
runTurnAdapterSseResponses.add(response);
|
|
4738
|
+
return response;
|
|
4464
4739
|
}
|
|
4465
4740
|
|
|
4466
4741
|
await runTurn();
|
|
@@ -4540,7 +4815,12 @@ async function handleResponsesInner(
|
|
|
4540
4815
|
let initialRequest: AdapterRequest | undefined;
|
|
4541
4816
|
let inputTokenEstimate: number | undefined;
|
|
4542
4817
|
try {
|
|
4543
|
-
|
|
4818
|
+
assertGoogleOptionsRoute(activeAdapter, route.provider);
|
|
4819
|
+
initialRequest = await activeAdapter.buildRequest(parsed, {
|
|
4820
|
+
headers: selectedForwardHeaders,
|
|
4821
|
+
translatorBudget,
|
|
4822
|
+
...(observeAntigravityProviderError ? { onProviderError: observeAntigravityProviderError } : {}),
|
|
4823
|
+
});
|
|
4544
4824
|
refreshRoutedNamespaceToolAliases(initialRequest);
|
|
4545
4825
|
recordAdapterReasoning(logCtx, initialRequest);
|
|
4546
4826
|
recordAdapterTier(logCtx, initialRequest);
|
|
@@ -4577,7 +4857,7 @@ async function handleResponsesInner(
|
|
|
4577
4857
|
let upstreamResponse: Response;
|
|
4578
4858
|
try {
|
|
4579
4859
|
if (activeAdapter.fetchResponse) {
|
|
4580
|
-
|
|
4860
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, inputTokenEstimate, undefined, activeAdapter.name);
|
|
4581
4861
|
await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, upstream.signal);
|
|
4582
4862
|
upstreamResponse = await activeAdapter.fetchResponse(builtInitialRequest, {
|
|
4583
4863
|
abortSignal: upstream.signal,
|
|
@@ -4586,6 +4866,7 @@ async function handleResponsesInner(
|
|
|
4586
4866
|
executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
4587
4867
|
providerName: route.providerName,
|
|
4588
4868
|
modelId: route.modelId,
|
|
4869
|
+
pacingSlotAcquired: true,
|
|
4589
4870
|
}),
|
|
4590
4871
|
...(antigravityAccountId ? { accountId: antigravityAccountId } : {}),
|
|
4591
4872
|
});
|
|
@@ -4597,7 +4878,7 @@ async function handleResponsesInner(
|
|
|
4597
4878
|
const fetchWithRetryPolicy = route.provider.adapter === "google" ? fetchWithTransientRetry : fetchWithResetRetry;
|
|
4598
4879
|
upstreamResponse = await fetchWithRetryPolicy(
|
|
4599
4880
|
recovery => {
|
|
4600
|
-
|
|
4881
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, inputTokenEstimate, recovery, activeAdapter.name);
|
|
4601
4882
|
return fetchWithHeaderTimeout(builtInitialRequest.url, applyUpstreamRecoveryInit({
|
|
4602
4883
|
method: builtInitialRequest.method,
|
|
4603
4884
|
headers: builtInitialRequest.headers,
|
|
@@ -4648,6 +4929,13 @@ async function handleResponsesInner(
|
|
|
4648
4929
|
const rebuildAndRefetch = async (
|
|
4649
4930
|
recovery: AttemptRecoveryKind,
|
|
4650
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
|
+
}
|
|
4651
4939
|
let retryRequest: AdapterRequest;
|
|
4652
4940
|
if (sameTargetRequest !== undefined && sameTargetParsed === parsed && sameTargetToken === transportToken) {
|
|
4653
4941
|
// Same target (key/adapter/parsed/tier unchanged): replay the exact cached request.
|
|
@@ -4658,6 +4946,7 @@ async function handleResponsesInner(
|
|
|
4658
4946
|
headers: selectedForwardHeaders,
|
|
4659
4947
|
translatorBudget,
|
|
4660
4948
|
...(imageTierBias > 0 ? { imageTierBias } : {}),
|
|
4949
|
+
...(observeAntigravityProviderError ? { onProviderError: observeAntigravityProviderError } : {}),
|
|
4661
4950
|
});
|
|
4662
4951
|
recordAdapterReasoning(logCtx, retryRequest);
|
|
4663
4952
|
recordAdapterTier(logCtx, retryRequest);
|
|
@@ -4682,7 +4971,7 @@ async function handleResponsesInner(
|
|
|
4682
4971
|
if (retryEstimate !== undefined) logCtx.usageLogInputTokens = retryEstimate;
|
|
4683
4972
|
logCtx.providerAdapter = activeAdapter.name;
|
|
4684
4973
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name, logCtx.accountLogLabel);
|
|
4685
|
-
|
|
4974
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, retryEstimate, recovery, activeAdapter.name);
|
|
4686
4975
|
try {
|
|
4687
4976
|
try {
|
|
4688
4977
|
if (activeAdapter.fetchResponse) {
|
|
@@ -4694,6 +4983,7 @@ async function handleResponsesInner(
|
|
|
4694
4983
|
executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
4695
4984
|
providerName: route.providerName,
|
|
4696
4985
|
modelId: route.modelId,
|
|
4986
|
+
pacingSlotAcquired: true,
|
|
4697
4987
|
}),
|
|
4698
4988
|
...(antigravityAccountId ? { accountId: antigravityAccountId } : {}),
|
|
4699
4989
|
});
|
|
@@ -4735,6 +5025,14 @@ async function handleResponsesInner(
|
|
|
4735
5025
|
cleanupUpstreamAbort();
|
|
4736
5026
|
return formatErrorResponse(401, "authentication_error", publicOAuthAuthenticationErrorMessage(err));
|
|
4737
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
|
+
}
|
|
4738
5036
|
sentOAuthSnapshot = refreshed;
|
|
4739
5037
|
replayOAuthCredentialSnapshot = {
|
|
4740
5038
|
accountId: refreshed.accountId,
|
|
@@ -4743,6 +5041,9 @@ async function handleResponsesInner(
|
|
|
4743
5041
|
if (route.providerName === "kiro") {
|
|
4744
5042
|
parsed._kiroAuthContext = { ...(refreshed.kiro ?? {}) };
|
|
4745
5043
|
}
|
|
5044
|
+
if (isAntigravityOAuth) {
|
|
5045
|
+
route.provider = { ...route.provider, project: refreshed.projectId };
|
|
5046
|
+
}
|
|
4746
5047
|
const refreshedProvider = resolveProviderTransport(
|
|
4747
5048
|
route.providerName,
|
|
4748
5049
|
{ ...route.provider, apiKey: refreshed.accessToken },
|
|
@@ -4838,6 +5139,37 @@ async function handleResponsesInner(
|
|
|
4838
5139
|
upstreamResponse = result;
|
|
4839
5140
|
}
|
|
4840
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
|
+
|
|
4841
5173
|
// Opt-in Anthropic OAuth account pool (#294): cool the failed account and retry
|
|
4842
5174
|
// with another eligible OAuth account (bounded per request). Disabled by default.
|
|
4843
5175
|
while (
|
|
@@ -4874,75 +5206,6 @@ async function handleResponsesInner(
|
|
|
4874
5206
|
break;
|
|
4875
5207
|
}
|
|
4876
5208
|
}
|
|
4877
|
-
// Antigravity OAuth accounts use the same bounded pre-stream carousel as Anthropic, but
|
|
4878
|
-
// their process-local routing module also excludes accounts cooled by quota/rate-limit
|
|
4879
|
-
// responses. Geoblocked 403s intentionally never enter this loop.
|
|
4880
|
-
while (
|
|
4881
|
-
upstreamResponse.status === 429
|
|
4882
|
-
&& route.providerName === "google-antigravity"
|
|
4883
|
-
&& route.provider.googleMode === "cloud-code-assist"
|
|
4884
|
-
&& antigravityAccountId
|
|
4885
|
-
&& antigravityFailovers < 3
|
|
4886
|
-
) {
|
|
4887
|
-
const stickWaitMs = antigravity429StickWaitMs(antigravityAccountId);
|
|
4888
|
-
if (stickWaitMs !== null) {
|
|
4889
|
-
await Bun.sleep(stickWaitMs);
|
|
4890
|
-
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
4891
|
-
invalidateSameTargetRequest();
|
|
4892
|
-
activeAdapter = resolveAdapter(
|
|
4893
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
4894
|
-
config.cacheRetention,
|
|
4895
|
-
);
|
|
4896
|
-
bindRouteReasoningReplayScope({
|
|
4897
|
-
parsed,
|
|
4898
|
-
providerName: route.providerName,
|
|
4899
|
-
provider: route.provider,
|
|
4900
|
-
adapterName: activeAdapter.name,
|
|
4901
|
-
codexAuthContext: authCtx,
|
|
4902
|
-
forwardHeaders: selectedForwardHeaders,
|
|
4903
|
-
});
|
|
4904
|
-
const result = await rebuildAndRefetch("rate-limit-429");
|
|
4905
|
-
if ("failed" in result) return result.failed;
|
|
4906
|
-
upstreamResponse = result;
|
|
4907
|
-
continue;
|
|
4908
|
-
}
|
|
4909
|
-
const nextAccountId = rotateAntigravityAccountOn429(antigravityAccountId, antigravitySessionKey);
|
|
4910
|
-
if (!nextAccountId) break;
|
|
4911
|
-
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
4912
|
-
try {
|
|
4913
|
-
const accessToken = await getValidAccessTokenForAccount("google-antigravity", nextAccountId);
|
|
4914
|
-
const nextCredential = getAccountCredential("google-antigravity", nextAccountId);
|
|
4915
|
-
const bound = bindAntigravityProject(
|
|
4916
|
-
{ ...route.provider, apiKey: accessToken },
|
|
4917
|
-
nextCredential?.projectId,
|
|
4918
|
-
);
|
|
4919
|
-
if (!bound.ok) {
|
|
4920
|
-
return formatErrorResponse(bound.status, bound.type, bound.message);
|
|
4921
|
-
}
|
|
4922
|
-
antigravityAccountId = nextAccountId;
|
|
4923
|
-
antigravityFailovers += 1;
|
|
4924
|
-
route.provider = bound.provider;
|
|
4925
|
-
replayOAuthCredentialSnapshot = undefined;
|
|
4926
|
-
invalidateSameTargetRequest();
|
|
4927
|
-
activeAdapter = resolveAdapter(
|
|
4928
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
|
|
4929
|
-
config.cacheRetention,
|
|
4930
|
-
);
|
|
4931
|
-
bindRouteReasoningReplayScope({
|
|
4932
|
-
parsed,
|
|
4933
|
-
providerName: route.providerName,
|
|
4934
|
-
provider: route.provider,
|
|
4935
|
-
adapterName: activeAdapter.name,
|
|
4936
|
-
codexAuthContext: authCtx,
|
|
4937
|
-
forwardHeaders: selectedForwardHeaders,
|
|
4938
|
-
});
|
|
4939
|
-
const result = await rebuildAndRefetch("rate-limit-429");
|
|
4940
|
-
if ("failed" in result) return result.failed;
|
|
4941
|
-
upstreamResponse = result;
|
|
4942
|
-
} catch {
|
|
4943
|
-
break;
|
|
4944
|
-
}
|
|
4945
|
-
}
|
|
4946
5209
|
if (
|
|
4947
5210
|
upstreamResponse.status === 402
|
|
4948
5211
|
&& cursorPoolAccountId
|
|
@@ -5093,14 +5356,16 @@ async function handleResponsesInner(
|
|
|
5093
5356
|
options.onConsumedComboFailure?.(failure);
|
|
5094
5357
|
return failure.response;
|
|
5095
5358
|
}
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5359
|
+
let errorText: string;
|
|
5360
|
+
try {
|
|
5361
|
+
errorText = await readDisplaySafeErrorText(
|
|
5362
|
+
upstreamResponse,
|
|
5363
|
+
upstream.signal,
|
|
5364
|
+
"unknown error",
|
|
5365
|
+
);
|
|
5366
|
+
} finally {
|
|
5367
|
+
cleanupUpstreamAbort();
|
|
5368
|
+
}
|
|
5104
5369
|
if (!isFixedCodexAccount(authCtx)) {
|
|
5105
5370
|
recordSubagentQuotaFailureForThreadSpawn(
|
|
5106
5371
|
req.headers,
|
|
@@ -5160,6 +5425,16 @@ async function handleResponsesInner(
|
|
|
5160
5425
|
nextParsed: OcxParsedRequest,
|
|
5161
5426
|
initialRecoveryKind?: AttemptRecoveryKind,
|
|
5162
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
|
+
};
|
|
5163
5438
|
let response: Response | undefined;
|
|
5164
5439
|
// One-shot recovery label for the next top-of-loop continuation send after a failover rotation.
|
|
5165
5440
|
let nextContinuationRecoveryKind: AttemptRecoveryKind | undefined = initialRecoveryKind;
|
|
@@ -5172,6 +5447,7 @@ async function handleResponsesInner(
|
|
|
5172
5447
|
*/
|
|
5173
5448
|
const fetchContinuation = async (recoveryKind?: AttemptRecoveryKind): Promise<Response> => {
|
|
5174
5449
|
let continuationRequest: AdapterRequest | undefined;
|
|
5450
|
+
assertGoogleOptionsRoute(activeAdapter, route.provider, nextParsed);
|
|
5175
5451
|
if (sameTargetRequest !== undefined && sameTargetParsed === nextParsed && sameTargetToken === transportToken) {
|
|
5176
5452
|
// Same target (key/adapter/parsed/tier unchanged): replay the exact cached request.
|
|
5177
5453
|
continuationRequest = sameTargetRequest;
|
|
@@ -5181,6 +5457,7 @@ async function handleResponsesInner(
|
|
|
5181
5457
|
headers: selectedForwardHeaders,
|
|
5182
5458
|
translatorBudget,
|
|
5183
5459
|
...(imageTierBias > 0 ? { imageTierBias } : {}),
|
|
5460
|
+
...(observeAntigravityProviderError ? { onProviderError: observeAntigravityProviderError } : {}),
|
|
5184
5461
|
});
|
|
5185
5462
|
recordAdapterReasoning(logCtx, continuationRequest);
|
|
5186
5463
|
recordAdapterTier(logCtx, continuationRequest);
|
|
@@ -5206,8 +5483,13 @@ async function handleResponsesInner(
|
|
|
5206
5483
|
// Optional recovery label for same-target / failover continuation sends.
|
|
5207
5484
|
const replayKind: AttemptRecoveryKind | undefined = recoveryKind;
|
|
5208
5485
|
try {
|
|
5486
|
+
if (diagnosticContext) {
|
|
5487
|
+
diagnosticContext.recovery = replayKind;
|
|
5488
|
+
diagnosticContext.attempt = logCtx.activeAttempt?.ordinal;
|
|
5489
|
+
diagnosticContext.adapterName = activeAdapter.name;
|
|
5490
|
+
}
|
|
5209
5491
|
if (activeAdapter.fetchResponse) {
|
|
5210
|
-
|
|
5492
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, continuationEstimate, replayKind, activeAdapter.name);
|
|
5211
5493
|
await waitForProviderRequestSlot(route.providerName, route.provider, nextParsed.modelId, upstream.signal);
|
|
5212
5494
|
return await activeAdapter.fetchResponse(builtContinuationRequest, {
|
|
5213
5495
|
abortSignal: upstream.signal,
|
|
@@ -5216,6 +5498,7 @@ async function handleResponsesInner(
|
|
|
5216
5498
|
executor: providerFetch(route.provider, options.codexWsRuntimeIdentity, {
|
|
5217
5499
|
providerName: route.providerName,
|
|
5218
5500
|
modelId: nextParsed.modelId,
|
|
5501
|
+
pacingSlotAcquired: true,
|
|
5219
5502
|
}),
|
|
5220
5503
|
...(antigravityAccountId ? { accountId: antigravityAccountId } : {}),
|
|
5221
5504
|
});
|
|
@@ -5225,7 +5508,7 @@ async function handleResponsesInner(
|
|
|
5225
5508
|
const fetchContinuationWithRetryPolicy = route.provider.adapter === "google" ? fetchWithTransientRetry : fetchWithResetRetry;
|
|
5226
5509
|
return await fetchContinuationWithRetryPolicy(
|
|
5227
5510
|
recovery => {
|
|
5228
|
-
|
|
5511
|
+
noteDiagnosticAttempt(logCtx.activeAttempt, continuationEstimate, recovery ?? replayKind, activeAdapter.name);
|
|
5229
5512
|
return fetchWithHeaderTimeout(
|
|
5230
5513
|
builtContinuationRequest.url,
|
|
5231
5514
|
applyUpstreamRecoveryInit({
|
|
@@ -5254,7 +5537,10 @@ async function handleResponsesInner(
|
|
|
5254
5537
|
nextContinuationRecoveryKind = undefined;
|
|
5255
5538
|
response = await fetchContinuation(recoveryKind);
|
|
5256
5539
|
} catch (error) {
|
|
5257
|
-
|
|
5540
|
+
const validationEvent = requestValidationEvent(error);
|
|
5541
|
+
if (validationEvent) {
|
|
5542
|
+
yield validationEvent;
|
|
5543
|
+
} else if (options.abortSignal?.aborted || upstream.signal.aborted) {
|
|
5258
5544
|
yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
|
|
5259
5545
|
} else {
|
|
5260
5546
|
yield { type: "error", message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
|
|
@@ -5300,7 +5586,10 @@ async function handleResponsesInner(
|
|
|
5300
5586
|
try {
|
|
5301
5587
|
response = await fetchContinuation("rate-limit-429");
|
|
5302
5588
|
} catch (error) {
|
|
5303
|
-
|
|
5589
|
+
const validationEvent = requestValidationEvent(error);
|
|
5590
|
+
if (validationEvent) {
|
|
5591
|
+
yield validationEvent;
|
|
5592
|
+
} else if (options.abortSignal?.aborted || upstream.signal.aborted) {
|
|
5304
5593
|
yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
|
|
5305
5594
|
} else {
|
|
5306
5595
|
yield { type: "error", message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
|
|
@@ -5309,6 +5598,42 @@ async function handleResponsesInner(
|
|
|
5309
5598
|
}
|
|
5310
5599
|
}
|
|
5311
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
|
+
|
|
5312
5637
|
if (response.status === 429 && hasKeyPoolFailover(route.provider)) {
|
|
5313
5638
|
const rotated = rotateProviderTransportOn429(config, route.providerName, route.provider, {
|
|
5314
5639
|
retryAfter: response.headers.get("retry-after"),
|
|
@@ -5499,12 +5824,7 @@ async function handleResponsesInner(
|
|
|
5499
5824
|
}
|
|
5500
5825
|
|
|
5501
5826
|
if (!response.ok) {
|
|
5502
|
-
|
|
5503
|
-
// is still a Bun fetch body, and an abort landing before `.text()` attaches its reader
|
|
5504
|
-
// orphans the internal rejection.
|
|
5505
|
-
const detachContinuationErrorGuard = cancelBodyOnAbort(response.body, upstream.signal);
|
|
5506
|
-
const errorText = await response.text().catch(() => "unknown error");
|
|
5507
|
-
detachContinuationErrorGuard();
|
|
5827
|
+
const errorText = await readDisplaySafeErrorText(response, upstream.signal, "unknown error");
|
|
5508
5828
|
yield {
|
|
5509
5829
|
type: "error",
|
|
5510
5830
|
status: response.status,
|
|
@@ -5520,7 +5840,13 @@ async function handleResponsesInner(
|
|
|
5520
5840
|
const detachContinuationBodyGuard = cancelBodyOnAbort(response.body, upstream.signal);
|
|
5521
5841
|
try {
|
|
5522
5842
|
if (nextParsed.stream) {
|
|
5523
|
-
yield*
|
|
5843
|
+
yield* diagnoseAdapterEvents(
|
|
5844
|
+
activeAdapter.parseStream(response, translatorBudget, logCtx.activeTierMetadata),
|
|
5845
|
+
activeAdapter.name,
|
|
5846
|
+
diagnosticRequestId,
|
|
5847
|
+
logCtx,
|
|
5848
|
+
adapterDiagnosticState,
|
|
5849
|
+
);
|
|
5524
5850
|
} else if (activeAdapter.parseResponse) {
|
|
5525
5851
|
yield* await activeAdapter.parseResponse(response, translatorBudget, logCtx.activeTierMetadata);
|
|
5526
5852
|
} else {
|
|
@@ -5530,7 +5856,10 @@ async function handleResponsesInner(
|
|
|
5530
5856
|
detachContinuationBodyGuard();
|
|
5531
5857
|
}
|
|
5532
5858
|
} catch (error) {
|
|
5533
|
-
|
|
5859
|
+
const validationEvent = requestValidationEvent(error);
|
|
5860
|
+
if (validationEvent) {
|
|
5861
|
+
yield validationEvent;
|
|
5862
|
+
} else if (options.abortSignal?.aborted) {
|
|
5534
5863
|
yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
|
|
5535
5864
|
} else {
|
|
5536
5865
|
yield { type: "error", message: `Provider continuation parse failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
|
|
@@ -5552,11 +5881,18 @@ async function handleResponsesInner(
|
|
|
5552
5881
|
};
|
|
5553
5882
|
|
|
5554
5883
|
if (parsed.stream) {
|
|
5555
|
-
const initialEventStream =
|
|
5556
|
-
upstreamResponse,
|
|
5557
|
-
|
|
5558
|
-
|
|
5884
|
+
const initialEventStream = diagnoseAdapterEvents(
|
|
5885
|
+
activeAdapter.parseStream(upstreamResponse, translatorBudget, logCtx.activeTierMetadata),
|
|
5886
|
+
activeAdapter.name,
|
|
5887
|
+
diagnosticRequestId,
|
|
5888
|
+
logCtx,
|
|
5889
|
+
adapterDiagnosticState,
|
|
5559
5890
|
);
|
|
5891
|
+
if (diagnosticContext) {
|
|
5892
|
+
diagnosticContext.adapterName = activeAdapter.name;
|
|
5893
|
+
diagnosticContext.attempt = logCtx.activeAttempt?.ordinal;
|
|
5894
|
+
diagnosticContext.recovery = logCtx.activeAttempt?.recoveryKinds.at(-1);
|
|
5895
|
+
}
|
|
5560
5896
|
const eventStream = terminalGuardEnabled
|
|
5561
5897
|
? guardTerminalEventStream({
|
|
5562
5898
|
parsed,
|
|
@@ -5592,6 +5928,7 @@ async function handleResponsesInner(
|
|
|
5592
5928
|
...(routedCompaction ? { compaction: true } : {}),
|
|
5593
5929
|
// Same grok-surface split as the runTurn branch above.
|
|
5594
5930
|
...(logCtx.surface === "grok" ? { heartbeatStyle: "comment" as const } : {}),
|
|
5931
|
+
...(diagnosticContext ? { diagnostic: diagnosticContext } : {}),
|
|
5595
5932
|
onUsage: usage => {
|
|
5596
5933
|
// Raw adapter usage, pre wire-normalization (see the runTurn branch above).
|
|
5597
5934
|
logCtx.usageFromBridge = true;
|