feat(antigravity): overhaul identity, fingerprinting & envelope format

- Add centralized antigravityIdentity service (sessionId, machineId, requestId)
- Switch User-Agent to Electron/Chrome desktop format
- Reorder upstream URLs: sandbox first, production last
- Add runtime headers: x-client-name, x-client-version, x-machine-id, x-vscode-sessionid, x-goog-user-project
- Add 403 retry without x-goog-user-project header
- Add generation defaults (topK=40, topP=1.0, maxOutputTokens guard)
- Strip cache_control from Claude requests recursively
- Enterprise/consumer routing via userAgent field (jetski vs antigravity)
- Update envelope field order and add enabledCreditTypes
- MITM proxy: support multiple target hosts
- Version: semver comparison with pickNewestVersion(), bump fallback to 4.1.33
- Update all affected tests
This commit is contained in:
ivan_yakimkin
2026-05-07 18:14:55 +03:00
parent 08e18867fd
commit f1af90e97e
23 changed files with 458 additions and 138 deletions

View File

@@ -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";

View File

@@ -175,17 +175,22 @@ export const CLI_FINGERPRINTS: Record<string, CliFingerprint> = {
"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,
},

View File

@@ -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 = {

View File

@@ -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<string, unknown> & {
project: string;
model: string;
userAgent: "antigravity";
requestType: "agent";
userAgent: "antigravity" | "jetski";
requestType: "agent" | "image_gen";
requestId: string;
request: Record<string, unknown>;
enabledCreditTypes?: string[];
};
/**
@@ -235,6 +249,70 @@ function getRequestTargetModel(body: Record<string, unknown>): 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<string, unknown>).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<string, string>,
credentials: Record<string, unknown> | 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<string, string>, 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<string, unknown>): void {
const generationConfig =
request.generationConfig && typeof request.generationConfig === "object"
? (request.generationConfig as Record<string, unknown>)
: {};
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<string, unknown>)
: 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",

View File

@@ -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",

View File

@@ -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<string> {
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<string, string> {
@@ -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
);

View File

@@ -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<string, unknown> | 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;
}

View File

@@ -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 | null | undefined>): 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<unknown> {
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 = {

View File

@@ -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",
};

View File

@@ -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<string, unknown> = {};
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
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)) {

View File

@@ -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;

View File

@@ -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(),

View File

@@ -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),
});

View File

@@ -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",

View File

@@ -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",
};

View File

@@ -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) => {

View File

@@ -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");
});

View File

@@ -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<string, string>;
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" }] },
]);

View File

@@ -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" } },

View File

@@ -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"));
});

View File

@@ -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());
});

View File

@@ -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);

View File

@@ -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);
});