mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
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:
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
102
open-sse/services/antigravityIdentity.ts
Normal file
102
open-sse/services/antigravityIdentity.ts
Normal 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;
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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" }] },
|
||||
]);
|
||||
|
||||
@@ -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" } },
|
||||
|
||||
@@ -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"));
|
||||
});
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user