diff --git a/src/adapters/client-fingerprint.ts b/src/adapters/client-fingerprint.ts index 91a51ea39a..e01880a938 100644 --- a/src/adapters/client-fingerprint.ts +++ b/src/adapters/client-fingerprint.ts @@ -49,15 +49,19 @@ const ANTIGRAVITY_IDE_PLATFORM = "windows/amd64"; export const ANTIGRAVITY_GOOG_API_CLIENT_UA = "google-api-nodejs-client/10.3.0"; /** - * The real Antigravity IDE User-Agent, e.g. - * `antigravity/ide/2.5.5 (aidev_client; os_type=windows; arch=amd64)`. + * Real Antigravity IDE User-Agent format, decompiled from 2.5.5 Go LS (`setHeaders` @ `0x1018fbe00`): + * `antigravity/ide/${version} (os_type=${osType}; arch=${arch}; aidev_client; auth_method=oauth)` * - * Must be the IDE client family, NOT `antigravity/cli/...`: the Cloud Code Assist backend gates + * Token ordering from decompiled binary: `os_type` -> `arch` -> `aidev_client` -> `auth_method=oauth`. + * + * Must be the IDE client family (`antigravity/ide/...`): Cloud Code Assist backend gates * newer agent models (e.g. `gemini-3.7-flash`) by User-Agent and answers 404 NOT_FOUND to * CLI-shaped UAs even with a valid OAuth token. Only `antigravity/ide/` unlocks them. * A `GOOGLE_ANTIGRAVITY_USER_AGENT` override (set by the caller) takes precedence upstream. */ -export function antigravityUserAgent(version = ANTIGRAVITY_IDE_VERSION): string { +export function antigravityUserAgent(version = ANTIGRAVITY_IDE_VERSION, authMethod = "oauth"): string { + const ov = process.env.GOOGLE_ANTIGRAVITY_USER_AGENT?.trim(); + if (ov) return ov; const [osType, arch] = ANTIGRAVITY_IDE_PLATFORM.split("/"); - return `antigravity/ide/${version} (${ANTIGRAVITY_IDE_CLIENT_NAME}; os_type=${osType}; arch=${arch})`; + return `antigravity/ide/${version} (os_type=${osType}; arch=${arch}; ${ANTIGRAVITY_IDE_CLIENT_NAME}; auth_method=${authMethod})`; } diff --git a/src/adapters/google-antigravity-wire.ts b/src/adapters/google-antigravity-wire.ts index e516b64c63..b4b0ca3176 100644 --- a/src/adapters/google-antigravity-wire.ts +++ b/src/adapters/google-antigravity-wire.ts @@ -9,7 +9,7 @@ import { antigravityUserAgent } from "./client-fingerprint"; * sends. The IDE client family is also required to unlock newer agent models (the backend 404s * CLI-shaped UAs for `gemini-3.7-*`). A `GOOGLE_ANTIGRAVITY_USER_AGENT` override still wins. */ -export const ANTIGRAVITY_REQUEST_UA = process.env.GOOGLE_ANTIGRAVITY_USER_AGENT || antigravityUserAgent(); +export const ANTIGRAVITY_REQUEST_UA = antigravityUserAgent(); /** * Whether a stored `OcxToolCall.thoughtSignature` is a REAL upstream Gemini signature versus a diff --git a/tests/client-fingerprint.test.ts b/tests/client-fingerprint.test.ts index 9e30d494ef..9f681fdb51 100644 --- a/tests/client-fingerprint.test.ts +++ b/tests/client-fingerprint.test.ts @@ -21,24 +21,38 @@ function parsed(): OcxParsedRequest { describe("client fingerprint — helpers", () => { test("antigravity UA has the real IDE shape, never the literal giveaway", async () => { const ua = antigravityUserAgent(); - expect(ua).toBe(`antigravity/ide/${ANTIGRAVITY_IDE_VERSION} (aidev_client; os_type=windows; arch=amd64)`); + expect(ua).toBe(`antigravity/ide/${ANTIGRAVITY_IDE_VERSION} (os_type=windows; arch=amd64; aidev_client; auth_method=oauth)`); expect(ua).not.toBe("antigravity"); }); - test("antigravity UA honors an explicit version override", async () => { - expect(antigravityUserAgent("9.9.9")).toBe("antigravity/ide/9.9.9 (aidev_client; os_type=windows; arch=amd64)"); + test("antigravity UA honors explicit version and authMethod overrides", async () => { + expect(antigravityUserAgent("9.9.9")).toBe("antigravity/ide/9.9.9 (os_type=windows; arch=amd64; aidev_client; auth_method=oauth)"); + expect(antigravityUserAgent(ANTIGRAVITY_IDE_VERSION, "api_key")).toBe( + `antigravity/ide/${ANTIGRAVITY_IDE_VERSION} (os_type=windows; arch=amd64; aidev_client; auth_method=api_key)`, + ); }); - test("GOOGLE_ANTIGRAVITY_USER_AGENT env override wins over the default UA", async () => { - const prev = process.env.GOOGLE_ANTIGRAVITY_USER_AGENT; - process.env.GOOGLE_ANTIGRAVITY_USER_AGENT = "custom-ua/1.2.3"; + test("GOOGLE_ANTIGRAVITY_USER_AGENT env override trims surrounding whitespace", async () => { + const prevGoogle = process.env.GOOGLE_ANTIGRAVITY_USER_AGENT; try { - // Fresh module instance so the env-driven constant is re-evaluated at import time. - const mod = await import(`../src/adapters/google-antigravity-wire?override=${Date.now()}`); - expect(mod.ANTIGRAVITY_REQUEST_UA).toBe("custom-ua/1.2.3"); + process.env.GOOGLE_ANTIGRAVITY_USER_AGENT = " custom-ua/1.2.3 "; + expect(antigravityUserAgent()).toBe("custom-ua/1.2.3"); } finally { - if (prev === undefined) delete process.env.GOOGLE_ANTIGRAVITY_USER_AGENT; - else process.env.GOOGLE_ANTIGRAVITY_USER_AGENT = prev; + if (prevGoogle === undefined) delete process.env.GOOGLE_ANTIGRAVITY_USER_AGENT; + else process.env.GOOGLE_ANTIGRAVITY_USER_AGENT = prevGoogle; + } + }); + + test("whitespace-only GOOGLE_ANTIGRAVITY_USER_AGENT falls back to default UA", async () => { + const prevGoogle = process.env.GOOGLE_ANTIGRAVITY_USER_AGENT; + try { + process.env.GOOGLE_ANTIGRAVITY_USER_AGENT = " "; + expect(antigravityUserAgent()).toBe( + `antigravity/ide/${ANTIGRAVITY_IDE_VERSION} (os_type=windows; arch=amd64; aidev_client; auth_method=oauth)`, + ); + } finally { + if (prevGoogle === undefined) delete process.env.GOOGLE_ANTIGRAVITY_USER_AGENT; + else process.env.GOOGLE_ANTIGRAVITY_USER_AGENT = prevGoogle; } }); diff --git a/tests/google-antigravity-wire.test.ts b/tests/google-antigravity-wire.test.ts index cc3b0d458f..b7274aed31 100644 --- a/tests/google-antigravity-wire.test.ts +++ b/tests/google-antigravity-wire.test.ts @@ -59,7 +59,7 @@ describe("antigravity CCA envelope", () => { // The exact default must not drift: Google gates models by family AND version, // so any change to version/platform could silently re-lock gemini-3.7-flash. expect(req.headers["User-Agent"]).toBe( - "antigravity/ide/2.5.5 (aidev_client; os_type=windows; arch=amd64)", + "antigravity/ide/2.5.5 (os_type=windows; arch=amd64; aidev_client; auth_method=oauth)", ); // The literal "antigravity" giveaway UA must no longer be sent. expect(req.headers["User-Agent"]).not.toBe("antigravity");