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

@@ -2,6 +2,8 @@
* Usage Fetcher - Get usage data from provider APIs
*/
import { CLIENT_METADATA, getPlatformUserAgent } from "../config/constants.js";
// GitHub API config
const GITHUB_CONFIG = {
apiVersion: "2022-11-28",
@@ -15,7 +17,7 @@ const ANTIGRAVITY_CONFIG = {
tokenUrl: "https://oauth2.googleapis.com/token",
clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
clientSecret: "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",
userAgent: "antigravity/1.11.3 Darwin/arm64",
userAgent: getPlatformUserAgent(),
};
// Codex (OpenAI) API config
@@ -65,23 +67,23 @@ export async function getUsageForProvider(connection) {
*/
function parseResetTime(resetValue) {
if (!resetValue) return null;
try {
// If it's already a Date object
if (resetValue instanceof Date) {
return resetValue.toISOString();
}
// If it's a number (Unix timestamp in milliseconds)
if (typeof resetValue === 'number') {
return new Date(resetValue).toISOString();
}
// If it's a string (ISO date or any parseable date string)
if (typeof resetValue === 'string') {
return new Date(resetValue).toISOString();
}
return null;
} catch (error) {
console.warn(`Failed to parse reset time: ${resetValue}`, error);
@@ -98,7 +100,7 @@ async function getGitHubUsage(accessToken, providerSpecificData) {
if (!accessToken) {
throw new Error("No GitHub access token available. Please re-authorize the connection.");
}
// copilot_internal/user API requires GitHub OAuth token, not copilotToken
const response = await fetch("https://api.github.com/copilot_internal/user", {
headers: {
@@ -123,7 +125,7 @@ async function getGitHubUsage(accessToken, providerSpecificData) {
// Paid plan format
const snapshots = data.quota_snapshots;
const resetAt = parseResetTime(data.quota_reset_date);
return {
plan: data.copilot_plan,
resetDate: data.quota_reset_date,
@@ -138,7 +140,7 @@ async function getGitHubUsage(accessToken, providerSpecificData) {
const monthlyQuotas = data.monthly_quotas || {};
const usedQuotas = data.limited_user_quotas || {};
const resetAt = parseResetTime(data.limited_user_reset_date);
return {
plan: data.copilot_plan || data.access_type_sku,
resetDate: data.limited_user_reset_date,
@@ -167,7 +169,7 @@ async function getGitHubUsage(accessToken, providerSpecificData) {
function formatGitHubQuotaSnapshot(quota) {
if (!quota) return { used: 0, total: 0, unlimited: true };
return {
used: quota.entitlement - quota.remaining,
total: quota.entitlement,
@@ -211,7 +213,7 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
try {
// First get project ID from subscription info
const projectId = await getAntigravityProjectId(accessToken);
// Fetch quota data
const response = await fetch(ANTIGRAVITY_CONFIG.quotaApiUrl, {
method: "POST",
@@ -220,7 +222,11 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
"User-Agent": ANTIGRAVITY_CONFIG.userAgent,
"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) {
@@ -233,7 +239,7 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
const data = await response.json();
const quotas = {};
// Parse model quotas (inspired by vscode-antigravity-cockpit)
if (data.models) {
// Filter only recommended/important models (must match PROVIDER_MODELS ag ids)
@@ -248,26 +254,26 @@ async function getAntigravityUsage(accessToken, providerSpecificData) {
'gemini-3-flash',
'gemini-2.5-flash',
];
for (const [modelKey, info] of Object.entries(data.models)) {
// Skip models without quota info
if (!info.quotaInfo) {
continue;
}
// Skip internal models and non-important models
if (info.isInternal || !importantModels.includes(modelKey)) {
continue;
}
const remainingFraction = info.quotaInfo.remainingFraction || 0;
const remainingPercentage = remainingFraction * 100;
// Convert percentage to used/total for UI compatibility
const total = 1000; // Normalized base
const remaining = Math.round(total * remainingFraction);
const used = total - remaining;
// Use modelKey as key (matches PROVIDER_MODELS id)
quotas[modelKey] = {
used,
@@ -317,7 +323,7 @@ async function getAntigravitySubscriptionInfo(accessToken) {
"User-Agent": ANTIGRAVITY_CONFIG.userAgent,
"Content-Type": "application/json",
},
body: JSON.stringify({ metadata: { ideType: "ANTIGRAVITY" } }),
body: JSON.stringify({ metadata: CLIENT_METADATA, mode: 1 }),
});
if (!response.ok) return null;
@@ -345,7 +351,7 @@ async function getClaudeUsage(accessToken) {
if (settingsResponse.ok) {
const settings = await settingsResponse.json();
// Try usage endpoint if we have org info
if (settings.organization_id) {
const usageResponse = await fetch(
@@ -402,7 +408,7 @@ async function getCodexUsage(accessToken) {
}
const data = await response.json();
// Parse rate limit info
const rateLimit = data.rate_limit || {};
const primaryWindow = rateLimit.primary_window || {};
@@ -475,7 +481,7 @@ async function getKiroUsage(accessToken, providerSpecificData) {
// Parse usage data from usageBreakdownList
const usageList = data.usageBreakdownList || [];
const quotaInfo = {};
// Parse reset time - supports multiple formats (nextDateReset, resetDate, etc.)
const resetAt = parseResetTime(data.nextDateReset || data.resetDate);
@@ -483,7 +489,7 @@ async function getKiroUsage(accessToken, providerSpecificData) {
const resourceType = breakdown.resourceType?.toLowerCase() || "unknown";
const used = breakdown.currentUsageWithPrecision || 0;
const total = breakdown.usageLimitWithPrecision || 0;
quotaInfo[resourceType] = {
used,
total,
@@ -496,7 +502,7 @@ async function getKiroUsage(accessToken, providerSpecificData) {
if (breakdown.freeTrialInfo) {
const freeUsed = breakdown.freeTrialInfo.currentUsageWithPrecision || 0;
const freeTotal = breakdown.freeTrialInfo.usageLimitWithPrecision || 0;
quotaInfo[`${resourceType}_freetrial`] = {
used: freeUsed,
total: freeTotal,