Spaces:
Running
Running
| /** | |
| * Bootstrap Antigravity OAuth accounts from environment variables. | |
| */ | |
| import { parseRefreshParts, formatRefreshParts } from '../auth/oauth.js'; | |
| import { loadAccounts, saveAccounts } from '../account-manager/storage.js'; | |
| import { logger } from '../utils/logger.js'; | |
| function envValue(env, key) { | |
| const value = env[key]; | |
| return typeof value === 'string' ? value.trim() : ''; | |
| } | |
| function isTruthy(value) { | |
| return ['1', 'true', 'yes', 'on'].includes(String(value || '').trim().toLowerCase()); | |
| } | |
| function normalizeTier(value) { | |
| const tier = String(value || '').trim().toLowerCase(); | |
| if (tier.includes('ultra')) return 'ultra'; | |
| if (tier.includes('pro') || tier.includes('premium') || tier.includes('standard')) return 'pro'; | |
| if (tier.includes('free')) return 'free'; | |
| return 'unknown'; | |
| } | |
| function findCredentialIndexes(env) { | |
| const indexes = new Set(); | |
| for (const key of Object.keys(env)) { | |
| const match = key.match(/^ANTIGRAVITY_(\d+)_REFRESH_TOKEN$/); | |
| if (match && envValue(env, key)) indexes.add(Number(match[1])); | |
| } | |
| if (indexes.size === 0 && envValue(env, 'ANTIGRAVITY_REFRESH_TOKEN')) { | |
| indexes.add(0); | |
| } | |
| return [...indexes].sort((a, b) => a - b); | |
| } | |
| export function collectEnvAccounts(env = process.env) { | |
| const accounts = []; | |
| for (const index of findCredentialIndexes(env)) { | |
| const prefix = index === 0 ? 'ANTIGRAVITY' : `ANTIGRAVITY_${index}`; | |
| const email = envValue(env, `${prefix}_EMAIL`); | |
| const rawRefreshToken = envValue(env, `${prefix}_REFRESH_TOKEN`); | |
| const projectId = envValue(env, `${prefix}_PROJECT_ID`); | |
| const tier = normalizeTier(envValue(env, `${prefix}_TIER`)); | |
| if (!email) { | |
| throw new Error(`${prefix}_EMAIL is required when ${prefix}_REFRESH_TOKEN is set`); | |
| } | |
| if (!rawRefreshToken) continue; | |
| const existingParts = parseRefreshParts(rawRefreshToken); | |
| const resolvedProjectId = projectId | |
| || existingParts.managedProjectId | |
| || existingParts.projectId; | |
| const refreshToken = formatRefreshParts({ | |
| refreshToken: existingParts.refreshToken, | |
| projectId: existingParts.projectId || resolvedProjectId, | |
| managedProjectId: existingParts.managedProjectId || resolvedProjectId | |
| }); | |
| accounts.push({ | |
| email, | |
| source: 'oauth', | |
| enabled: true, | |
| refreshToken, | |
| projectId: resolvedProjectId || undefined, | |
| addedAt: new Date().toISOString(), | |
| isInvalid: false, | |
| invalidReason: null, | |
| verifyUrl: null, | |
| modelRateLimits: {}, | |
| lastUsed: null, | |
| subscription: { | |
| tier, | |
| projectId: resolvedProjectId || null, | |
| detectedAt: tier !== 'unknown' || resolvedProjectId ? Date.now() : null | |
| }, | |
| quota: { | |
| models: {}, | |
| lastChecked: null | |
| }, | |
| modelQuotaThresholds: {} | |
| }); | |
| } | |
| return accounts; | |
| } | |
| export async function bootstrapEnvAccounts(configPath, env = process.env) { | |
| const envAccounts = collectEnvAccounts(env); | |
| if (envAccounts.length === 0) { | |
| return { changed: false, count: 0 }; | |
| } | |
| const force = isTruthy(env.FORCE_ENV_ACCOUNTS); | |
| const { accounts: existingAccounts, settings, activeIndex } = await loadAccounts(configPath); | |
| const merged = force ? [] : [...existingAccounts]; | |
| for (const envAccount of envAccounts) { | |
| const existingIndex = merged.findIndex(account => account.email === envAccount.email); | |
| if (existingIndex === -1) { | |
| merged.push(envAccount); | |
| } else { | |
| merged[existingIndex] = { | |
| ...merged[existingIndex], | |
| ...envAccount, | |
| addedAt: merged[existingIndex].addedAt || envAccount.addedAt, | |
| modelRateLimits: force ? {} : (merged[existingIndex].modelRateLimits || {}), | |
| lastUsed: force ? null : (merged[existingIndex].lastUsed || null), | |
| quota: force ? envAccount.quota : (merged[existingIndex].quota || envAccount.quota) | |
| }; | |
| } | |
| } | |
| await saveAccounts(configPath, merged, settings, Math.min(activeIndex, Math.max(0, merged.length - 1))); | |
| // saveAccounts logs and swallows I/O errors, so verify persistence explicitly. | |
| const { accounts: persistedAccounts } = await loadAccounts(configPath); | |
| const persistedEmails = new Set(persistedAccounts.map(account => account.email)); | |
| const missingEmails = envAccounts | |
| .map(account => account.email) | |
| .filter(email => !persistedEmails.has(email)); | |
| if (missingEmails.length > 0) { | |
| throw new Error(`Failed to persist environment accounts: ${missingEmails.join(', ')}`); | |
| } | |
| logger.info(`[Bootstrap] Loaded ${envAccounts.length} Antigravity account(s) from environment${force ? ' (authoritative)' : ''}`); | |
| return { changed: true, count: envAccounts.length }; | |
| } | |