Files
fusion/packages/engine/src/auth-storage.ts

147 lines
4.3 KiB
TypeScript

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<string, StoredCredential> {
const credentials: Record<string, StoredCredential> = {};
for (const authPath of authPaths) {
if (!existsSync(authPath)) {
continue;
}
try {
const parsed = JSON.parse(readFileSync(authPath, "utf-8")) as Record<string, StoredCredential>;
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;
}