import { existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; import { AuthStorage } from "@mariozechner/pi-coding-agent"; import { getOAuthProvider } from "@mariozechner/pi-ai/oauth"; import type { OAuthCredentials } from "@mariozechner/pi-ai/oauth"; type StoredCredential = { type?: string; key?: string; access?: string; refresh?: string; expires?: number; [key: string]: unknown; }; function getHomeDir(): string { return process.env.HOME || process.env.USERPROFILE || homedir(); } export function getFusionAuthPath(home = getHomeDir()): string { return join(home, ".fusion", "agent", "auth.json"); } export function getFusionModelsPath(home = getHomeDir()): string { return join(home, ".fusion", "agent", "models.json"); } function getLegacyAuthPaths(home = getHomeDir()): string[] { return [ join(home, ".pi", "agent", "auth.json"), join(home, ".pi", "auth.json"), ]; } function getLegacyModelsPaths(home = getHomeDir()): string[] { return [ join(home, ".pi", "agent", "models.json"), join(home, ".pi", "models.json"), ]; } export function getModelRegistryModelsPath(home = getHomeDir()): string { const fusionModelsPath = getFusionModelsPath(home); if (existsSync(fusionModelsPath)) { return fusionModelsPath; } return getLegacyModelsPaths(home).find((modelsPath) => existsSync(modelsPath)) ?? fusionModelsPath; } function readLegacyCredentials(authPaths = getLegacyAuthPaths()): Record { const credentials: Record = {}; for (const authPath of authPaths) { if (!existsSync(authPath)) { continue; } try { const parsed = JSON.parse(readFileSync(authPath, "utf-8")) as Record; for (const [provider, credential] of Object.entries(parsed)) { credentials[provider] ??= credential; } } catch { // Ignore invalid legacy auth files and continue with other candidates. } } return credentials; } function resolveStoredApiKey(key: string | undefined): string | undefined { if (!key) return undefined; return process.env[key] ?? key; } function resolveOAuthApiKey(providerId: string, credential: StoredCredential): string | undefined { if ( credential.type !== "oauth" || typeof credential.access !== "string" || typeof credential.refresh !== "string" || typeof credential.expires !== "number" || Date.now() >= credential.expires ) { return undefined; } return getOAuthProvider(providerId)?.getApiKey(credential as OAuthCredentials); } function resolveStoredCredentialApiKey(providerId: string, credential: StoredCredential | undefined): string | undefined { if (credential?.type === "api_key") { return resolveStoredApiKey(credential.key); } if (credential?.type === "oauth") { return resolveOAuthApiKey(providerId, credential); } return undefined; } export function createFusionAuthStorage(): AuthStorage { const primary = AuthStorage.create(getFusionAuthPath()); let legacyCredentials = readLegacyCredentials(); return new Proxy(primary, { get(target, prop, receiver) { if (prop === "reload") { return () => { target.reload(); legacyCredentials = readLegacyCredentials(); }; } if (prop === "get") { return (provider: string) => target.get(provider) ?? legacyCredentials[provider]; } if (prop === "has") { return (provider: string) => target.has(provider) || provider in legacyCredentials; } if (prop === "hasAuth") { return (provider: string) => target.hasAuth(provider) || Boolean(legacyCredentials[provider]); } if (prop === "getAll") { return () => ({ ...legacyCredentials, ...target.getAll() }); } if (prop === "list") { return () => Array.from(new Set([...Object.keys(legacyCredentials), ...target.list()])); } if (prop === "getApiKey") { return async (provider: string) => { const primaryKey = await target.getApiKey(provider); if (primaryKey) return primaryKey; return resolveStoredCredentialApiKey(provider, legacyCredentials[provider]); }; } return Reflect.get(target, prop, receiver); }, }) as AuthStorage; }