const ANTIGRAVITY_RELEASE_FEED_URL = "https://antigravity-auto-updater-974169037036.us-central1.run.app/releases"; const ANTIGRAVITY_GITHUB_RELEASE_URL = "https://api.github.com/repos/antigravityide/antigravity/releases/latest"; export const ANTIGRAVITY_VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000; export const ANTIGRAVITY_VERSION_FETCH_TIMEOUT_MS = 5_000; // Floor version synced with Antigravity-Manager v4.2.0 KNOWN_STABLE_VERSION. export const ANTIGRAVITY_FALLBACK_VERSION = "4.2.0"; type VersionCache = { fetchedAt: number; version: string; }; type FetchLike = typeof fetch; let versionCache: VersionCache | null = null; let inFlightRequest: Promise | null = null; function normalizeVersion(value: unknown): string | null { if (typeof value !== "string") return null; const trimmed = value.trim().replace(/^v/i, ""); const match = trimmed.match(/^(\d+\.\d+\.\d+)\b/); return match ? match[1] : null; } function compareSemver(a: string, b: string): number { const aParts = a.split(".").map((part) => Number.parseInt(part, 10) || 0); const bParts = b.split(".").map((part) => Number.parseInt(part, 10) || 0); for (let i = 0; i < 3; i += 1) { if (aParts[i] !== bParts[i]) return aParts[i] - bParts[i]; } return 0; } function pickNewestVersion(...versions: Array): string { return versions .map((version) => normalizeVersion(version)) .filter((version): version is string => !!version) .reduce( (best, version) => (compareSemver(version, best) > 0 ? version : best), ANTIGRAVITY_FALLBACK_VERSION ); } async function fetchJsonWithTimeout(fetchImpl: FetchLike, url: string): Promise { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), ANTIGRAVITY_VERSION_FETCH_TIMEOUT_MS); try { const response = await fetchImpl(url, { headers: { Accept: "application/json", "User-Agent": "OmniRoute-AntigravityVersion/1.0", }, signal: controller.signal, }); if (!response.ok) { throw new Error(`Version source ${url} returned ${response.status}`); } return response.json(); } finally { clearTimeout(timeoutId); } } function parseOfficialReleaseFeed(payload: unknown): string | null { if (!Array.isArray(payload)) return null; for (const entry of payload) { const version = normalizeVersion((entry as { version?: unknown })?.version); if (version) return version; } return null; } function parseGitHubRelease(payload: unknown): string | null { if (!payload || typeof payload !== "object") return null; const candidate = (payload as { tag_name?: unknown }).tag_name ?? (payload as { name?: unknown }).name; return normalizeVersion(candidate); } async function fetchLatestAntigravityVersion(fetchImpl: FetchLike): Promise { const sources = [ { parse: parseOfficialReleaseFeed, url: ANTIGRAVITY_RELEASE_FEED_URL, }, { parse: parseGitHubRelease, url: ANTIGRAVITY_GITHUB_RELEASE_URL, }, ]; for (const source of sources) { try { const payload = await fetchJsonWithTimeout(fetchImpl, source.url); const version = source.parse(payload); if (version) return version; } catch { // Try the next source and fall back to the last known good version if all fail. } } return null; } export async function resolveAntigravityVersion(fetchImpl: FetchLike = fetch): Promise { const now = Date.now(); if (versionCache && now - versionCache.fetchedAt < ANTIGRAVITY_VERSION_CACHE_TTL_MS) { return versionCache.version; } if (inFlightRequest) { return inFlightRequest; } inFlightRequest = (async () => { const resolved = await fetchLatestAntigravityVersion(fetchImpl); const version = resolved ? pickNewestVersion(resolved, ANTIGRAVITY_FALLBACK_VERSION) : pickNewestVersion(versionCache?.version, ANTIGRAVITY_FALLBACK_VERSION); if (resolved) { versionCache = { fetchedAt: Date.now(), version, }; } return version; })(); try { return await inFlightRequest; } finally { inFlightRequest = null; } } export function getCachedAntigravityVersion(): string { return versionCache?.version || ANTIGRAVITY_FALLBACK_VERSION; } export function seedAntigravityVersionCache(version: string, fetchedAt = Date.now()): void { versionCache = { fetchedAt, version, }; } export function clearAntigravityVersionCache(): void { versionCache = null; inFlightRequest = null; }