feat(antigravity): initial steps for Antigravity anti-ban alignment

Cherry-picked from decolua/9router#141 (author: LinearSakana / zx <me@char.moe>)

- Implement client identity spoofing with numeric enums (ideType: 9, pluginType: 2)
- Add runtime platform detection for User-Agent and metadata
- Implement per-connection session ID caching (binary-compatible format)
- Add ANTIGRAVITY_HEADERS (X-Client-Name, X-Client-Version, x-goog-api-client)
- Add X-Machine-Session-Id header injection
- Align metadata/mode parameters across all Antigravity API calls
- Implement double injection for system prompt (raw + [ignore] wrapped)
- Rename internal anti-loop header to x-request-source for anonymity

Skipped: commit 6 (signature side-channel caching) — kept DEFAULT_THINKING_GEMINI_SIGNATURE
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zx
2026-02-20 14:44:29 +07:00
committed by decolua
parent 9fbd6e619d
commit a229d79158
11 changed files with 309 additions and 121 deletions

View File

@@ -1,3 +1,79 @@
import { platform, arch } from "os";
// === Antigravity Binary Alignment: Numeric Enums ===
// Reference: Antigravity binary analysis - google.internal.cloud.code.v1internal.ClientMetadata
// IDE Type enum (numeric values as expected by Cloud Code API)
export const IDE_TYPE = {
UNSPECIFIED: 0,
JETSKI: 10, // Internal codename for Gemini CLI
ANTIGRAVITY: 9,
PLUGINS: 7
};
// Platform enum (as specified in Antigravity binary)
export const PLATFORM = {
UNSPECIFIED: 0,
DARWIN_AMD64: 1,
DARWIN_ARM64: 2,
LINUX_AMD64: 3,
LINUX_ARM64: 4,
WINDOWS_AMD64: 5
};
// Plugin type enum (as specified in Antigravity binary)
export const PLUGIN_TYPE = {
UNSPECIFIED: 0,
CLOUD_CODE: 1,
GEMINI: 2
};
/**
* Get the platform enum value based on the current OS.
* @returns {number} Platform enum value
*/
export function getPlatformEnum() {
const os = platform();
const architecture = arch();
if (os === "darwin") {
return architecture === "arm64" ? PLATFORM.DARWIN_ARM64 : PLATFORM.DARWIN_AMD64;
} else if (os === "linux") {
return architecture === "arm64" ? PLATFORM.LINUX_ARM64 : PLATFORM.LINUX_AMD64;
} else if (os === "win32") {
return PLATFORM.WINDOWS_AMD64;
}
return PLATFORM.UNSPECIFIED;
}
/**
* Generate platform-specific User-Agent string.
* @returns {string} User-Agent in format "antigravity/version os/arch"
*/
export function getPlatformUserAgent() {
const os = platform();
const architecture = arch();
return `antigravity/1.16.5 ${os}/${architecture}`;
}
// Centralized client metadata (used in request bodies for loadCodeAssist, onboardUser, etc.)
// Using numeric enum values as expected by the Cloud Code API
export const CLIENT_METADATA = {
ideType: IDE_TYPE.ANTIGRAVITY, // 9 - identifies as Antigravity client
platform: getPlatformEnum(), // Runtime platform detection
pluginType: PLUGIN_TYPE.GEMINI // 2
};
// Internal anti-loop header to identify requests originating from this proxy
export const INTERNAL_REQUEST_HEADER = { name: "x-request-source", value: "local" };
// Antigravity headers
export const ANTIGRAVITY_HEADERS = {
"X-Client-Name": "antigravity",
"X-Client-Version": "1.107.0",
"x-goog-api-client": "gl-node/18.18.2 fire/0.8.6 grpc/1.10.x"
};
// Provider configurations // Provider configurations
export const PROVIDERS = { export const PROVIDERS = {
claude: { claude: {
@@ -78,7 +154,7 @@ export const PROVIDERS = {
], ],
format: "antigravity", format: "antigravity",
headers: { headers: {
"User-Agent": "antigravity/1.104.0 darwin/arm64" "User-Agent": getPlatformUserAgent()
}, },
clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com", clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
clientSecret: "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" clientSecret: "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"
@@ -181,7 +257,7 @@ export const PROVIDERS = {
export const CLAUDE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude."; export const CLAUDE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";
// Antigravity default system prompt (required for API to work) // 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]"; export const ANTIGRAVITY_DEFAULT_SYSTEM = "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**";
// OAuth endpoints // OAuth endpoints
export const OAUTH_ENDPOINTS = { export const OAUTH_ENDPOINTS = {

View File

@@ -1,6 +1,7 @@
import crypto from "crypto"; import crypto from "crypto";
import { BaseExecutor } from "./base.js"; import { BaseExecutor } from "./base.js";
import { PROVIDERS, OAUTH_ENDPOINTS, HTTP_STATUS } from "../config/constants.js"; import { PROVIDERS, OAUTH_ENDPOINTS, HTTP_STATUS, ANTIGRAVITY_HEADERS, INTERNAL_REQUEST_HEADER } from "../config/constants.js";
import { deriveSessionId } from "../utils/sessionManager.js";
const MAX_RETRY_AFTER_MS = 10000; const MAX_RETRY_AFTER_MS = 10000;
@@ -16,12 +17,14 @@ export class AntigravityExecutor extends BaseExecutor {
return `${baseUrl}/v1internal:${action}`; return `${baseUrl}/v1internal:${action}`;
} }
buildHeaders(credentials, stream = true) { buildHeaders(credentials, stream = true, sessionId = null) {
return { return {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": `Bearer ${credentials.accessToken}`, "Authorization": `Bearer ${credentials.accessToken}`,
"User-Agent": this.config.headers?.["User-Agent"] || "antigravity/1.104.0 darwin/arm64", "User-Agent": this.config.headers?.["User-Agent"] || "antigravity/1.104.0 darwin/arm64",
"X-9Router-Source": "9router", [INTERNAL_REQUEST_HEADER.name]: INTERNAL_REQUEST_HEADER.value,
...ANTIGRAVITY_HEADERS,
...(sessionId && { "X-Machine-Session-Id": sessionId }),
...(stream && { "Accept": "text/event-stream" }) ...(stream && { "Accept": "text/event-stream" })
}; };
} }
@@ -51,7 +54,7 @@ export class AntigravityExecutor extends BaseExecutor {
const transformedRequest = { const transformedRequest = {
...body.request, ...body.request,
...(contents && { contents }), ...(contents && { contents }),
sessionId: body.request?.sessionId || this.generateSessionId(), sessionId: body.request?.sessionId || deriveSessionId(credentials?.email || credentials?.connectionId),
safetySettings: undefined, safetySettings: undefined,
toolConfig: body.request?.tools?.length > 0 toolConfig: body.request?.tools?.length > 0
? { functionCallingConfig: { mode: "VALIDATED" } } ? { functionCallingConfig: { mode: "VALIDATED" } }
@@ -108,7 +111,7 @@ export class AntigravityExecutor extends BaseExecutor {
} }
generateSessionId() { generateSessionId() {
return `-${Math.floor(Math.random() * 9_000_000_000_000_000_000)}`; return crypto.randomUUID() + Date.now().toString();
} }
parseRetryHeaders(headers) { parseRetryHeaders(headers) {
@@ -167,8 +170,9 @@ export class AntigravityExecutor extends BaseExecutor {
for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) { for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
const url = this.buildUrl(model, stream, urlIndex); const url = this.buildUrl(model, stream, urlIndex);
const headers = this.buildHeaders(credentials, stream);
const transformedBody = this.transformRequest(model, body, stream, credentials); const transformedBody = this.transformRequest(model, body, stream, credentials);
const sessionId = transformedBody.request?.sessionId;
const headers = this.buildHeaders(credentials, stream, sessionId);
// Initialize retry counter for this URL // Initialize retry counter for this URL
if (!retryAttemptsByUrl[urlIndex]) { if (!retryAttemptsByUrl[urlIndex]) {
@@ -200,7 +204,7 @@ export class AntigravityExecutor extends BaseExecutor {
} }
if (retryMs && retryMs <= MAX_RETRY_AFTER_MS) { if (retryMs && retryMs <= MAX_RETRY_AFTER_MS) {
log?.debug?.("RETRY", `${response.status} with Retry-After: ${Math.ceil(retryMs/1000)}s, waiting...`); log?.debug?.("RETRY", `${response.status} with Retry-After: ${Math.ceil(retryMs / 1000)}s, waiting...`);
await new Promise(resolve => setTimeout(resolve, retryMs)); await new Promise(resolve => setTimeout(resolve, retryMs));
urlIndex--; urlIndex--;
continue; continue;
@@ -211,13 +215,13 @@ export class AntigravityExecutor extends BaseExecutor {
retryAttemptsByUrl[urlIndex]++; retryAttemptsByUrl[urlIndex]++;
// Exponential backoff: 2s, 4s, 8s... // Exponential backoff: 2s, 4s, 8s...
const backoffMs = Math.min(1000 * (2 ** retryAttemptsByUrl[urlIndex]), MAX_RETRY_AFTER_MS); const backoffMs = Math.min(1000 * (2 ** retryAttemptsByUrl[urlIndex]), MAX_RETRY_AFTER_MS);
log?.debug?.("RETRY", `429 auto retry ${retryAttemptsByUrl[urlIndex]}/${MAX_AUTO_RETRIES} after ${backoffMs/1000}s`); log?.debug?.("RETRY", `429 auto retry ${retryAttemptsByUrl[urlIndex]}/${MAX_AUTO_RETRIES} after ${backoffMs / 1000}s`);
await new Promise(resolve => setTimeout(resolve, backoffMs)); await new Promise(resolve => setTimeout(resolve, backoffMs));
urlIndex--; urlIndex--;
continue; continue;
} }
log?.debug?.("RETRY", `${response.status}, Retry-After ${retryMs ? `too long (${Math.ceil(retryMs/1000)}s)` : 'missing'}, trying fallback`); log?.debug?.("RETRY", `${response.status}, Retry-After ${retryMs ? `too long (${Math.ceil(retryMs / 1000)}s)` : 'missing'}, trying fallback`);
lastStatus = response.status; lastStatus = response.status;
if (urlIndex + 1 < fallbackCount) { if (urlIndex + 1 < fallbackCount) {

View File

@@ -2,6 +2,8 @@
* Usage Fetcher - Get usage data from provider APIs * Usage Fetcher - Get usage data from provider APIs
*/ */
import { CLIENT_METADATA, getPlatformUserAgent } from "../config/constants.js";
// GitHub API config // GitHub API config
const GITHUB_CONFIG = { const GITHUB_CONFIG = {
apiVersion: "2022-11-28", apiVersion: "2022-11-28",
@@ -15,7 +17,7 @@ const ANTIGRAVITY_CONFIG = {
tokenUrl: "https://oauth2.googleapis.com/token", tokenUrl: "https://oauth2.googleapis.com/token",
clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com", clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
clientSecret: "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", clientSecret: "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",
userAgent: "antigravity/1.11.3 Darwin/arm64", userAgent: getPlatformUserAgent(),
}; };
// Codex (OpenAI) API config // Codex (OpenAI) API config
@@ -220,7 +222,11 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
"User-Agent": ANTIGRAVITY_CONFIG.userAgent, "User-Agent": ANTIGRAVITY_CONFIG.userAgent,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(projectId ? { project: projectId } : {}), body: JSON.stringify({
...(projectId ? { project: projectId } : {}),
metadata: CLIENT_METADATA,
mode: 1
}),
}); });
if (response.status === 403) { if (response.status === 403) {
@@ -317,7 +323,7 @@ async function getAntigravitySubscriptionInfo(accessToken) {
"User-Agent": ANTIGRAVITY_CONFIG.userAgent, "User-Agent": ANTIGRAVITY_CONFIG.userAgent,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ metadata: { ideType: "ANTIGRAVITY" } }), body: JSON.stringify({ metadata: CLIENT_METADATA, mode: 1 }),
}); });
if (!response.ok) return null; if (!response.ok) return null;

View File

@@ -85,9 +85,9 @@ export function generateRequestId() {
return `agent-${crypto.randomUUID()}`; return `agent-${crypto.randomUUID()}`;
} }
// Generate session ID // Generate session ID (binary-compatible format: UUID + timestamp)
export function generateSessionId() { export function generateSessionId() {
return `-${Math.floor(Math.random() * 9000000000000000000)}`; return crypto.randomUUID() + Date.now().toString();
} }
// Generate project ID // Generate project ID

View File

@@ -18,6 +18,7 @@ import {
generateProjectId, generateProjectId,
cleanJSONSchemaForAntigravity cleanJSONSchemaForAntigravity
} from "../helpers/geminiHelper.js"; } from "../helpers/geminiHelper.js";
import { deriveSessionId } from "../../utils/sessionManager.js";
// Core: Convert OpenAI request to Gemini format (base for all variants) // Core: Convert OpenAI request to Gemini format (base for all variants)
function openaiToGeminiBase(model, body, stream) { function openaiToGeminiBase(model, body, stream) {
@@ -259,7 +260,7 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
userAgent: isAntigravity ? "antigravity" : "gemini-cli", userAgent: isAntigravity ? "antigravity" : "gemini-cli",
requestId: isAntigravity ? `agent-${generateUUID()}` : generateRequestId(), requestId: isAntigravity ? `agent-${generateUUID()}` : generateRequestId(),
request: { request: {
sessionId: generateSessionId(), sessionId: isAntigravity ? deriveSessionId(credentials?.email || credentials?.connectionId) : generateSessionId(),
contents: geminiCLI.contents, contents: geminiCLI.contents,
systemInstruction: geminiCLI.systemInstruction, systemInstruction: geminiCLI.systemInstruction,
generationConfig: geminiCLI.generationConfig, generationConfig: geminiCLI.generationConfig,
@@ -272,11 +273,16 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
envelope.requestType = "agent"; envelope.requestType = "agent";
// Inject required default system prompt for Antigravity // Inject required default system prompt for Antigravity
const defaultPart = { text: ANTIGRAVITY_DEFAULT_SYSTEM }; // Inject required default system prompt for Antigravity (double injection)
const systemParts = [
{ text: ANTIGRAVITY_DEFAULT_SYSTEM },
{ text: `Please ignore the following [ignore]${ANTIGRAVITY_DEFAULT_SYSTEM}[/ignore]` }
];
if (envelope.request.systemInstruction?.parts) { if (envelope.request.systemInstruction?.parts) {
envelope.request.systemInstruction.parts.unshift(defaultPart); envelope.request.systemInstruction.parts.unshift(...systemParts);
} else { } else {
envelope.request.systemInstruction = { role: "user", parts: [defaultPart] }; envelope.request.systemInstruction = { role: "user", parts: systemParts };
} }
// Add toolConfig for Antigravity // Add toolConfig for Antigravity
@@ -304,7 +310,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
requestId: `agent-${generateUUID()}`, requestId: `agent-${generateUUID()}`,
requestType: "agent", requestType: "agent",
request: { request: {
sessionId: generateSessionId(), sessionId: deriveSessionId(credentials?.email || credentials?.connectionId),
contents: [], contents: [],
generationConfig: { generationConfig: {
temperature: claudeRequest.temperature || 1, temperature: claudeRequest.temperature || 1,
@@ -378,9 +384,11 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
} }
} }
// Add system instruction (Antigravity default) // Add system instruction (Antigravity default - double injection)
const defaultPart = { text: ANTIGRAVITY_DEFAULT_SYSTEM }; const systemParts = [
const systemParts = [defaultPart]; { text: ANTIGRAVITY_DEFAULT_SYSTEM },
{ text: `Please ignore the following [ignore]${ANTIGRAVITY_DEFAULT_SYSTEM}[/ignore]` }
];
if (claudeRequest.system) { if (claudeRequest.system) {
if (Array.isArray(claudeRequest.system)) { if (Array.isArray(claudeRequest.system)) {

View File

@@ -0,0 +1,66 @@
/**
* Session Manager for Antigravity Cloud Code
*
* Handles session ID generation and caching for prompt caching continuity.
* Mimics the Antigravity binary behavior: generates a session ID at startup
* and keeps it for the process lifetime, scoped per account/connection.
*
* Reference: antigravity-claude-proxy/src/cloudcode/session-manager.js
*/
import crypto from "crypto";
// Runtime storage for session IDs (per connection/account)
// Key: connectionId (email or identifier), Value: sessionId
const runtimeSessionStore = new Map();
/**
* Get or create a session ID for the given connection.
*
* The binary generates a session ID once at startup: `rs() + Date.now()`.
* Since 9router is long-running, we simulate this "per-launch" behavior by
* storing a generated ID in memory for each connection.
*
* - If 9router restarts, the ID changes (matching binary restart behavior).
* - Within a running instance, the ID is stable for that connection.
* - This enables prompt caching while using the EXACT random logic of the binary.
*
* @param {string} connectionId - The connection identifier (email or unique ID)
* @returns {string} A stable session ID string matching binary format
*/
export function deriveSessionId(connectionId) {
if (!connectionId) {
// Fallback for requests without a connection identifier
return generateBinaryStyleId();
}
// Check if we already have a session ID for this connection in this process run
if (runtimeSessionStore.has(connectionId)) {
return runtimeSessionStore.get(connectionId);
}
// Generate a new ID using the binary's exact logic
const newSessionId = generateBinaryStyleId();
// Store it for future requests from this connection
runtimeSessionStore.set(connectionId, newSessionId);
return newSessionId;
}
/**
* Generate a Session ID using the binary's exact logic.
* Format: `rs() + Date.now()` where `rs()` is randomUUID
*
* @returns {string} A session ID in binary format
*/
export function generateBinaryStyleId() {
return crypto.randomUUID() + Date.now().toString();
}
/**
* Clears all session IDs (e.g. useful for testing or explicit reset)
*/
export function clearSessionStore() {
runtimeSessionStore.clear();
}

View File

@@ -1,6 +1,20 @@
/** /**
* OAuth Configuration Constants * OAuth Configuration Constants
*/ */
import { platform, arch } from "os";
/**
* Get the platform enum value based on the current OS.
* Matches Antigravity binary's ClientMetadata.Platform enum.
*/
function getOAuthPlatformEnum() {
const os = platform();
const architecture = arch();
if (os === "darwin") return architecture === "arm64" ? 2 : 1;
if (os === "linux") return architecture === "arm64" ? 4 : 3;
if (os === "win32") return 5;
return 0;
}
// Claude OAuth Configuration (Authorization Code Flow with PKCE) // Claude OAuth Configuration (Authorization Code Flow with PKCE)
export const CLAUDE_CONFIG = { export const CLAUDE_CONFIG = {
@@ -83,9 +97,17 @@ export const ANTIGRAVITY_CONFIG = {
onboardUserEndpoint: "https://cloudcode-pa.googleapis.com/v1internal:onboardUser", onboardUserEndpoint: "https://cloudcode-pa.googleapis.com/v1internal:onboardUser",
loadCodeAssistUserAgent: "google-api-nodejs-client/9.15.1", loadCodeAssistUserAgent: "google-api-nodejs-client/9.15.1",
loadCodeAssistApiClient: "google-cloud-sdk vscode_cloudshelleditor/0.1", loadCodeAssistApiClient: "google-cloud-sdk vscode_cloudshelleditor/0.1",
loadCodeAssistClientMetadata: `{"ideType":"IDE_UNSPECIFIED","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}`, loadCodeAssistClientMetadata: JSON.stringify({ ideType: 9, platform: getOAuthPlatformEnum(), pluginType: 2 }),
}; };
/**
* Get client metadata using numeric enum values for API calls.
* @returns {{ ideType: number, platform: number, pluginType: number }}
*/
export function getOAuthClientMetadata() {
return { ideType: 9, platform: getOAuthPlatformEnum(), pluginType: 2 };
}
// OpenAI OAuth Configuration (Authorization Code Flow with PKCE) // OpenAI OAuth Configuration (Authorization Code Flow with PKCE)
export const OPENAI_CONFIG = { export const OPENAI_CONFIG = {
clientId: "app_EMoamEEZ73f0CkXaXp7hrann", clientId: "app_EMoamEEZ73f0CkXaXp7hrann",

View File

@@ -14,6 +14,7 @@ import {
GITHUB_CONFIG, GITHUB_CONFIG,
KIRO_CONFIG, KIRO_CONFIG,
CURSOR_CONFIG, CURSOR_CONFIG,
getOAuthClientMetadata,
} from "./constants/oauth"; } from "./constants/oauth";
// Provider configurations // Provider configurations
@@ -184,7 +185,8 @@ const PROVIDERS = {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
metadata: { ideType: "IDE_UNSPECIFIED", platform: "PLATFORM_UNSPECIFIED", pluginType: "GEMINI" }, metadata: getOAuthClientMetadata(),
mode: 1,
}), }),
} }
); );
@@ -254,7 +256,7 @@ const PROVIDERS = {
"X-Goog-Api-Client": ANTIGRAVITY_CONFIG.loadCodeAssistApiClient, "X-Goog-Api-Client": ANTIGRAVITY_CONFIG.loadCodeAssistApiClient,
"Client-Metadata": ANTIGRAVITY_CONFIG.loadCodeAssistClientMetadata, "Client-Metadata": ANTIGRAVITY_CONFIG.loadCodeAssistClientMetadata,
}; };
const metadata = { ideType: "IDE_UNSPECIFIED", platform: "PLATFORM_UNSPECIFIED", pluginType: "GEMINI" }; const metadata = getOAuthClientMetadata();
// Fetch user info // Fetch user info
const userInfoRes = await fetch(`${ANTIGRAVITY_CONFIG.userInfoUrl}?alt=json`, { const userInfoRes = await fetch(`${ANTIGRAVITY_CONFIG.userInfoUrl}?alt=json`, {
@@ -269,7 +271,7 @@ const PROVIDERS = {
const loadRes = await fetch(ANTIGRAVITY_CONFIG.loadCodeAssistEndpoint, { const loadRes = await fetch(ANTIGRAVITY_CONFIG.loadCodeAssistEndpoint, {
method: "POST", method: "POST",
headers, headers,
body: JSON.stringify({ metadata }), body: JSON.stringify({ metadata, mode: 1 }),
}); });
if (loadRes.ok) { if (loadRes.ok) {
const data = await loadRes.json(); const data = await loadRes.json();
@@ -295,7 +297,7 @@ const PROVIDERS = {
const onboardRes = await fetch(ANTIGRAVITY_CONFIG.onboardUserEndpoint, { const onboardRes = await fetch(ANTIGRAVITY_CONFIG.onboardUserEndpoint, {
method: "POST", method: "POST",
headers, headers,
body: JSON.stringify({ tierId, metadata, cloudaicompanionProject: projectId }), body: JSON.stringify({ tierId, metadata, cloudaicompanionProject: projectId, mode: 1 }),
}); });
if (onboardRes.ok) { if (onboardRes.ok) {
const result = await onboardRes.json(); const result = await onboardRes.json();

View File

@@ -1,4 +1,5 @@
import crypto from "crypto"; import crypto from "crypto";
import { platform, arch } from "os";
import open from "open"; import open from "open";
import { ANTIGRAVITY_CONFIG } from "../constants/oauth.js"; import { ANTIGRAVITY_CONFIG } from "../constants/oauth.js";
import { getServerCredentials } from "../config/index.js"; import { getServerCredentials } from "../config/index.js";
@@ -92,12 +93,20 @@ export class AntigravityService {
/** /**
* Get metadata object for API calls * Get metadata object for API calls
* Uses numeric enum values matching Antigravity binary specifications
*/ */
getMetadata() { getMetadata() {
const os = platform();
const architecture = arch();
let platformEnum = 0; // UNSPECIFIED
if (os === "darwin") platformEnum = architecture === "arm64" ? 2 : 1;
else if (os === "linux") platformEnum = architecture === "arm64" ? 4 : 3;
else if (os === "win32") platformEnum = 5;
return { return {
ideType: "IDE_UNSPECIFIED", ideType: 9, // ANTIGRAVITY
platform: "PLATFORM_UNSPECIFIED", platform: platformEnum,
pluginType: "GEMINI", pluginType: 2, // GEMINI
}; };
} }
@@ -108,7 +117,7 @@ export class AntigravityService {
const response = await fetch(this.config.loadCodeAssistEndpoint, { const response = await fetch(this.config.loadCodeAssistEndpoint, {
method: "POST", method: "POST",
headers: this.getApiHeaders(accessToken), headers: this.getApiHeaders(accessToken),
body: JSON.stringify({ metadata: this.getMetadata() }), body: JSON.stringify({ metadata: this.getMetadata(), mode: 1 }),
}); });
if (!response.ok) { if (!response.ok) {
@@ -149,6 +158,7 @@ export class AntigravityService {
tierId, tierId,
metadata: this.getMetadata(), metadata: this.getMetadata(),
cloudaicompanionProject: projectId, cloudaicompanionProject: projectId,
mode: 1,
}), }),
}); });

View File

@@ -1,6 +1,6 @@
import crypto from "crypto"; import crypto from "crypto";
import open from "open"; import open from "open";
import { GEMINI_CONFIG } from "../constants/oauth.js"; import { GEMINI_CONFIG, getOAuthClientMetadata } from "../constants/oauth.js";
import { getServerCredentials } from "../config/index.js"; import { getServerCredentials } from "../config/index.js";
import { startLocalServer } from "../utils/server.js"; import { startLocalServer } from "../utils/server.js";
import { spinner as createSpinner } from "../utils/ui.js"; import { spinner as createSpinner } from "../utils/ui.js";
@@ -71,18 +71,11 @@ export class GeminiCLIService {
"Content-Type": "application/json", "Content-Type": "application/json",
"User-Agent": "google-api-nodejs-client/9.15.1", "User-Agent": "google-api-nodejs-client/9.15.1",
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1", "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
"Client-Metadata": JSON.stringify({ "Client-Metadata": JSON.stringify(getOAuthClientMetadata())
ideType: "IDE_UNSPECIFIED",
platform: "PLATFORM_UNSPECIFIED",
pluginType: "GEMINI"
})
}, },
body: JSON.stringify({ body: JSON.stringify({
metadata: { metadata: getOAuthClientMetadata(),
ideType: "IDE_UNSPECIFIED", mode: 1
platform: "PLATFORM_UNSPECIFIED",
pluginType: "GEMINI"
}
}) })
} }
); );

View File

@@ -6,6 +6,7 @@ const { promisify } = require("util");
const os = require("os"); const os = require("os");
// Configuration // Configuration
const INTERNAL_REQUEST_HEADER = { name: "x-request-source", value: "local" };
const TARGET_HOST = "daily-cloudcode-pa.googleapis.com"; const TARGET_HOST = "daily-cloudcode-pa.googleapis.com";
const LOCAL_PORT = 443; const LOCAL_PORT = 443;
const ROUTER_URL = "http://localhost:20128/v1/chat/completions"; const ROUTER_URL = "http://localhost:20128/v1/chat/completions";
@@ -174,7 +175,7 @@ const server = https.createServer(sslOptions, async (req, res) => {
if (bodyBuffer.length > 0) saveRequestLog(req.url, bodyBuffer); if (bodyBuffer.length > 0) saveRequestLog(req.url, bodyBuffer);
// Anti-loop: requests from 9Router bypass interception // Anti-loop: requests from 9Router bypass interception
if (req.headers["x-9router-source"] === "9router") { if (req.headers[INTERNAL_REQUEST_HEADER.name] === INTERNAL_REQUEST_HEADER.value) {
return passthrough(req, res, bodyBuffer); return passthrough(req, res, bodyBuffer);
} }