Spaces:
Running
Running
| /** | |
| * 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 } 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': '1.107.0', // Match product.json version | |
| '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 = process.env.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-thinking', | |
| 'claude-opus-4-6-thinking': 'gemini-3.1-pro-high', | |
| 'claude-sonnet-4-6-thinking': 'gemini-3-flash', | |
| '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-thinking', | |
| gemini: 'gemini-3-flash' | |
| }; | |
| // 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-thinking', | |
| ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-sonnet-4-6', | |
| CLAUDE_CODE_SUBAGENT_MODEL: 'claude-sonnet-4-6-thinking', | |
| 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-high', | |
| ANTHROPIC_DEFAULT_OPUS_MODEL: 'gemini-3.1-pro-high', | |
| ANTHROPIC_DEFAULT_SONNET_MODEL: 'gemini-3.1-flash', | |
| ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-3.1-flash', | |
| CLAUDE_CODE_SUBAGENT_MODEL: 'gemini-3.1-flash', | |
| 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 | |
| }; | |