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:
@@ -1,6 +1,20 @@
|
||||
/**
|
||||
* 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)
|
||||
export const CLAUDE_CONFIG = {
|
||||
@@ -83,9 +97,17 @@ export const ANTIGRAVITY_CONFIG = {
|
||||
onboardUserEndpoint: "https://cloudcode-pa.googleapis.com/v1internal:onboardUser",
|
||||
loadCodeAssistUserAgent: "google-api-nodejs-client/9.15.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)
|
||||
export const OPENAI_CONFIG = {
|
||||
clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
GITHUB_CONFIG,
|
||||
KIRO_CONFIG,
|
||||
CURSOR_CONFIG,
|
||||
getOAuthClientMetadata,
|
||||
} from "./constants/oauth";
|
||||
|
||||
// Provider configurations
|
||||
@@ -184,7 +185,8 @@ const PROVIDERS = {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
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,
|
||||
"Client-Metadata": ANTIGRAVITY_CONFIG.loadCodeAssistClientMetadata,
|
||||
};
|
||||
const metadata = { ideType: "IDE_UNSPECIFIED", platform: "PLATFORM_UNSPECIFIED", pluginType: "GEMINI" };
|
||||
const metadata = getOAuthClientMetadata();
|
||||
|
||||
// Fetch user info
|
||||
const userInfoRes = await fetch(`${ANTIGRAVITY_CONFIG.userInfoUrl}?alt=json`, {
|
||||
@@ -269,7 +271,7 @@ const PROVIDERS = {
|
||||
const loadRes = await fetch(ANTIGRAVITY_CONFIG.loadCodeAssistEndpoint, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({ metadata }),
|
||||
body: JSON.stringify({ metadata, mode: 1 }),
|
||||
});
|
||||
if (loadRes.ok) {
|
||||
const data = await loadRes.json();
|
||||
@@ -295,7 +297,7 @@ const PROVIDERS = {
|
||||
const onboardRes = await fetch(ANTIGRAVITY_CONFIG.onboardUserEndpoint, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({ tierId, metadata, cloudaicompanionProject: projectId }),
|
||||
body: JSON.stringify({ tierId, metadata, cloudaicompanionProject: projectId, mode: 1 }),
|
||||
});
|
||||
if (onboardRes.ok) {
|
||||
const result = await onboardRes.json();
|
||||
@@ -727,9 +729,9 @@ export function generateAuthData(providerName, redirectUri) {
|
||||
*/
|
||||
export async function exchangeTokens(providerName, code, redirectUri, codeVerifier, state) {
|
||||
const provider = getProvider(providerName);
|
||||
|
||||
|
||||
const tokens = await provider.exchangeToken(provider.config, code, redirectUri, codeVerifier, state);
|
||||
|
||||
|
||||
let extra = null;
|
||||
if (provider.postExchange) {
|
||||
extra = await provider.postExchange(tokens);
|
||||
@@ -761,9 +763,9 @@ export async function pollForToken(providerName, deviceCode, codeVerifier, extra
|
||||
if (provider.flowType !== "device_code") {
|
||||
throw new Error(`Provider ${providerName} does not support device code flow`);
|
||||
}
|
||||
|
||||
|
||||
const result = await provider.pollToken(provider.config, deviceCode, codeVerifier, extraData);
|
||||
|
||||
|
||||
if (result.ok) {
|
||||
// For device code flows, success is only when we have an access token
|
||||
if (result.data.access_token) {
|
||||
@@ -777,23 +779,23 @@ export async function pollForToken(providerName, deviceCode, codeVerifier, extra
|
||||
// Check if it's still pending authorization
|
||||
if (result.data.error === 'authorization_pending' || result.data.error === 'slow_down') {
|
||||
// This is not a failure, just still waiting
|
||||
return {
|
||||
success: false,
|
||||
error: result.data.error,
|
||||
return {
|
||||
success: false,
|
||||
error: result.data.error,
|
||||
errorDescription: result.data.error_description || result.data.message,
|
||||
pending: result.data.error === 'authorization_pending'
|
||||
};
|
||||
} else {
|
||||
// Actual error
|
||||
return {
|
||||
success: false,
|
||||
error: result.data.error || 'no_access_token',
|
||||
errorDescription: result.data.error_description || result.data.message || 'No access token received'
|
||||
return {
|
||||
success: false,
|
||||
error: result.data.error || 'no_access_token',
|
||||
errorDescription: result.data.error_description || result.data.message || 'No access token received'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return { success: false, error: result.data.error, errorDescription: result.data.error_description };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import crypto from "crypto";
|
||||
import { platform, arch } from "os";
|
||||
import open from "open";
|
||||
import { ANTIGRAVITY_CONFIG } from "../constants/oauth.js";
|
||||
import { getServerCredentials } from "../config/index.js";
|
||||
@@ -92,12 +93,20 @@ export class AntigravityService {
|
||||
|
||||
/**
|
||||
* Get metadata object for API calls
|
||||
* Uses numeric enum values matching Antigravity binary specifications
|
||||
*/
|
||||
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 {
|
||||
ideType: "IDE_UNSPECIFIED",
|
||||
platform: "PLATFORM_UNSPECIFIED",
|
||||
pluginType: "GEMINI",
|
||||
ideType: 9, // ANTIGRAVITY
|
||||
platform: platformEnum,
|
||||
pluginType: 2, // GEMINI
|
||||
};
|
||||
}
|
||||
|
||||
@@ -108,7 +117,7 @@ export class AntigravityService {
|
||||
const response = await fetch(this.config.loadCodeAssistEndpoint, {
|
||||
method: "POST",
|
||||
headers: this.getApiHeaders(accessToken),
|
||||
body: JSON.stringify({ metadata: this.getMetadata() }),
|
||||
body: JSON.stringify({ metadata: this.getMetadata(), mode: 1 }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -117,7 +126,7 @@ export class AntigravityService {
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
|
||||
// Extract project ID
|
||||
let projectId = data.cloudaicompanionProject;
|
||||
if (typeof projectId === 'object' && projectId !== null && projectId.id) {
|
||||
@@ -149,6 +158,7 @@ export class AntigravityService {
|
||||
tierId,
|
||||
metadata: this.getMetadata(),
|
||||
cloudaicompanionProject: projectId,
|
||||
mode: 1,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -166,7 +176,7 @@ export class AntigravityService {
|
||||
async completeOnboarding(accessToken, projectId, tierId, maxRetries = 10) {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
const result = await this.onboardUser(accessToken, projectId, tierId);
|
||||
|
||||
|
||||
if (result.done === true) {
|
||||
// Extract final project ID from response
|
||||
let finalProjectId = projectId;
|
||||
@@ -301,7 +311,7 @@ export class AntigravityService {
|
||||
|
||||
// Load Code Assist to get project ID and tier
|
||||
const { projectId, tierId } = await this.loadCodeAssist(tokens.access_token);
|
||||
|
||||
|
||||
if (!projectId) {
|
||||
throw new Error("No Google Cloud Project found. Please ensure you have a GCP project with Gemini Code Assist enabled.");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import crypto from "crypto";
|
||||
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 { startLocalServer } from "../utils/server.js";
|
||||
import { spinner as createSpinner } from "../utils/ui.js";
|
||||
@@ -71,18 +71,11 @@ export class GeminiCLIService {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "google-api-nodejs-client/9.15.1",
|
||||
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
|
||||
"Client-Metadata": JSON.stringify({
|
||||
ideType: "IDE_UNSPECIFIED",
|
||||
platform: "PLATFORM_UNSPECIFIED",
|
||||
pluginType: "GEMINI"
|
||||
})
|
||||
"Client-Metadata": JSON.stringify(getOAuthClientMetadata())
|
||||
},
|
||||
body: JSON.stringify({
|
||||
metadata: {
|
||||
ideType: "IDE_UNSPECIFIED",
|
||||
platform: "PLATFORM_UNSPECIFIED",
|
||||
pluginType: "GEMINI"
|
||||
}
|
||||
metadata: getOAuthClientMetadata(),
|
||||
mode: 1
|
||||
})
|
||||
}
|
||||
);
|
||||
@@ -93,7 +86,7 @@ export class GeminiCLIService {
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
|
||||
// Extract project ID
|
||||
let projectId = "";
|
||||
if (typeof data.cloudaicompanionProject === "string") {
|
||||
|
||||
@@ -6,6 +6,7 @@ const { promisify } = require("util");
|
||||
const os = require("os");
|
||||
|
||||
// Configuration
|
||||
const INTERNAL_REQUEST_HEADER = { name: "x-request-source", value: "local" };
|
||||
const TARGET_HOST = "daily-cloudcode-pa.googleapis.com";
|
||||
const LOCAL_PORT = 443;
|
||||
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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user