diff --git a/open-sse/executors/antigravity.ts b/open-sse/executors/antigravity.ts index 01a315ef80..406cbbd05b 100644 --- a/open-sse/executors/antigravity.ts +++ b/open-sse/executors/antigravity.ts @@ -508,6 +508,7 @@ function isAntigravityGeminiChatModel(upstreamModel: string): boolean { export const __test_stripTrailingAntigravityAssistantTurn = stripTrailingAntigravityAssistantTurn; type AntigravityCreditsRetryState = { attempted: boolean }; +type AntigravityPhysicalSendCounter = { value: number }; /** Base per-url-index attempt context, before the request has been sent. */ type AntigravityAttemptContext = { @@ -527,6 +528,8 @@ type AntigravityAttemptContext = { urlIndex: number; retryAttemptsByUrl: Record; fallbackCount: number; + physicalSendCounter: AntigravityPhysicalSendCounter; + correlationId: string | null; }; /** Context threaded through the 429/503 handling helpers — adds the sent response. */ @@ -1169,6 +1172,7 @@ export class AntigravityExecutor extends BaseExecutor { * exactly the same single call as before (zero extra upstream requests). */ async execute(input: ExecuteInput) { + const physicalSendCounter: AntigravityPhysicalSendCounter = { value: 0 }; await resolveAntigravityClientVersion(getAntigravityClientProfile(input.credentials)); // Look up the chain by the NORMALLY-resolved upstream id (honours MITM/static aliases). @@ -1178,7 +1182,7 @@ export class AntigravityExecutor extends BaseExecutor { if (chain.length <= 1) { // No fallback chain (flash, claude, plain pro, unknown) → single attempt, unchanged. - return this.executeOnce(input); + return this.executeOnce(input, undefined, physicalSendCounter); } let firstResult: Awaited> | null = null; @@ -1186,7 +1190,7 @@ export class AntigravityExecutor extends BaseExecutor { const candidate = chain[i]; let result: Awaited>; try { - result = await this.executeOnce(input, candidate); + result = await this.executeOnce(input, candidate, physicalSendCounter); } catch (error) { const outcome = handleAntigravityFallbackChainError( input, @@ -1228,7 +1232,7 @@ export class AntigravityExecutor extends BaseExecutor { } // Unreachable (loop always returns), but keeps the type checker happy. - return firstResult ?? this.executeOnce(input); + return firstResult ?? this.executeOnce(input, undefined, physicalSendCounter); } /** @@ -1239,8 +1243,18 @@ export class AntigravityExecutor extends BaseExecutor { * status of the first response so `execute()` can decide whether to fall through. @internal */ private async executeOnce( - { model, body, stream, credentials, signal, log, upstreamExtraHeaders }: ExecuteInput, - modelIdOverride?: string + { + model, + body, + stream, + credentials, + signal, + log, + upstreamExtraHeaders, + correlationId = null, + }: ExecuteInput, + modelIdOverride?: string, + physicalSendCounter: AntigravityPhysicalSendCounter = { value: 0 } ) { await resolveAntigravityClientVersion(getAntigravityClientProfile(credentials)); const fallbackCount = this.getFallbackCount(); @@ -1307,6 +1321,8 @@ export class AntigravityExecutor extends BaseExecutor { urlIndex, retryAttemptsByUrl, fallbackCount, + physicalSendCounter, + correlationId, }); if (outcome.action === "return") return outcome.result; @@ -1356,6 +1372,8 @@ export class AntigravityExecutor extends BaseExecutor { urlIndex, retryAttemptsByUrl, fallbackCount, + physicalSendCounter, + correlationId, } = ctx; const { response, finalHeaders } = await sendAntigravityRequest( @@ -1368,7 +1386,9 @@ export class AntigravityExecutor extends BaseExecutor { stream, signal, log, - retryAttemptsByUrl[urlIndex] + retryAttemptsByUrl[urlIndex], + physicalSendCounter, + correlationId ); let retryMs: number | null = null; @@ -1619,7 +1639,9 @@ export class AntigravityExecutor extends BaseExecutor { signal, log, accountId, - updateAntigravityRemainingCredits + updateAntigravityRemainingCredits, + ctx.physicalSendCounter, + ctx.correlationId ); if (creditsResult) return { kind: "return", result: creditsResult }; if (retryMs) markConnectionQuotaExhausted(accountId, retryMs, ctx.model); diff --git a/open-sse/executors/antigravity/executeAttempt.ts b/open-sse/executors/antigravity/executeAttempt.ts index b8b9447c19..276412efaa 100644 --- a/open-sse/executors/antigravity/executeAttempt.ts +++ b/open-sse/executors/antigravity/executeAttempt.ts @@ -333,7 +333,9 @@ export async function sendAntigravityRequest( stream: boolean, signal: AbortSignal | null | undefined, log: SafeAntigravityLog, - retryAttempt: number + retryAttempt: number, + physicalSendCounter: { value: number }, + correlationId: string | null ): Promise<{ response: Response; finalHeaders: Record }> { const serializedRequest = serializeAntigravityRequest(provider, headers, transformedBody); let finalHeaders = serializedRequest.headers; @@ -356,6 +358,11 @@ export async function sendAntigravityRequest( } await prl.captureCurrentProviderBody(url, finalHeaders, serializedRequest.bodyString, log); + const physicalSendOrdinal = ++physicalSendCounter.value; + log.debug( + "TELEMETRY", + `[Antigravity] PhysicalSend - RequestId: ${correlationId ?? "none"}, URL: ${url}, Model: ${model}, PhysicalSend: ${physicalSendOrdinal}, RetryAttempt: ${retryAttempt}` + ); let response = await fetchAntigravityWithReadinessTimeout(url, { method: "POST", headers: finalHeaders, @@ -369,6 +376,11 @@ export async function sendAntigravityRequest( removeHeaderCaseInsensitive(retryHeaders, "x-goog-user-project"); log.debug("RETRY", "403 with x-goog-user-project, retrying once without it"); await prl.captureCurrentProviderBody(url, retryHeaders, serializedRequest.bodyString, log); + const retryPhysicalSendOrdinal = ++physicalSendCounter.value; + log.debug( + "TELEMETRY", + `[Antigravity] PhysicalSend - RequestId: ${correlationId ?? "none"}, URL: ${url}, Model: ${model}, PhysicalSend: ${retryPhysicalSendOrdinal}, RetryAttempt: ${retryAttempt}, Cause: x-goog-user-project-403` + ); response = await fetchAntigravityWithReadinessTimeout(url, { method: "POST", headers: retryHeaders, @@ -416,7 +428,9 @@ export async function tryCreditsRetry( signal: AbortSignal | null | undefined, log: SafeAntigravityLog, accountId: string, - onCreditsUpdate: OnAntigravityCreditsUpdate + onCreditsUpdate: OnAntigravityCreditsUpdate, + physicalSendCounter: { value: number }, + correlationId: string | null ): Promise { log.info("AG_CREDITS", "Retrying with Google One AI credits"); const creditsBody = attachToolNameMap( @@ -433,6 +447,11 @@ export async function tryCreditsRetry( serializedCreditsRequest.bodyString, log ); + const creditsPhysicalSendOrdinal = ++physicalSendCounter.value; + log.debug( + "TELEMETRY", + `[Antigravity] PhysicalSend - RequestId: ${correlationId ?? "none"}, URL: ${url}, PhysicalSend: ${creditsPhysicalSendOrdinal}, Cause: google-one-ai-credits-retry` + ); const creditsResp = await fetchAntigravityWithReadinessTimeout(url, { method: "POST", headers: finalCreditsHeaders, diff --git a/tests/unit/antigravity-streaming-passthrough.test.ts b/tests/unit/antigravity-streaming-passthrough.test.ts index 88702823b2..336c1527d8 100644 --- a/tests/unit/antigravity-streaming-passthrough.test.ts +++ b/tests/unit/antigravity-streaming-passthrough.test.ts @@ -36,6 +36,7 @@ test("AntigravityExecutor.execute auto-retries short 429 responses and collects const originalFetch = globalThis.fetch; const originalSetTimeout = globalThis.setTimeout; const calls = []; + const telemetry: string[] = []; seedAntigravityIdeVersionCache("2026.04.17-test"); seedAntigravityCliVersionCache("2026.04.17-test"); @@ -71,7 +72,13 @@ test("AntigravityExecutor.execute auto-retries short 429 responses and collects body: { request: { contents: [] } }, stream: false, credentials: { accessToken: "token", projectId: "project-1" }, - log: { debug() {}, warn() {} }, + log: { + debug(_scope, message) { + telemetry.push(String(message)); + }, + warn() {}, + }, + correlationId: "prompt194-native-retry-test", }); // Non-streaming collects the upstream SSE and returns the already-converted // OpenAI chat.completion payload — no further SSE parsing on the caller side. @@ -79,6 +86,10 @@ test("AntigravityExecutor.execute auto-retries short 429 responses and collects assert.equal(payload.object, "chat.completion"); assert.equal(calls.length, 2); + const physicalSends = telemetry.filter((line) => line.includes("[Antigravity] PhysicalSend")); + assert.equal(physicalSends.length, calls.length); + assert.match(physicalSends[0] ?? "", /RequestId: prompt194-native-retry-test/); + assert.match(physicalSends[1] ?? "", /PhysicalSend: 2/); assert.equal(result.response.status, 200); assert.equal(payload.choices[0].message.content, "Hello again"); assert.deepEqual(payload.usage, { diff --git a/tests/unit/executor-antigravity.test.ts b/tests/unit/executor-antigravity.test.ts index befff78723..9040aa70fa 100644 --- a/tests/unit/executor-antigravity.test.ts +++ b/tests/unit/executor-antigravity.test.ts @@ -871,6 +871,7 @@ test("AntigravityExecutor.execute bounds a persistent short-retry 429 instead of const originalFetch = globalThis.fetch; const originalSetTimeout = globalThis.setTimeout; const calls: string[] = []; + const telemetry: string[] = []; seedAntigravityIdeVersionCache("2.1.1"); // "rate limited" with no parseable retry hint classifies as rate_limited → @@ -897,7 +898,13 @@ test("AntigravityExecutor.execute bounds a persistent short-retry 429 instead of body: { request: { contents: [] } }, stream: true, credentials: { accessToken: "token", projectId: "project-1" }, - log: { debug() {}, warn() {} }, + log: { + debug(_scope, message) { + telemetry.push(String(message)); + }, + warn() {}, + }, + correlationId: "prompt194-physical-send-test", }); // Returns the 429 rather than hanging. @@ -906,6 +913,11 @@ test("AntigravityExecutor.execute bounds a persistent short-retry 429 instead of // Bounded: switchAuth declines same-URL retries → 2 live runtime endpoints // × 1 attempt each = 2 attempts total (#9351). assert.equal(calls.length, 2); + const physicalSends = telemetry.filter((line) => line.includes("[Antigravity] PhysicalSend")); + assert.equal(physicalSends.length, calls.length); + assert.match(physicalSends[0] ?? "", /RequestId: prompt194-physical-send-test/); + assert.match(physicalSends[0] ?? "", /PhysicalSend: 1/); + assert.match(physicalSends[1] ?? "", /PhysicalSend: 2/); // Tried every distinct live runtime base URL before giving up. const distinctHosts = new Set(calls.map((u) => new URL(u).host));