diff --git a/open-sse/config/antigravityUpstream.ts b/open-sse/config/antigravityUpstream.ts index e020723d15..843f917d82 100644 --- a/open-sse/config/antigravityUpstream.ts +++ b/open-sse/config/antigravityUpstream.ts @@ -1,7 +1,7 @@ export const ANTIGRAVITY_BASE_URLS = Object.freeze([ - "https://cloudcode-pa.googleapis.com", - "https://daily-cloudcode-pa.googleapis.com", "https://daily-cloudcode-pa.sandbox.googleapis.com", + "https://daily-cloudcode-pa.googleapis.com", + "https://cloudcode-pa.googleapis.com", ]); const ANTIGRAVITY_MODELS_PATH = "/v1internal:models"; diff --git a/open-sse/config/cliFingerprints.ts b/open-sse/config/cliFingerprints.ts index 4449502104..2f0adf8bb7 100644 --- a/open-sse/config/cliFingerprints.ts +++ b/open-sse/config/cliFingerprints.ts @@ -175,17 +175,22 @@ export const CLI_FINGERPRINTS: Record = { "Content-Type", "Authorization", "User-Agent", + "x-client-name", + "x-client-version", + "x-machine-id", + "x-vscode-sessionid", + "x-goog-user-project", "Accept", "Accept-Encoding", ], bodyFieldOrder: [ "project", + "requestId", + "request", "model", "userAgent", "requestType", - "requestId", "enabledCreditTypes", - "request", ], userAgent: getAntigravityUserAgent, }, diff --git a/open-sse/config/constants.ts b/open-sse/config/constants.ts index 462dcd481c..3c688597e9 100644 --- a/open-sse/config/constants.ts +++ b/open-sse/config/constants.ts @@ -35,7 +35,10 @@ export const CLAUDE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official C // Antigravity default system prompt (required for API to work) export const ANTIGRAVITY_DEFAULT_SYSTEM = - "Please ignore the following [ignore]You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**[/ignore]"; + "You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.\n" + + "You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.\n" + + "**Absolute paths only**\n" + + "**Proactiveness**"; // OAuth endpoints export const OAUTH_ENDPOINTS = { diff --git a/open-sse/executors/antigravity.ts b/open-sse/executors/antigravity.ts index 0b6982d8c3..25614a2114 100644 --- a/open-sse/executors/antigravity.ts +++ b/open-sse/executors/antigravity.ts @@ -3,7 +3,10 @@ import { BaseExecutor, mergeUpstreamExtraHeaders, type ExecuteInput } from "./ba import { applyFingerprint, isCliCompatEnabled } from "../config/cliFingerprints.ts"; import { PROVIDERS, OAUTH_ENDPOINTS, HTTP_STATUS } from "../config/constants.ts"; import { scrubProxyAndFingerprintHeaders } from "../services/antigravityHeaderScrub.ts"; -import { antigravityUserAgent } from "../services/antigravityHeaders.ts"; +import { + antigravityNativeOAuthUserAgent, + antigravityUserAgent, +} from "../services/antigravityHeaders.ts"; import { classify429, decide429, type Decision } from "../services/antigravity429Engine.ts"; import { injectCreditsField, @@ -14,13 +17,23 @@ import { } from "../services/antigravityCredits.ts"; import { persistCreditBalance, getAllPersistedCreditBalances } from "@/lib/db/creditBalance"; import { obfuscateSensitiveWords } from "../services/antigravityObfuscation.ts"; -import { resolveAntigravityVersion } from "../services/antigravityVersion.ts"; +import { + getCachedAntigravityVersion, + resolveAntigravityVersion, +} from "../services/antigravityVersion.ts"; import { resolveAntigravityModelId } from "../config/antigravityModelAliases.ts"; import { cloakAntigravityToolPayload } from "../config/toolCloaking.ts"; import { shouldStripCloudCodeThinking, stripCloudCodeThinkingConfig, } from "../services/cloudCodeThinking.ts"; +import { + deriveAntigravityMachineId, + generateAntigravityRequestId, + getAntigravityEnvelopeUserAgent, + getAntigravitySessionId, + getAntigravityVscodeSessionId, +} from "../services/antigravityIdentity.ts"; const MAX_RETRY_AFTER_MS = 60_000; const LONG_RETRY_THRESHOLD_MS = 60_000; @@ -63,10 +76,11 @@ type AntigravityCollectedStream = { type AntigravityRequestEnvelope = Record & { project: string; model: string; - userAgent: "antigravity"; - requestType: "agent"; + userAgent: "antigravity" | "jetski"; + requestType: "agent" | "image_gen"; requestId: string; request: Record; + enabledCreditTypes?: string[]; }; /** @@ -235,6 +249,70 @@ function getRequestTargetModel(body: Record): string { return typeof target === "string" && target.length > 0 ? target : "unknown"; } +function getProjectHeaderValue(body: unknown): string | null { + const project = + body && typeof body === "object" ? (body as Record).project : null; + if (typeof project !== "string" || project.trim().length === 0) return null; + if (project === "test-project" || project === "project-id") return null; + return project; +} + +function applyAntigravityRuntimeHeaders( + headers: Record, + credentials: Record | null | undefined, + body: unknown +): void { + headers["User-Agent"] = antigravityUserAgent(); + headers["x-client-name"] = "antigravity"; + headers["x-client-version"] = getCachedAntigravityVersion(); + headers["x-machine-id"] = deriveAntigravityMachineId(credentials); + headers["x-vscode-sessionid"] = getAntigravityVscodeSessionId(); + + const project = getProjectHeaderValue(body); + if (project) { + headers["x-goog-user-project"] = project; + } +} + +function removeHeaderCaseInsensitive(headers: Record, name: string): void { + const lowerName = name.toLowerCase(); + for (const key of Object.keys(headers)) { + if (key.toLowerCase() === lowerName) { + delete headers[key]; + } + } +} + +function applyAntigravityGenerationDefaults(request: Record): void { + const generationConfig = + request.generationConfig && typeof request.generationConfig === "object" + ? (request.generationConfig as Record) + : {}; + + if (generationConfig.topK === undefined) { + generationConfig.topK = 40; + } + if (generationConfig.topP === undefined) { + generationConfig.topP = 1.0; + } + + const thinkingConfig = + generationConfig.thinkingConfig && typeof generationConfig.thinkingConfig === "object" + ? (generationConfig.thinkingConfig as Record) + : null; + const thinkingBudget = Number(thinkingConfig?.thinkingBudget); + const maxOutputTokens = Number(generationConfig.maxOutputTokens); + if ( + Number.isFinite(thinkingBudget) && + thinkingBudget > 0 && + (!Number.isFinite(maxOutputTokens) || maxOutputTokens <= thinkingBudget) + ) { + generationConfig.maxOutputTokens = Math.floor(thinkingBudget) + 1; + } + + request.generationConfig = generationConfig; +} + export class AntigravityExecutor extends BaseExecutor { constructor() { super("antigravity", PROVIDERS.antigravity); @@ -313,7 +391,7 @@ export class AntigravityExecutor extends BaseExecutor { // exactly as generated by openaiToClaudeRequestForAntigravity, without Gemini mappings. transformedRequest = { ...normalizedBody.request, - sessionId: normalizedBody.request?.sessionId || this.generateSessionId(), + sessionId: getAntigravitySessionId(credentials, normalizedBody.request?.sessionId), }; } else { // Fix contents for Gemini models via Antigravity @@ -349,7 +427,7 @@ export class AntigravityExecutor extends BaseExecutor { transformedRequest = { ...normalizedBody.request, ...(contents.length > 0 && { contents }), - sessionId: normalizedBody.request?.sessionId || this.generateSessionId(), + sessionId: getAntigravitySessionId(credentials, normalizedBody.request?.sessionId), safetySettings: undefined, toolConfig: normalizedBody.request?.tools?.length > 0 @@ -372,6 +450,8 @@ export class AntigravityExecutor extends BaseExecutor { } } + applyAntigravityGenerationDefaults(transformedRequest); + const { project: _project, model: _model, @@ -382,15 +462,22 @@ export class AntigravityExecutor extends BaseExecutor { ...passthroughFields } = normalizedBody; - return { + const requestType = _requestType === "image_gen" ? "image_gen" : "agent"; + const envelope: AntigravityRequestEnvelope = { project: projectId, - model: upstreamModel, - userAgent: "antigravity", - requestType: "agent", - requestId: `agent-${crypto.randomUUID()}`, + requestId: generateAntigravityRequestId(), request: transformedRequest, + model: upstreamModel, + userAgent: getAntigravityEnvelopeUserAgent(credentials), + requestType, ...passthroughFields, }; + + if (requestType === "agent" && envelope.enabledCreditTypes === undefined) { + envelope.enabledCreditTypes = ["GOOGLE_ONE_AI"]; + } + + return envelope; } async refreshCredentials(credentials, log) { @@ -402,6 +489,7 @@ export class AntigravityExecutor extends BaseExecutor { headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", + "User-Agent": antigravityNativeOAuthUserAgent(), }, body: new URLSearchParams({ grant_type: "refresh_token", @@ -617,6 +705,8 @@ export class AntigravityExecutor extends BaseExecutor { requestToolNameMap = cloaked.toolNameMap; } + applyAntigravityRuntimeHeaders(headers, credentials, transformedBody); + // Credits-first: inject GOOGLE_ONE_AI upfront so we never try the normal // quota path. If credits are exhausted / disabled shouldUseCreditsFirst() // returns false and we fall back to the legacy retry-on-429 flow. @@ -636,20 +726,33 @@ export class AntigravityExecutor extends BaseExecutor { headers, transformedBody ); - const finalHeaders = serializedRequest.headers; + let finalHeaders = serializedRequest.headers; log?.debug?.( "TELEMETRY", `[Antigravity] Execute - URL: ${url}, Model: ${model}, Target: ${getRequestTargetModel(transformedBody)}, RetryAttempt: ${retryAttemptsByUrl[urlIndex]}` ); - const response = await fetch(url, { + let response = await fetch(url, { method: "POST", headers: finalHeaders, body: serializedRequest.bodyString, signal, }); + if (response.status === HTTP_STATUS.FORBIDDEN && finalHeaders["x-goog-user-project"]) { + const retryHeaders = { ...finalHeaders }; + removeHeaderCaseInsensitive(retryHeaders, "x-goog-user-project"); + log?.debug?.("RETRY", "403 with x-goog-user-project, retrying once without it"); + response = await fetch(url, { + method: "POST", + headers: retryHeaders, + body: serializedRequest.bodyString, + signal, + }); + finalHeaders = retryHeaders; + } + if (!response.ok) { log?.warn?.( "TELEMETRY", diff --git a/open-sse/services/antigravityHeaderScrub.ts b/open-sse/services/antigravityHeaderScrub.ts index 200e324957..8c4d8db781 100644 --- a/open-sse/services/antigravityHeaderScrub.ts +++ b/open-sse/services/antigravityHeaderScrub.ts @@ -30,6 +30,7 @@ const HEADERS_TO_REMOVE = [ "x-stainless-helper-method", "http-referer", "referer", + "x-goog-api-client", // Browser / Chromium fingerprint (Electron clients, NOT Node.js) "sec-ch-ua", "sec-ch-ua-mobile", diff --git a/open-sse/services/antigravityHeaders.ts b/open-sse/services/antigravityHeaders.ts index 758803ddf9..92db40d442 100644 --- a/open-sse/services/antigravityHeaders.ts +++ b/open-sse/services/antigravityHeaders.ts @@ -16,14 +16,13 @@ import { type AntigravityHeaderProfile = "loadCodeAssist" | "fetchAvailableModels" | "models"; const ANTIGRAVITY_VERSION = ANTIGRAVITY_FALLBACK_VERSION; -export const ANTIGRAVITY_LOAD_CODE_ASSIST_USER_AGENT = "google-api-nodejs-client/10.3.0"; -export const ANTIGRAVITY_LOAD_CODE_ASSIST_API_CLIENT = - "google-cloud-sdk vscode_cloudshelleditor/0.1"; +export const ANTIGRAVITY_CHROME_VERSION = "132.0.6834.160"; +export const ANTIGRAVITY_ELECTRON_VERSION = "39.2.3"; +export const ANTIGRAVITY_LOAD_CODE_ASSIST_USER_AGENT = `vscode/1.X.X (Antigravity/${ANTIGRAVITY_FALLBACK_VERSION})`; +export const ANTIGRAVITY_LOAD_CODE_ASSIST_API_CLIENT = ""; export const ANTIGRAVITY_CREDIT_PROBE_API_CLIENT = "google-genai-sdk/1.30.0 gl-node/v22.21.1"; const LOAD_CODE_ASSIST_METADATA = Object.freeze({ - ideType: "IDE_UNSPECIFIED", - platform: "PLATFORM_UNSPECIFIED", - pluginType: "GEMINI", + ideType: "ANTIGRAVITY", }); function withOptionalBearerAuth( @@ -37,20 +36,20 @@ function withOptionalBearerAuth( } /** - * Antigravity User-Agent: "antigravity/VERSION darwin/arm64" - * - * Always claims darwin/arm64 regardless of actual server OS. - * Real Antigravity is a macOS desktop tool — most users are on macOS. - * Claiming linux/amd64 from a datacenter IP is MORE suspicious than - * darwin/arm64. Matches CLIProxyAPI's proven production behavior. + * Antigravity desktop User-Agent: + * "Antigravity/VERSION (Macintosh; Intel Mac OS X 10_15_7) Chrome/132... Electron/39..." */ export function antigravityUserAgent(): string { - return `antigravity/${getCachedAntigravityVersion()} darwin/arm64`; + return `Antigravity/${getCachedAntigravityVersion()} (Macintosh; Intel Mac OS X 10_15_7) Chrome/${ANTIGRAVITY_CHROME_VERSION} Electron/${ANTIGRAVITY_ELECTRON_VERSION}`; } export async function resolveAntigravityUserAgent(): Promise { const version = await resolveAntigravityVersion(); - return `antigravity/${version} darwin/arm64`; + return `Antigravity/${version} (Macintosh; Intel Mac OS X 10_15_7) Chrome/${ANTIGRAVITY_CHROME_VERSION} Electron/${ANTIGRAVITY_ELECTRON_VERSION}`; +} + +export function antigravityNativeOAuthUserAgent(): string { + return `vscode/1.X.X (Antigravity/${getCachedAntigravityVersion()})`; } export function getAntigravityLoadCodeAssistMetadata(): Record { @@ -70,9 +69,7 @@ export function getAntigravityHeaders( return withOptionalBearerAuth( { "Content-Type": "application/json", - "User-Agent": ANTIGRAVITY_LOAD_CODE_ASSIST_USER_AGENT, - "X-Goog-Api-Client": ANTIGRAVITY_LOAD_CODE_ASSIST_API_CLIENT, - "Client-Metadata": getAntigravityLoadCodeAssistClientMetadata(), + "User-Agent": antigravityNativeOAuthUserAgent(), }, accessToken ); diff --git a/open-sse/services/antigravityIdentity.ts b/open-sse/services/antigravityIdentity.ts new file mode 100644 index 0000000000..a0bb1d902a --- /dev/null +++ b/open-sse/services/antigravityIdentity.ts @@ -0,0 +1,102 @@ +import crypto from "node:crypto"; + +type AntigravityCredentialsLike = { + accessToken?: string | null; + connectionId?: string | null; + email?: string | null; + projectId?: string | null; + providerSpecificData?: Record | null; +}; + +const FNV_OFFSET_I64 = -3750763034362895579n; +const FNV_PRIME_I64 = 1099511628211n; +const PROCESS_SESSION_ID = crypto.randomUUID(); + +function toNonEmptyString(value: unknown): string | null { + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; +} + +function getProviderDataString( + credentials: AntigravityCredentialsLike | null | undefined, + key: string +): string | null { + const data = credentials?.providerSpecificData; + return data && typeof data === "object" ? toNonEmptyString(data[key]) : null; +} + +export function getAntigravityAccountKey( + credentials?: AntigravityCredentialsLike | null +): string | null { + return ( + toNonEmptyString(credentials?.email) || + getProviderDataString(credentials, "email") || + getProviderDataString(credentials, "accountId") || + toNonEmptyString(credentials?.connectionId) || + null + ); +} + +export function isAntigravityEnterpriseAccount( + credentials?: AntigravityCredentialsLike | null +): boolean { + const email = + toNonEmptyString(credentials?.email) || getProviderDataString(credentials, "email") || ""; + return !!email && !/@(?:gmail|googlemail)\.com$/i.test(email); +} + +export function getAntigravityEnvelopeUserAgent( + credentials?: AntigravityCredentialsLike | null +): "antigravity" | "jetski" { + return isAntigravityEnterpriseAccount(credentials) ? "jetski" : "antigravity"; +} + +export function generateAntigravityRequestId(): string { + return `agent/${Date.now()}/${crypto.randomBytes(4).toString("hex")}`; +} + +export function generateAntigravitySessionId(): string { + const bytes = crypto.randomBytes(8); + const value = bytes.readBigUInt64BE() % 9_000_000_000_000_000_000n; + return `-${value.toString()}`; +} + +export function deriveAntigravitySessionId(accountKey?: string | null): string | null { + const key = toNonEmptyString(accountKey); + if (!key) return null; + + let hash = FNV_OFFSET_I64; + for (const byte of Buffer.from(key, "utf8")) { + hash = BigInt.asIntN(64, hash * FNV_PRIME_I64); + hash = BigInt.asIntN(64, hash ^ BigInt(byte)); + } + return hash.toString(); +} + +export function getAntigravitySessionId( + credentials?: AntigravityCredentialsLike | null, + fallback?: unknown +): string { + return ( + deriveAntigravitySessionId(getAntigravityAccountKey(credentials)) || + toNonEmptyString(fallback) || + generateAntigravitySessionId() + ); +} + +export function deriveAntigravityMachineId( + credentials?: AntigravityCredentialsLike | null +): string { + const key = getAntigravityAccountKey(credentials) || PROCESS_SESSION_ID; + const hex = crypto.createHash("sha256").update(`antigravity:${key}`).digest("hex"); + return [ + hex.slice(0, 8), + hex.slice(8, 12), + hex.slice(12, 16), + hex.slice(16, 20), + hex.slice(20, 32), + ].join("-"); +} + +export function getAntigravityVscodeSessionId(): string { + return PROCESS_SESSION_ID; +} diff --git a/open-sse/services/antigravityVersion.ts b/open-sse/services/antigravityVersion.ts index e299074941..b31ee6d29f 100644 --- a/open-sse/services/antigravityVersion.ts +++ b/open-sse/services/antigravityVersion.ts @@ -5,7 +5,7 @@ const ANTIGRAVITY_GITHUB_RELEASE_URL = export const ANTIGRAVITY_VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000; export const ANTIGRAVITY_VERSION_FETCH_TIMEOUT_MS = 5_000; -export const ANTIGRAVITY_FALLBACK_VERSION = "1.23.2"; +export const ANTIGRAVITY_FALLBACK_VERSION = "4.1.33"; type VersionCache = { fetchedAt: number; @@ -24,6 +24,25 @@ function normalizeVersion(value: unknown): string | null { return match ? match[1] : null; } +function compareSemver(a: string, b: string): number { + const aParts = a.split(".").map((part) => Number.parseInt(part, 10) || 0); + const bParts = b.split(".").map((part) => Number.parseInt(part, 10) || 0); + for (let i = 0; i < 3; i += 1) { + if (aParts[i] !== bParts[i]) return aParts[i] - bParts[i]; + } + return 0; +} + +function pickNewestVersion(...versions: Array): string { + return versions + .map((version) => normalizeVersion(version)) + .filter((version): version is string => !!version) + .reduce( + (best, version) => (compareSemver(version, best) > 0 ? version : best), + ANTIGRAVITY_FALLBACK_VERSION + ); +} + async function fetchJsonWithTimeout(fetchImpl: FetchLike, url: string): Promise { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), ANTIGRAVITY_VERSION_FETCH_TIMEOUT_MS); @@ -105,7 +124,9 @@ export async function resolveAntigravityVersion(fetchImpl: FetchLike = fetch): P inFlightRequest = (async () => { const resolved = await fetchLatestAntigravityVersion(fetchImpl); - const version = resolved || versionCache?.version || ANTIGRAVITY_FALLBACK_VERSION; + const version = resolved + ? pickNewestVersion(resolved, ANTIGRAVITY_FALLBACK_VERSION) + : pickNewestVersion(versionCache?.version, ANTIGRAVITY_FALLBACK_VERSION); if (resolved) { versionCache = { diff --git a/open-sse/services/usage.ts b/open-sse/services/usage.ts index 2bf2edec5f..b94eea2d21 100644 --- a/open-sse/services/usage.ts +++ b/open-sse/services/usage.ts @@ -2,7 +2,6 @@ * Usage Fetcher - Get usage data from provider APIs */ -import crypto from "node:crypto"; import { PROVIDERS } from "../config/constants.ts"; import { getAntigravityFetchAvailableModelsUrls, @@ -19,7 +18,6 @@ import { safePercentage } from "@/shared/utils/formatting"; import { fetchBailianQuota, type BailianTripleWindowQuota } from "./bailianQuotaFetcher.ts"; import { antigravityUserAgent, - getAntigravityCreditProbeApiClientHeader, getAntigravityHeaders, getAntigravityLoadCodeAssistMetadata, } from "./antigravityHeaders.ts"; @@ -28,11 +26,18 @@ import { updateAntigravityRemainingCredits, } from "../executors/antigravity.ts"; import { getCreditsMode } from "./antigravityCredits.ts"; +import { + deriveAntigravityMachineId, + generateAntigravityRequestId, + getAntigravitySessionId, + getAntigravityVscodeSessionId, +} from "./antigravityIdentity.ts"; +import { getCachedAntigravityVersion } from "./antigravityVersion.ts"; // Antigravity API config (credentials from PROVIDERS via credential loader) const ANTIGRAVITY_CONFIG = { quotaApiUrls: getAntigravityFetchAvailableModelsUrls(), - loadProjectApiUrl: "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist", + loadProjectApiUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:loadCodeAssist", tokenUrl: "https://oauth2.googleapis.com/token", get clientId() { return PROVIDERS.antigravity.clientId; @@ -1493,13 +1498,13 @@ async function probeAntigravityCreditBalanceUncached( for (const baseUrl of ANTIGRAVITY_BASE_URLS) { const url = `${baseUrl}/v1internal:streamGenerateContent?alt=sse`; - const sessionId = `-${crypto.randomUUID()}`; + const sessionId = getAntigravitySessionId({ connectionId: accountId, projectId }); const body = { project: projectId, model: "gemini-2-flash", userAgent: "antigravity", requestType: "agent", - requestId: `credits-probe-${Date.now()}`, + requestId: generateAntigravityRequestId(), enabledCreditTypes: ["GOOGLE_ONE_AI"], request: { model: "gemini-2-flash", @@ -1513,7 +1518,11 @@ async function probeAntigravityCreditBalanceUncached( "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, "User-Agent": antigravityUserAgent(), - "X-Goog-Api-Client": getAntigravityCreditProbeApiClientHeader(), + "x-client-name": "antigravity", + "x-client-version": getCachedAntigravityVersion(), + "x-machine-id": deriveAntigravityMachineId({ connectionId: accountId, projectId }), + "x-vscode-sessionid": getAntigravityVscodeSessionId(), + "x-goog-user-project": projectId, Accept: "text/event-stream", }; diff --git a/open-sse/translator/request/openai-to-claude.ts b/open-sse/translator/request/openai-to-claude.ts index 0b48332a0b..8d03a0382e 100644 --- a/open-sse/translator/request/openai-to-claude.ts +++ b/open-sse/translator/request/openai-to-claude.ts @@ -543,9 +543,27 @@ function tryParseJSON(str) { } } +function stripCacheControl(value: unknown): unknown { + if (Array.isArray(value)) { + return value.map((item) => stripCacheControl(item)); + } + if (!value || typeof value !== "object") { + return value; + } + + const cleaned: Record = {}; + for (const [key, child] of Object.entries(value as Record)) { + if (key === "cache_control") continue; + cleaned[key] = stripCacheControl(child); + } + return cleaned; +} + // OpenAI -> Claude format for Antigravity (without system prompt modifications) function openaiToClaudeRequestForAntigravity(model, body, stream) { - const result = openaiToClaudeRequest(model, body, stream); + const result = stripCacheControl(openaiToClaudeRequest(model, body, stream)) as ReturnType< + typeof openaiToClaudeRequest + >; // Strip prefix from tool names for Antigravity (doesn't use Claude OAuth) if (result.tools && Array.isArray(result.tools)) { diff --git a/open-sse/translator/request/openai-to-gemini.ts b/open-sse/translator/request/openai-to-gemini.ts index 018c6fdb5a..e79f0653fb 100644 --- a/open-sse/translator/request/openai-to-gemini.ts +++ b/open-sse/translator/request/openai-to-gemini.ts @@ -4,6 +4,11 @@ import { DEFAULT_THINKING_GEMINI_SIGNATURE } from "../../config/defaultThinkingS import { ANTIGRAVITY_DEFAULT_SYSTEM } from "../../config/constants.ts"; import { resolveGeminiThoughtSignature } from "../../services/geminiThoughtSignatureStore.ts"; import { openaiToClaudeRequestForAntigravity } from "./openai-to-claude.ts"; +import { + generateAntigravityRequestId, + getAntigravityEnvelopeUserAgent, + getAntigravitySessionId, +} from "../../services/antigravityIdentity.ts"; import { capMaxOutputTokens, capThinkingBudget, @@ -74,9 +79,10 @@ type CloudCodeEnvelope = { project: string; model: string; user_prompt_id?: string; - userAgent?: string; + userAgent?: "antigravity" | "jetski" | string; requestId?: string; requestType?: string; + enabledCreditTypes?: string[]; request: { session_id?: string; sessionId?: string; @@ -130,6 +136,28 @@ function extractClientThoughtSignature(toolCall: unknown): string | null { return typeof signature === "string" && signature.length > 0 ? signature : null; } +function applyAntigravityGenerationDefaults(generationConfig: GeminiGenerationConfig) { + const config = { ...generationConfig }; + if (config.topK === undefined) { + config.topK = 40; + } + if (config.topP === undefined) { + config.topP = 1.0; + } + + const thinkingBudget = Number(config.thinkingConfig?.thinkingBudget); + const maxOutputTokens = Number(config.maxOutputTokens); + if ( + Number.isFinite(thinkingBudget) && + thinkingBudget > 0 && + (!Number.isFinite(maxOutputTokens) || maxOutputTokens <= thinkingBudget) + ) { + config.maxOutputTokens = Math.floor(thinkingBudget) + 1; + } + + return config; +} + // Core: Convert OpenAI request to Gemini format (base for all variants) function openaiToGeminiBase(model, body, stream, toolNameOptions: GeminiToolNameOptions = {}) { const result: GeminiRequest = { @@ -421,17 +449,18 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra const envelope: CloudCodeEnvelope = isAntigravity ? { project: projectId, - model: cleanModel, - userAgent: "antigravity", - requestType: "agent", - requestId: `agent-${generateUUID()}`, + requestId: generateAntigravityRequestId(), request: { - sessionId: generateSessionId(), + sessionId: getAntigravitySessionId(credentials), contents: geminiCLI.contents, systemInstruction: geminiCLI.systemInstruction, - generationConfig: geminiCLI.generationConfig, + generationConfig: applyAntigravityGenerationDefaults(geminiCLI.generationConfig), tools: geminiCLI.tools, }, + model: cleanModel, + userAgent: getAntigravityEnvelopeUserAgent(credentials), + requestType: "agent", + enabledCreditTypes: ["GOOGLE_ONE_AI"], } : { model: cleanModel, @@ -512,16 +541,17 @@ function wrapInCloudCodeEnvelopeForClaude( const envelope: CloudCodeEnvelope = { project: projectId, - model: cleanModel, - userAgent: "antigravity", - requestId: `agent-${generateUUID()}`, - requestType: "agent", + requestId: generateAntigravityRequestId(), request: { ...claudeRequest, system: systemText, max_tokens: getAntigravityClaudeOutputTokens(sourceBody), - sessionId: generateSessionId(), + sessionId: getAntigravitySessionId(credentials), }, + model: cleanModel, + userAgent: getAntigravityEnvelopeUserAgent(credentials), + requestType: "agent", + enabledCreditTypes: ["GOOGLE_ONE_AI"], }; return envelope; diff --git a/src/lib/oauth/constants/oauth.ts b/src/lib/oauth/constants/oauth.ts index b5f1d30c84..6a32388d20 100644 --- a/src/lib/oauth/constants/oauth.ts +++ b/src/lib/oauth/constants/oauth.ts @@ -63,7 +63,9 @@ export const GEMINI_CONFIG = { process.env.GEMINI_OAUTH_CLIENT_ID || "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com", clientSecret: - process.env.GEMINI_CLI_OAUTH_CLIENT_SECRET || process.env.GEMINI_OAUTH_CLIENT_SECRET || "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", + process.env.GEMINI_CLI_OAUTH_CLIENT_SECRET || + process.env.GEMINI_OAUTH_CLIENT_SECRET || + "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", authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth", tokenUrl: "https://oauth2.googleapis.com/token", userInfoUrl: "https://www.googleapis.com/oauth2/v1/userinfo", @@ -150,12 +152,13 @@ export const ANTIGRAVITY_CONFIG = { "https://www.googleapis.com/auth/experimentsandconfigs", ], // Antigravity specific - apiEndpoint: "https://cloudcode-pa.googleapis.com", + apiEndpoint: "https://daily-cloudcode-pa.sandbox.googleapis.com", apiVersion: "v1internal", - loadCodeAssistEndpoint: "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist", - onboardUserEndpoint: "https://cloudcode-pa.googleapis.com/v1internal:onboardUser", + loadCodeAssistEndpoint: + "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:loadCodeAssist", + onboardUserEndpoint: "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:onboardUser", fetchAvailableModelsEndpoint: - "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels", + "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels", loadCodeAssistUserAgent: ANTIGRAVITY_LOAD_CODE_ASSIST_USER_AGENT, loadCodeAssistApiClient: ANTIGRAVITY_LOAD_CODE_ASSIST_API_CLIENT, loadCodeAssistClientMetadata: getAntigravityLoadCodeAssistClientMetadata(), diff --git a/src/lib/oauth/providers/antigravity.ts b/src/lib/oauth/providers/antigravity.ts index be053ee464..96e33520a0 100644 --- a/src/lib/oauth/providers/antigravity.ts +++ b/src/lib/oauth/providers/antigravity.ts @@ -1,5 +1,6 @@ import { ANTIGRAVITY_CONFIG } from "../constants/oauth"; import { + antigravityNativeOAuthUserAgent, getAntigravityHeaders, getAntigravityLoadCodeAssistMetadata, } from "@omniroute/open-sse/services/antigravityHeaders.ts"; @@ -36,6 +37,7 @@ export const antigravity = { headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", + "User-Agent": antigravityNativeOAuthUserAgent(), }, body: new URLSearchParams(bodyParams), }); diff --git a/src/lib/oauth/services/antigravity.ts b/src/lib/oauth/services/antigravity.ts index 4db27b5e21..1deb5b4fd7 100644 --- a/src/lib/oauth/services/antigravity.ts +++ b/src/lib/oauth/services/antigravity.ts @@ -2,6 +2,7 @@ import crypto from "crypto"; import open from "open"; import { ANTIGRAVITY_CONFIG } from "../constants/oauth"; import { + antigravityNativeOAuthUserAgent, getAntigravityHeaders, getAntigravityLoadCodeAssistMetadata, } from "@omniroute/open-sse/services/antigravityHeaders.ts"; @@ -46,6 +47,7 @@ export class AntigravityService { headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", + "User-Agent": antigravityNativeOAuthUserAgent(), }, body: new URLSearchParams({ grant_type: "authorization_code", diff --git a/src/lib/usage/fetcher.ts b/src/lib/usage/fetcher.ts index aa90bacd49..a0fefb3380 100644 --- a/src/lib/usage/fetcher.ts +++ b/src/lib/usage/fetcher.ts @@ -10,7 +10,6 @@ import { import { getAntigravityHeaders, antigravityUserAgent, - getAntigravityCreditProbeApiClientHeader, } from "@omniroute/open-sse/services/antigravityHeaders.ts"; import { getAntigravityFetchAvailableModelsUrls, @@ -21,6 +20,13 @@ import { updateAntigravityRemainingCredits, } from "@omniroute/open-sse/executors/antigravity.ts"; import { getCreditsMode } from "@omniroute/open-sse/services/antigravityCredits.ts"; +import { + deriveAntigravityMachineId, + generateAntigravityRequestId, + getAntigravitySessionId, + getAntigravityVscodeSessionId, +} from "@omniroute/open-sse/services/antigravityIdentity.ts"; +import { getCachedAntigravityVersion } from "@omniroute/open-sse/services/antigravityVersion.ts"; /** * Get usage data for a provider connection @@ -189,12 +195,13 @@ async function probeAntigravityCreditBalance( model: "gemini-2-flash", userAgent: "antigravity", requestType: "agent", - requestId: `credits-probe-${Date.now()}`, + requestId: generateAntigravityRequestId(), enabledCreditTypes: ["GOOGLE_ONE_AI"], request: { model: "gemini-2-flash", contents: [{ role: "user", parts: [{ text: "hi" }] }], generationConfig: { maxOutputTokens: 1 }, + sessionId: getAntigravitySessionId({ connectionId: accountId, projectId }), }, }; @@ -202,7 +209,11 @@ async function probeAntigravityCreditBalance( "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, "User-Agent": antigravityUserAgent(), - "X-Goog-Api-Client": getAntigravityCreditProbeApiClientHeader(), + "x-client-name": "antigravity", + "x-client-version": getCachedAntigravityVersion(), + "x-machine-id": deriveAntigravityMachineId({ connectionId: accountId, projectId }), + "x-vscode-sessionid": getAntigravityVscodeSessionId(), + "x-goog-user-project": projectId, Accept: "text/event-stream", }; diff --git a/src/mitm/server.cjs b/src/mitm/server.cjs index 532fa0e3b3..9b6ac9b02b 100644 --- a/src/mitm/server.cjs +++ b/src/mitm/server.cjs @@ -13,7 +13,11 @@ function getDataDir() { } // Configuration -const TARGET_HOST = "daily-cloudcode-pa.googleapis.com"; +const TARGET_HOSTS = new Set([ + "daily-cloudcode-pa.sandbox.googleapis.com", + "daily-cloudcode-pa.googleapis.com", + "cloudcode-pa.googleapis.com", +]); const parsedLocalPort = Number.parseInt(process.env.MITM_LOCAL_PORT || "443", 10); const LOCAL_PORT = Number.isInteger(parsedLocalPort) && parsedLocalPort > 0 && parsedLocalPort <= 65535 @@ -112,15 +116,23 @@ function saveResponseLog(url, data) { } // Resolve real IP of target host (bypass /etc/hosts) -let cachedTargetIP = null; -async function resolveTargetIP() { - if (cachedTargetIP) return cachedTargetIP; +const cachedTargetIPs = new Map(); +function getTargetHost(req) { + const host = String(req.headers.host || "") + .split(":")[0] + .toLowerCase(); + return TARGET_HOSTS.has(host) ? host : "daily-cloudcode-pa.sandbox.googleapis.com"; +} + +async function resolveTargetIP(targetHost) { + if (cachedTargetIPs.has(targetHost)) return cachedTargetIPs.get(targetHost); const resolver = new dns.Resolver(); resolver.setServers(["8.8.8.8"]); const resolve4 = promisify(resolver.resolve4.bind(resolver)); - const addresses = await resolve4(TARGET_HOST); - cachedTargetIP = addresses[0]; - return cachedTargetIP; + const addresses = await resolve4(targetHost); + const targetIP = addresses[0]; + cachedTargetIPs.set(targetHost, targetIP); + return targetIP; } function collectBodyRaw(req) { @@ -193,7 +205,8 @@ function getMappedModel(model) { } async function passthrough(req, res, bodyBuffer) { - const targetIP = await resolveTargetIP(); + const targetHost = getTargetHost(req); + const targetIP = await resolveTargetIP(targetHost); // TLS validation is enabled by default. Set MITM_DISABLE_TLS_VERIFY=1 only // in controlled local environments where the target uses a self-signed cert. @@ -205,8 +218,8 @@ async function passthrough(req, res, bodyBuffer) { port: 443, path: req.url, method: req.method, - headers: { ...req.headers, host: TARGET_HOST }, - servername: TARGET_HOST, + headers: { ...req.headers, host: targetHost }, + servername: targetHost, rejectUnauthorized, }, (forwardRes) => { diff --git a/tests/unit/antigravity-version.test.ts b/tests/unit/antigravity-version.test.ts index a2566ed60e..d1c0bae454 100644 --- a/tests/unit/antigravity-version.test.ts +++ b/tests/unit/antigravity-version.test.ts @@ -21,7 +21,7 @@ test("resolveAntigravityVersion uses the official release feed and caches the re let calls = 0; const fetchMock = async () => { calls += 1; - return new Response(JSON.stringify([{ version: "1.22.2", execution_id: "4781536860569600" }]), { + return new Response(JSON.stringify([{ version: "4.2.0", execution_id: "4781536860569600" }]), { status: 200, headers: { "Content-Type": "application/json" }, }); @@ -30,10 +30,10 @@ test("resolveAntigravityVersion uses the official release feed and caches the re const first = await resolveAntigravityVersion(fetchMock as typeof fetch); const second = await resolveAntigravityVersion(fetchMock as typeof fetch); - assert.equal(first, "1.22.2"); - assert.equal(second, "1.22.2"); + assert.equal(first, "4.2.0"); + assert.equal(second, "4.2.0"); assert.equal(calls, 1); - assert.equal(getCachedAntigravityVersion(), "1.22.2"); + assert.equal(getCachedAntigravityVersion(), "4.2.0"); }); test("resolveAntigravityVersion refreshes the cache after the TTL elapses", async () => { @@ -41,22 +41,22 @@ test("resolveAntigravityVersion refreshes the cache after the TTL elapses", asyn Date.now = () => now; const firstFetch = async () => - new Response(JSON.stringify([{ version: "1.22.2" }]), { + new Response(JSON.stringify([{ version: "4.2.0" }]), { status: 200, headers: { "Content-Type": "application/json" }, }); const secondFetch = async () => - new Response(JSON.stringify([{ version: "1.24.0" }]), { + new Response(JSON.stringify([{ version: "4.3.0" }]), { status: 200, headers: { "Content-Type": "application/json" }, }); - assert.equal(await resolveAntigravityVersion(firstFetch as typeof fetch), "1.22.2"); + assert.equal(await resolveAntigravityVersion(firstFetch as typeof fetch), "4.2.0"); now += ANTIGRAVITY_VERSION_CACHE_TTL_MS + 1; - assert.equal(await resolveAntigravityVersion(secondFetch as typeof fetch), "1.24.0"); - assert.equal(getCachedAntigravityVersion(), "1.24.0"); + assert.equal(await resolveAntigravityVersion(secondFetch as typeof fetch), "4.3.0"); + assert.equal(getCachedAntigravityVersion(), "4.3.0"); }); test("resolveAntigravityVersion falls back to the last known good version or bundled fallback", async () => { @@ -69,8 +69,8 @@ test("resolveAntigravityVersion falls back to the last known good version or bun ANTIGRAVITY_FALLBACK_VERSION ); - seedAntigravityVersionCache("1.23.1", 0); - assert.equal(await resolveAntigravityVersion(failingFetch as typeof fetch), "1.23.1"); + seedAntigravityVersionCache("4.2.1", 0); + assert.equal(await resolveAntigravityVersion(failingFetch as typeof fetch), "4.2.1"); }); test("resolveAntigravityVersion parses GitHub-style tag_name payloads with or without a v prefix", async () => { @@ -84,11 +84,11 @@ test("resolveAntigravityVersion parses GitHub-style tag_name payloads with or wi }); } - return new Response(JSON.stringify({ tag_name: "v1.24.3" }), { + return new Response(JSON.stringify({ tag_name: "v4.2.3" }), { status: 200, headers: { "Content-Type": "application/json" }, }); }; - assert.equal(await resolveAntigravityVersion(fetchMock as typeof fetch), "1.24.3"); + assert.equal(await resolveAntigravityVersion(fetchMock as typeof fetch), "4.2.3"); }); diff --git a/tests/unit/executor-antigravity.test.ts b/tests/unit/executor-antigravity.test.ts index 993ca818af..d3d48a66df 100644 --- a/tests/unit/executor-antigravity.test.ts +++ b/tests/unit/executor-antigravity.test.ts @@ -50,6 +50,7 @@ test("AntigravityExecutor.buildHeaders includes native headers without OmniRoute assert.equal(headers.Authorization, "Bearer ag-token"); assert.equal(headers.Accept, "text/event-stream"); + assert.match(headers["User-Agent"], /^Antigravity\/4\.1\.33 /); assert.equal(headers["X-OmniRoute-Source"], undefined); }); @@ -98,14 +99,19 @@ test("AntigravityExecutor.transformRequest normalizes model, project and content assert.equal(result.model, "gemini-3.1-pro-low"); assert.deepEqual(Object.keys(result), [ "project", + "requestId", + "request", "model", "userAgent", "requestType", - "requestId", - "request", + "enabledCreditTypes", ]); assert.equal(result.userAgent, "antigravity"); + assert.match(result.requestId, /^agent\/\d+\/[0-9a-f]{8}$/); + assert.deepEqual(result.enabledCreditTypes, ["GOOGLE_ONE_AI"]); assert.ok(result.request.sessionId); + assert.equal(result.request.generationConfig.topK, 40); + assert.equal(result.request.generationConfig.topP, 1.0); assert.deepEqual(result.request.toolConfig, { functionCallingConfig: { mode: "VALIDATED" }, }); @@ -466,15 +472,21 @@ test("AntigravityExecutor.execute applies CLI fingerprint when enabled", async ( const headers = init?.headers as Record; const parsedBody = JSON.parse(String(init?.body)); - assert.equal(headers["User-Agent"], "antigravity/2026.04.17-test darwin/arm64"); + assert.equal( + headers["User-Agent"], + "Antigravity/2026.04.17-test (Macintosh; Intel Mac OS X 10_15_7) Chrome/132.0.6834.160 Electron/39.2.3" + ); + assert.equal(headers["x-client-name"], "antigravity"); + assert.equal(headers["x-client-version"], "2026.04.17-test"); + assert.equal(headers["x-goog-user-project"], "project-1"); assert.deepEqual(Object.keys(parsedBody), [ "project", + "requestId", + "request", "model", "userAgent", "requestType", - "requestId", "enabledCreditTypes", - "request", ]); return new Response( @@ -530,6 +542,7 @@ test("AntigravityExecutor.transformRequest bypasses Gemini contents mapping for assert.equal(result.model, "claude-sonnet-4-6"); assert.equal(result.requestType, "agent"); assert.ok(result.request.sessionId); + assert.deepEqual(result.enabledCreditTypes, ["GOOGLE_ONE_AI"]); assert.deepEqual(result.request.messages, [ { role: "user", content: [{ type: "text", text: "Hello" }] }, ]); diff --git a/tests/unit/oauth-providers-config.test.ts b/tests/unit/oauth-providers-config.test.ts index 2ab7555dd8..806b6addb4 100644 --- a/tests/unit/oauth-providers-config.test.ts +++ b/tests/unit/oauth-providers-config.test.ts @@ -438,19 +438,10 @@ test("Gemini and Antigravity run mocked browser OAuth exchanges and post-exchang (_url, init = {}) => { assert.equal(init.method, "POST"); assert.equal(init.headers.Authorization, "Bearer anti-access"); - assert.equal(init.headers["User-Agent"], "google-api-nodejs-client/10.3.0"); - assert.equal( - init.headers["X-Goog-Api-Client"], - "google-cloud-sdk vscode_cloudshelleditor/0.1" - ); - assert.equal( - init.headers["Client-Metadata"], - JSON.stringify({ - ideType: "IDE_UNSPECIFIED", - platform: "PLATFORM_UNSPECIFIED", - pluginType: "GEMINI", - }) - ); + assert.match(init.headers["User-Agent"], /^vscode\/1\.X\.X \(Antigravity\//); + assert.equal(init.headers["X-Goog-Api-Client"], undefined); + assert.equal(init.headers["Client-Metadata"], undefined); + assert.deepEqual(JSON.parse(String(init.body)).metadata, { ideType: "ANTIGRAVITY" }); return jsonResponse({ cloudaicompanionProject: { id: "anti-project" }, allowedTiers: [{ id: "tier-default", isDefault: true }], @@ -459,11 +450,9 @@ test("Gemini and Antigravity run mocked browser OAuth exchanges and post-exchang (_url, init = {}) => { assert.equal(init.method, "POST"); assert.equal(init.headers.Authorization, "Bearer anti-access"); - assert.equal(init.headers["User-Agent"], "google-api-nodejs-client/10.3.0"); - assert.equal( - init.headers["X-Goog-Api-Client"], - "google-cloud-sdk vscode_cloudshelleditor/0.1" - ); + assert.match(init.headers["User-Agent"], /^vscode\/1\.X\.X \(Antigravity\//); + assert.equal(init.headers["X-Goog-Api-Client"], undefined); + assert.deepEqual(JSON.parse(String(init.body)).metadata, { ideType: "ANTIGRAVITY" }); return jsonResponse({ done: true, response: { cloudaicompanionProject: { id: "anti-project-final" } }, diff --git a/tests/unit/provider-models-route.test.ts b/tests/unit/provider-models-route.test.ts index c781105d0f..b62d7b357d 100644 --- a/tests/unit/provider-models-route.test.ts +++ b/tests/unit/provider-models-route.test.ts @@ -651,7 +651,7 @@ test("provider models route retries Antigravity discovery endpoints before retur assert.equal(init.method, "POST"); assert.equal(init.headers.Authorization, "Bearer ag-access"); - assert.match(init.headers["User-Agent"], /^antigravity\//); + assert.match(init.headers["User-Agent"], /^Antigravity\//); return Response.json({ models: [{ id: "gemini-3-flash", displayName: "Gemini 3 Flash" }], }); @@ -664,7 +664,7 @@ test("provider models route retries Antigravity discovery endpoints before retur assert.equal(response.status, 200); assert.equal(body.source, "api"); assert.deepEqual(discoveryUrls, [ - "https://cloudcode-pa.googleapis.com/v1internal:models", + "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:models", "https://daily-cloudcode-pa.googleapis.com/v1internal:models", ]); assert.deepEqual(body.models, [{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash" }]); @@ -691,9 +691,9 @@ test("provider models route falls back through all Antigravity discovery endpoin assert.equal(body.source, "local_catalog"); assert.match(body.warning, /local catalog/i); assert.deepEqual(discoveryUrls, [ - "https://cloudcode-pa.googleapis.com/v1internal:models", - "https://daily-cloudcode-pa.googleapis.com/v1internal:models", "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:models", + "https://daily-cloudcode-pa.googleapis.com/v1internal:models", + "https://cloudcode-pa.googleapis.com/v1internal:models", ]); assert.ok(body.models.some((model) => model.id === "gemini-3-pro-preview")); }); diff --git a/tests/unit/t20-t22-provider-headers.test.ts b/tests/unit/t20-t22-provider-headers.test.ts index 03d95da978..2793572a11 100644 --- a/tests/unit/t20-t22-provider-headers.test.ts +++ b/tests/unit/t20-t22-provider-headers.test.ts @@ -9,9 +9,7 @@ const { geminiCliUserAgent, GEMINI_CLI_VERSION } = test("T20: antigravity config has updated User-Agent and sandbox fallback URL", () => { const antigravity = REGISTRY.antigravity; assert.ok(Array.isArray(antigravity.baseUrls)); - assert.ok( - antigravity.baseUrls.some((u) => u === "https://daily-cloudcode-pa.sandbox.googleapis.com") - ); + assert.equal(antigravity.baseUrls[0], "https://daily-cloudcode-pa.sandbox.googleapis.com"); assert.equal(antigravity.headers["User-Agent"], antigravityUserAgent()); }); diff --git a/tests/unit/translator-openai-to-gemini.test.ts b/tests/unit/translator-openai-to-gemini.test.ts index afac00bd68..5dcd93aec6 100644 --- a/tests/unit/translator-openai-to-gemini.test.ts +++ b/tests/unit/translator-openai-to-gemini.test.ts @@ -597,16 +597,20 @@ test("OpenAI -> Antigravity wraps Gemini requests in a Cloud Code envelope", () assert.equal(result.project, "proj-1"); assert.deepEqual(Object.keys(result), [ "project", + "requestId", + "request", "model", "userAgent", "requestType", - "requestId", - "request", + "enabledCreditTypes", ]); assert.equal(result.userAgent, "antigravity"); assert.equal(result.requestType, "agent"); - assert.match(result.requestId, /^agent-/); - assert.match(result.request.sessionId, /^-\d+$/); + assert.match(result.requestId, /^agent\/\d+\/[0-9a-f]{8}$/); + assert.match(result.request.sessionId, /^-?\d+$/); + assert.deepEqual(result.enabledCreditTypes, ["GOOGLE_ONE_AI"]); + assert.equal(result.request.generationConfig.topK, 40); + assert.equal(result.request.generationConfig.topP, 1.0); assert.equal( (result as any).request?.systemInstruction.parts[0].text, ANTIGRAVITY_DEFAULT_SYSTEM @@ -659,6 +663,8 @@ test("OpenAI -> Antigravity uses the Claude bridge for Claude-family models", () assert.equal(result.project, "proj-claude"); assert.equal(result.userAgent, "antigravity"); + assert.match(result.requestId, /^agent\/\d+\/[0-9a-f]{8}$/); + assert.deepEqual((result as any).enabledCreditTypes, ["GOOGLE_ONE_AI"]); assert.ok((result as any).request?.system.includes(ANTIGRAVITY_DEFAULT_SYSTEM)); assert.ok((result as any).request?.system.includes("Project rules")); assert.equal((result as any).request?.max_tokens, 16384); diff --git a/tests/unit/usage-service-hardening.test.ts b/tests/unit/usage-service-hardening.test.ts index 8642c6564b..5c0ebde4bd 100644 --- a/tests/unit/usage-service-hardening.test.ts +++ b/tests/unit/usage-service-hardening.test.ts @@ -323,19 +323,13 @@ test("usage service covers Antigravity quota parsing, exclusions and forbidden a assert.equal(usage.quotas["gemini-3.1-pro-high"].total, 0); assert.equal(usage.quotas["gemini-3.1-pro-high"].remainingPercentage, 100); const loadCodeAssistCall = calls.find((call) => call.url.includes("loadCodeAssist")); - assert.equal(loadCodeAssistCall?.init.headers["User-Agent"], "google-api-nodejs-client/10.3.0"); - assert.equal( - loadCodeAssistCall?.init.headers["X-Goog-Api-Client"], - "google-cloud-sdk vscode_cloudshelleditor/0.1" - ); - assert.equal( - loadCodeAssistCall?.init.headers["Client-Metadata"], - JSON.stringify({ - ideType: "IDE_UNSPECIFIED", - platform: "PLATFORM_UNSPECIFIED", - pluginType: "GEMINI", - }) - ); + assert.match(loadCodeAssistCall?.url, /daily-cloudcode-pa\.sandbox\.googleapis\.com/); + assert.match(loadCodeAssistCall?.init.headers["User-Agent"], /^vscode\/1\.X\.X \(Antigravity\//); + assert.equal(loadCodeAssistCall?.init.headers["X-Goog-Api-Client"], undefined); + assert.equal(loadCodeAssistCall?.init.headers["Client-Metadata"], undefined); + assert.deepEqual(JSON.parse(loadCodeAssistCall?.init.body).metadata, { + ideType: "ANTIGRAVITY", + }); globalThis.fetch = async (url) => { if (String(url).includes("loadCodeAssist")) { @@ -369,7 +363,7 @@ test("usage service retries Antigravity fetchAvailableModels across the shared f try { const parsedUrl = new URL(String(url)); - if (parsedUrl.hostname === "cloudcode-pa.googleapis.com") { + if (parsedUrl.hostname === "daily-cloudcode-pa.sandbox.googleapis.com") { return new Response("bad gateway", { status: 502 }); } if (parsedUrl.hostname === "daily-cloudcode-pa.googleapis.com") { @@ -403,12 +397,12 @@ test("usage service retries Antigravity fetchAvailableModels across the shared f assert.deepEqual( quotaCalls.map((call) => call.url), [ - "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels", - "https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels", "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels", + "https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels", + "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels", ] ); - assert.match(quotaCalls[2].init.headers["User-Agent"], /^antigravity\//); + assert.match(quotaCalls[2].init.headers["User-Agent"], /^Antigravity\//); assert.equal(usage.plan, "Business"); assert.equal(usage.quotas["claude-sonnet-4-6"].used, 500); });