/** * Constants for Antigravity Cloud Code API integration * Based on: https://github.com/NoeFabris/opencode-antigravity-auth */ import { homedir, platform, arch } from 'os'; import { join } from 'path'; import { config } from './config.js'; import { generateSmartUserAgent, getClientVersion } from './utils/version-detector.js'; /** * Get the Antigravity database path based on the current platform. * - macOS: ~/Library/Application Support/Antigravity/... * - Windows: ~/AppData/Roaming/Antigravity/... * - Linux/other: ~/.config/Antigravity/... * @returns {string} Full path to the Antigravity state database */ function getAntigravityDbPath() { const home = homedir(); switch (platform()) { case 'darwin': return join(home, 'Library/Application Support/Antigravity/User/globalStorage/state.vscdb'); case 'win32': return join(home, 'AppData/Roaming/Antigravity/User/globalStorage/state.vscdb'); default: // linux, freebsd, etc. return join(home, '.config/Antigravity/User/globalStorage/state.vscdb'); } } /** * Generate platform-specific User-Agent string. * @returns {string} User-Agent in format "antigravity/version os/arch" */ export function getPlatformUserAgent() { return generateSmartUserAgent(); } // IDE Type enum (numeric values as expected by Cloud Code API) // Reference: Antigravity binary analysis - google.internal.cloud.code.v1internal.ClientMetadata.IdeType 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 */ 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; } // 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, // 6 - identifies as Antigravity client platform: getPlatformEnum(), // Runtime platform detection pluginType: PLUGIN_TYPE.GEMINI // 2 }; // Cloud Code API endpoints (in fallback order) const ANTIGRAVITY_ENDPOINT_DAILY = 'https://daily-cloudcode-pa.googleapis.com'; const ANTIGRAVITY_ENDPOINT_PROD = 'https://cloudcode-pa.googleapis.com'; // Endpoint fallback order (daily → prod) export const ANTIGRAVITY_ENDPOINT_FALLBACKS = [ ANTIGRAVITY_ENDPOINT_DAILY, ANTIGRAVITY_ENDPOINT_PROD ]; // Required headers for Antigravity API requests // Headers for general Antigravity API requests // Strictly matches the generic 'u' method in main.js export const ANTIGRAVITY_HEADERS = { 'User-Agent': getPlatformUserAgent(), 'Content-Type': 'application/json', 'X-Client-Name': 'antigravity', 'X-Client-Version': getClientVersion(), 'x-goog-api-client': 'gl-node/18.18.2 fire/0.8.6 grpc/1.10.x' // Simulate Google Node.js client environment }; // Endpoint order for loadCodeAssist (prod first) // loadCodeAssist works better on prod for fresh/unprovisioned accounts export const LOAD_CODE_ASSIST_ENDPOINTS = [ ANTIGRAVITY_ENDPOINT_PROD, ANTIGRAVITY_ENDPOINT_DAILY ]; // Endpoint order for onboardUser (same as generateContent fallbacks) export const ONBOARD_USER_ENDPOINTS = ANTIGRAVITY_ENDPOINT_FALLBACKS; // Headers for loadCodeAssist API // Matches the minimal headers seen in the binary's u() method export const LOAD_CODE_ASSIST_HEADERS = ANTIGRAVITY_HEADERS; // Default project ID if none can be discovered export const DEFAULT_PROJECT_ID = 'rising-fact-p41fc'; // Configurable constants - values from config.json take precedence export const TOKEN_REFRESH_INTERVAL_MS = config?.tokenCacheTtlMs || (5 * 60 * 1000); // From config or 5 minutes export const REQUEST_BODY_LIMIT = config?.requestBodyLimit || '50mb'; export const ANTIGRAVITY_AUTH_PORT = 9092; export const DEFAULT_PORT = config?.port || 8080; // Multi-account configuration export const ACCOUNT_CONFIG_PATH = config?.accountConfigPath || join( homedir(), '.config/antigravity-proxy/accounts.json' ); // Usage history persistence path export const USAGE_HISTORY_PATH = join( homedir(), '.config/antigravity-proxy/usage-history.json' ); // Antigravity app database path (for legacy single-account token extraction) // Uses platform-specific path detection export const ANTIGRAVITY_DB_PATH = getAntigravityDbPath(); export const DEFAULT_COOLDOWN_MS = config?.defaultCooldownMs || (10 * 1000); // From config or 10 seconds export const MAX_RETRIES = config?.maxRetries || 5; // From config or 5 export const MAX_EMPTY_RESPONSE_RETRIES = 2; // Max retries for empty API responses (from upstream) export const MAX_ACCOUNTS = config?.maxAccounts || 10; // From config or 10 // Rate limit wait thresholds export const MAX_WAIT_BEFORE_ERROR_MS = config?.maxWaitBeforeErrorMs || 120000; // From config or 2 minutes // Retry deduplication - prevents thundering herd on concurrent rate limits export const RATE_LIMIT_DEDUP_WINDOW_MS = config?.rateLimitDedupWindowMs || 2000; // 2 seconds export const RATE_LIMIT_STATE_RESET_MS = config?.rateLimitStateResetMs || 120000; // 2 minutes - reset consecutive429 after inactivity export const FIRST_RETRY_DELAY_MS = config?.firstRetryDelayMs || 1000; // Quick 1s retry on first 429 export const SWITCH_ACCOUNT_DELAY_MS = config?.switchAccountDelayMs || 5000; // Delay before switching accounts // Consecutive failure tracking - extended cooldown after repeated failures export const MAX_CONSECUTIVE_FAILURES = config?.maxConsecutiveFailures || 3; export const EXTENDED_COOLDOWN_MS = config?.extendedCooldownMs || 60000; // 1 minute // Capacity exhaustion - progressive backoff tiers for model capacity issues export const CAPACITY_BACKOFF_TIERS_MS = config?.capacityBackoffTiersMs || [5000, 10000, 20000, 30000, 60000]; export const MAX_CAPACITY_RETRIES = config?.maxCapacityRetries || 5; // Smart backoff by error type export const BACKOFF_BY_ERROR_TYPE = { RATE_LIMIT_EXCEEDED: 30000, // 30 seconds MODEL_CAPACITY_EXHAUSTED: 15000, // 15 seconds SERVER_ERROR: 20000, // 20 seconds UNKNOWN: 60000 // 1 minute }; // Progressive backoff tiers for QUOTA_EXHAUSTED (60s, 5m, 30m, 2h) export const QUOTA_EXHAUSTED_BACKOFF_TIERS_MS = [60000, 300000, 1800000, 7200000]; // Minimum backoff floor to prevent "Available in 0s" loops (matches opencode-antigravity-auth) export const MIN_BACKOFF_MS = 2000; // Jitter range for capacity backoff (Thundering Herd Prevention) // Applied to MODEL_CAPACITY_EXHAUSTED to stagger client retries export const CAPACITY_JITTER_MAX_MS = 10000; // ±5s jitter range // Thinking model constants export const MIN_SIGNATURE_LENGTH = 50; // Minimum valid thinking signature length // Account selection strategies export const SELECTION_STRATEGIES = ['sticky', 'round-robin', 'hybrid']; export const DEFAULT_SELECTION_STRATEGY = 'hybrid'; // Strategy display labels export const STRATEGY_LABELS = { 'sticky': 'Sticky (Cache Optimized)', 'round-robin': 'Round Robin (Load Balanced)', 'hybrid': 'Hybrid (Smart Distribution)' }; // Gemini-specific limits export const GEMINI_MAX_OUTPUT_TOKENS = 16384; // Gemini signature handling // Sentinel value to skip thought signature validation when Claude Code strips the field // See: https://ai.google.dev/gemini-api/docs/thought-signatures export const GEMINI_SKIP_SIGNATURE = 'skip_thought_signature_validator'; // Cache TTL for Gemini thoughtSignatures (2 hours) export const GEMINI_SIGNATURE_CACHE_TTL_MS = 2 * 60 * 60 * 1000; // Cache TTL for model validation (5 minutes) export const MODEL_VALIDATION_CACHE_TTL_MS = 5 * 60 * 1000; /** * Get the model family from model name (dynamic detection, no hardcoded list). * @param {string} modelName - The model name from the request * @returns {'claude' | 'gemini' | 'unknown'} The model family */ export function getModelFamily(modelName) { const lower = (modelName || '').toLowerCase(); if (lower.includes('claude')) return 'claude'; if (lower.includes('gemini')) return 'gemini'; return 'unknown'; } /** * Check if a model supports thinking/reasoning output. * @param {string} modelName - The model name from the request * @returns {boolean} True if the model supports thinking blocks */ export function isThinkingModel(modelName) { const lower = (modelName || '').toLowerCase(); // Claude thinking models have "thinking" in the name if (lower.includes('claude') && lower.includes('thinking')) return true; // Gemini thinking models: explicit "thinking" in name, OR gemini version 3+ if (lower.includes('gemini')) { if (lower.includes('thinking')) return true; // Check for gemini-3 or higher (e.g., gemini-3, gemini-3.5, gemini-4, etc.) const versionMatch = lower.match(/gemini-(\d+)/); if (versionMatch && parseInt(versionMatch[1], 10) >= 3) return true; } return false; } // Google OAuth configuration (from opencode-antigravity-auth) // OAuth callback port - configurable via environment variable for Windows compatibility (issue #176) // Windows may reserve ports in range 49152-65535 for Hyper-V/WSL2/Docker, causing EACCES errors const OAUTH_CALLBACK_PORT = parseInt(process.env.OAUTH_CALLBACK_PORT || '51121', 10); const OAUTH_CALLBACK_FALLBACK_PORTS = [51122, 51123, 51124, 51125, 51126]; export const OAUTH_CONFIG = { clientId: '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com', clientSecret: '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', authUrl: 'https://accounts.google.com/o/oauth2/v2/auth', tokenUrl: 'https://oauth2.googleapis.com/token', userInfoUrl: 'https://www.googleapis.com/oauth2/v1/userinfo', callbackPort: OAUTH_CALLBACK_PORT, callbackFallbackPorts: OAUTH_CALLBACK_FALLBACK_PORTS, scopes: [ 'https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/cclog', 'https://www.googleapis.com/auth/experimentsandconfigs' ] }; export const OAUTH_REDIRECT_URI = `http://localhost:${OAUTH_CONFIG.callbackPort}/oauth-callback`; // Minimal Antigravity system instruction (from CLIProxyAPI) // Only includes the essential identity portion to reduce token usage and improve response quality // Reference: GitHub issue #76, CLIProxyAPI, gcli2api export const ANTIGRAVITY_SYSTEM_INSTRUCTION = `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**`; // Model fallback mapping - maps primary model to fallback when quota exhausted export const MODEL_FALLBACK_MAP = { 'gemini-3.1-pro-high': 'claude-opus-4-6-thinking', 'gemini-3.1-pro-low': 'claude-sonnet-4-6', 'gemini-3-flash': 'claude-sonnet-4-6', 'claude-opus-4-6-thinking': 'gemini-3.1-pro-high', 'claude-sonnet-4-6': 'gemini-3-flash' }; // Default test models for each family (used by test suite) export const TEST_MODELS = { claude: 'claude-sonnet-4-6', gemini: 'gemini-3.5-flash-low' }; // Default Claude CLI presets (used by WebUI settings) export const DEFAULT_PRESETS = [ { name: 'Claude Thinking', config: { ANTHROPIC_AUTH_TOKEN: 'test', ANTHROPIC_BASE_URL: 'http://localhost:8080', ANTHROPIC_MODEL: 'claude-opus-4-6-thinking', ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-6-thinking', ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-6', ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-sonnet-4-6', CLAUDE_CODE_SUBAGENT_MODEL: 'claude-sonnet-4-6', ENABLE_EXPERIMENTAL_MCP_CLI: 'true' } }, { name: 'Gemini 1M', config: { ANTHROPIC_AUTH_TOKEN: 'test', ANTHROPIC_BASE_URL: 'http://localhost:8080', ANTHROPIC_MODEL: 'gemini-3.1-pro-low', ANTHROPIC_DEFAULT_OPUS_MODEL: 'gemini-3.1-pro-low', ANTHROPIC_DEFAULT_SONNET_MODEL: 'gemini-3.5-flash-low', ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-3.5-flash-low', CLAUDE_CODE_SUBAGENT_MODEL: 'gemini-3.5-flash-low', ENABLE_EXPERIMENTAL_MCP_CLI: 'true' } } ]; /** * Built-in server configuration presets. * Each preset has builtIn: true and cannot be deleted by users. */ export const DEFAULT_SERVER_PRESETS = [ { name: 'Default (3-5 Accounts)', builtIn: true, descriptionKey: 'presetDefaultDesc', config: { maxRetries: 5, retryBaseMs: 1000, retryMaxMs: 30000, defaultCooldownMs: 10000, maxWaitBeforeErrorMs: 120000, maxAccounts: 10, globalQuotaThreshold: 0, rateLimitDedupWindowMs: 2000, maxConsecutiveFailures: 3, extendedCooldownMs: 60000, maxCapacityRetries: 5, switchAccountDelayMs: 5000, capacityBackoffTiersMs: [5000, 10000, 20000, 30000, 60000], accountSelection: { strategy: 'hybrid', healthScore: { initial: 70, successReward: 1, rateLimitPenalty: -10, failurePenalty: -20, recoveryPerHour: 10, minUsable: 50, maxScore: 100 }, tokenBucket: { maxTokens: 50, tokensPerMinute: 6, initialTokens: 50 }, quota: { lowThreshold: 0.10, criticalThreshold: 0.05, staleMs: 300000 }, weights: { health: 2, tokens: 5, quota: 3, lru: 0.1 } } } }, { name: 'Many Accounts (10+)', builtIn: true, descriptionKey: 'presetManyAccountsDesc', config: { maxRetries: 3, retryBaseMs: 500, retryMaxMs: 15000, defaultCooldownMs: 5000, maxWaitBeforeErrorMs: 60000, maxAccounts: 50, globalQuotaThreshold: 0.10, rateLimitDedupWindowMs: 1000, maxConsecutiveFailures: 2, extendedCooldownMs: 30000, maxCapacityRetries: 3, switchAccountDelayMs: 3000, capacityBackoffTiersMs: [3000, 6000, 12000, 20000, 40000], accountSelection: { strategy: 'hybrid', healthScore: { initial: 70, successReward: 1, rateLimitPenalty: -15, failurePenalty: -25, recoveryPerHour: 5, minUsable: 40, maxScore: 100 }, tokenBucket: { maxTokens: 30, tokensPerMinute: 8, initialTokens: 30 }, quota: { lowThreshold: 0.15, criticalThreshold: 0.05, staleMs: 180000 }, weights: { health: 5, tokens: 2, quota: 3, lru: 0.01 } } } }, { name: 'Conservative', builtIn: true, descriptionKey: 'presetConservativeDesc', config: { maxRetries: 8, retryBaseMs: 2000, retryMaxMs: 60000, defaultCooldownMs: 20000, maxWaitBeforeErrorMs: 240000, maxAccounts: 10, globalQuotaThreshold: 0.20, rateLimitDedupWindowMs: 3000, maxConsecutiveFailures: 5, extendedCooldownMs: 120000, maxCapacityRetries: 8, switchAccountDelayMs: 8000, capacityBackoffTiersMs: [8000, 15000, 30000, 45000, 90000], accountSelection: { strategy: 'sticky', healthScore: { initial: 80, successReward: 2, rateLimitPenalty: -5, failurePenalty: -10, recoveryPerHour: 3, minUsable: 50, maxScore: 100 }, tokenBucket: { maxTokens: 80, tokensPerMinute: 4, initialTokens: 80 }, quota: { lowThreshold: 0.20, criticalThreshold: 0.10, staleMs: 300000 }, weights: { health: 3, tokens: 4, quota: 2, lru: 0.05 } } } } ]; export default { IDE_TYPE, PLATFORM, PLUGIN_TYPE, CLIENT_METADATA, ANTIGRAVITY_ENDPOINT_FALLBACKS, ANTIGRAVITY_HEADERS, LOAD_CODE_ASSIST_ENDPOINTS, ONBOARD_USER_ENDPOINTS, LOAD_CODE_ASSIST_HEADERS, DEFAULT_PROJECT_ID, TOKEN_REFRESH_INTERVAL_MS, REQUEST_BODY_LIMIT, ANTIGRAVITY_AUTH_PORT, DEFAULT_PORT, ACCOUNT_CONFIG_PATH, ANTIGRAVITY_DB_PATH, DEFAULT_COOLDOWN_MS, MAX_RETRIES, MAX_EMPTY_RESPONSE_RETRIES, MAX_ACCOUNTS, MAX_WAIT_BEFORE_ERROR_MS, RATE_LIMIT_DEDUP_WINDOW_MS, RATE_LIMIT_STATE_RESET_MS, FIRST_RETRY_DELAY_MS, SWITCH_ACCOUNT_DELAY_MS, MAX_CONSECUTIVE_FAILURES, EXTENDED_COOLDOWN_MS, CAPACITY_BACKOFF_TIERS_MS, MAX_CAPACITY_RETRIES, BACKOFF_BY_ERROR_TYPE, QUOTA_EXHAUSTED_BACKOFF_TIERS_MS, MIN_BACKOFF_MS, CAPACITY_JITTER_MAX_MS, MIN_SIGNATURE_LENGTH, GEMINI_MAX_OUTPUT_TOKENS, GEMINI_SKIP_SIGNATURE, GEMINI_SIGNATURE_CACHE_TTL_MS, MODEL_VALIDATION_CACHE_TTL_MS, getModelFamily, isThinkingModel, OAUTH_CONFIG, OAUTH_REDIRECT_URI, STRATEGY_LABELS, MODEL_FALLBACK_MAP, TEST_MODELS, DEFAULT_PRESETS, DEFAULT_SERVER_PRESETS, ANTIGRAVITY_SYSTEM_INSTRUCTION };