import { ANTIGRAVITY_PROVIDER_ID, CODE_ASSIST_ENDPOINT, CODE_ASSIST_HEADERS, } from "../constants"; import { formatRefreshParts, parseRefreshParts } from "./auth"; import { printAntigravityConsole } from "./logger"; import type { OAuthAuthDetails, PluginClient, ProjectContextResult, } from "./types"; const projectContextResultCache = new Map(); const projectContextPendingCache = new Map>(); const CODE_ASSIST_METADATA = { ideType: "IDE_UNSPECIFIED", platform: "PLATFORM_UNSPECIFIED", pluginType: "ANTIGRAVITY", } as const; interface AntigravityUserTier { id?: string; isDefault?: boolean; userDefinedCloudaicompanionProject?: boolean; } interface LoadCodeAssistPayload { cloudaicompanionProject?: string; currentTier?: { id?: string; }; allowedTiers?: AntigravityUserTier[]; } interface OnboardUserPayload { done?: boolean; response?: { cloudaicompanionProject?: { id?: string; }; }; } class ProjectIdRequiredError extends Error { /** * Error raised when a required Google Cloud project is missing during Antigravity onboarding. */ constructor() { super( "Antigravity requires a Google Cloud project. Enable the Gemini for Google Cloud API on a project you control, rerun `opencode auth login`, and supply that project ID when prompted.", ); } } /** * Builds metadata headers required by the Code Assist API. */ function buildMetadata(projectId?: string): Record { const metadata: Record = { ideType: CODE_ASSIST_METADATA.ideType, platform: CODE_ASSIST_METADATA.platform, pluginType: CODE_ASSIST_METADATA.pluginType, }; if (projectId) { metadata.duetProject = projectId; } return metadata; } /** * Selects the default tier ID from the allowed tiers list. */ function getDefaultTierId(allowedTiers?: AntigravityUserTier[]): string | undefined { if (!allowedTiers || allowedTiers.length === 0) { return undefined; } for (const tier of allowedTiers) { if (tier?.isDefault) { return tier.id; } } return allowedTiers[0]?.id; } /** * Promise-based delay utility. */ function wait(ms: number): Promise { return new Promise(function (resolve) { setTimeout(resolve, ms); }); } /** * Generates a cache key for project context based on refresh token. */ function getCacheKey(auth: OAuthAuthDetails): string | undefined { const refresh = auth.refresh?.trim(); return refresh ? refresh : undefined; } /** * Clears cached project context results and pending promises, globally or for a refresh key. */ export function invalidateProjectContextCache(refresh?: string): void { if (!refresh) { projectContextPendingCache.clear(); projectContextResultCache.clear(); return; } projectContextPendingCache.delete(refresh); projectContextResultCache.delete(refresh); } /** * Loads managed project information for the given access token and optional project. */ export async function loadManagedProject( accessToken: string, projectId?: string, ): Promise { try { const metadata = buildMetadata(projectId); const requestBody: Record = { metadata }; if (projectId) { requestBody.cloudaicompanionProject = projectId; } const response = await fetch( `${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, ...CODE_ASSIST_HEADERS, }, body: JSON.stringify(requestBody), }, ); if (!response.ok) { return null; } return (await response.json()) as LoadCodeAssistPayload; } catch (error) { printAntigravityConsole("error", "Failed to load Antigravity managed project", error); return null; } } /** * Onboards a managed project for the user, optionally retrying until completion. */ export async function onboardManagedProject( accessToken: string, tierId: string, projectId?: string, attempts = 10, delayMs = 5000, ): Promise { const metadata = buildMetadata(projectId); const requestBody: Record = { tierId, metadata, }; if (tierId !== "FREE") { if (!projectId) { throw new ProjectIdRequiredError(); } requestBody.cloudaicompanionProject = projectId; } for (let attempt = 0; attempt < attempts; attempt += 1) { try { const response = await fetch( `${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, ...CODE_ASSIST_HEADERS, }, body: JSON.stringify(requestBody), }, ); if (!response.ok) { return undefined; } const payload = (await response.json()) as OnboardUserPayload; const managedProjectId = payload.response?.cloudaicompanionProject?.id; if (payload.done && managedProjectId) { return managedProjectId; } if (payload.done && projectId) { return projectId; } } catch (error) { printAntigravityConsole("error", "Failed to onboard Antigravity managed project", error); return undefined; } await wait(delayMs); } return undefined; } /** * Resolves an effective project ID for the current auth state, caching results per refresh token. */ export async function ensureProjectContext( auth: OAuthAuthDetails, client: PluginClient, ): Promise { const accessToken = auth.access; if (!accessToken) { return { auth, effectiveProjectId: "" }; } const cacheKey = getCacheKey(auth); if (cacheKey) { const cached = projectContextResultCache.get(cacheKey); if (cached) { return cached; } const pending = projectContextPendingCache.get(cacheKey); if (pending) { return pending; } } const resolveContext = async (): Promise => { const parts = parseRefreshParts(auth.refresh); if (parts.projectId || parts.managedProjectId) { return { auth, effectiveProjectId: parts.projectId || parts.managedProjectId || "", }; } const loadPayload = await loadManagedProject(accessToken, parts.projectId); if (loadPayload?.cloudaicompanionProject) { const managedProjectId = loadPayload.cloudaicompanionProject; const updatedAuth: OAuthAuthDetails = { ...auth, refresh: formatRefreshParts({ refreshToken: parts.refreshToken, projectId: parts.projectId, managedProjectId, }), }; await client.auth.set({ path: { id: ANTIGRAVITY_PROVIDER_ID }, body: updatedAuth, }); return { auth: updatedAuth, effectiveProjectId: managedProjectId }; } if (!loadPayload) { throw new ProjectIdRequiredError(); } const currentTierId = loadPayload.currentTier?.id ?? undefined; if (currentTierId && currentTierId !== "FREE") { throw new ProjectIdRequiredError(); } const defaultTierId = getDefaultTierId(loadPayload.allowedTiers); const tierId = defaultTierId ?? "FREE"; if (tierId !== "FREE") { throw new ProjectIdRequiredError(); } const managedProjectId = await onboardManagedProject(accessToken, tierId, parts.projectId); if (managedProjectId) { const updatedAuth: OAuthAuthDetails = { ...auth, refresh: formatRefreshParts({ refreshToken: parts.refreshToken, projectId: parts.projectId, managedProjectId, }), }; await client.auth.set({ path: { id: ANTIGRAVITY_PROVIDER_ID }, body: updatedAuth, }); return { auth: updatedAuth, effectiveProjectId: managedProjectId }; } throw new ProjectIdRequiredError(); }; if (!cacheKey) { return resolveContext(); } const promise = resolveContext() .then((result) => { const nextKey = getCacheKey(result.auth) ?? cacheKey; projectContextPendingCache.delete(cacheKey); projectContextResultCache.set(nextKey, result); if (nextKey !== cacheKey) { projectContextResultCache.delete(cacheKey); } return result; }) .catch((error) => { projectContextPendingCache.delete(cacheKey); throw error; }); projectContextPendingCache.set(cacheKey, promise); return promise; }