import fs from "node:fs"; import path from "node:path"; import type { OAuthCredentials } from "@mariozechner/pi-ai"; import { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; import { upsertAuthProfile } from "../agents/auth-profiles.js"; import { resolveStateDir } from "../config/paths.js"; export { CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF } from "../agents/cloudflare-ai-gateway.js"; export { XAI_DEFAULT_MODEL_REF } from "./onboard-auth.models.js"; const resolveAuthAgentDir = (agentDir?: string) => agentDir ?? resolveOpenClawAgentDir(); export type WriteOAuthCredentialsOptions = { syncSiblingAgents?: boolean; }; /** Resolve real path, returning null if the target doesn't exist. */ function safeRealpathSync(dir: string): string | null { try { return fs.realpathSync(path.resolve(dir)); } catch { return null; } } function resolveSiblingAgentDirs(primaryAgentDir: string): string[] { const normalized = path.resolve(primaryAgentDir); // Derive agentsRoot from primaryAgentDir when it matches the standard // layout (.../agents//agent). Falls back to global state dir. const parentOfAgent = path.dirname(normalized); const candidateAgentsRoot = path.dirname(parentOfAgent); const looksLikeStandardLayout = path.basename(normalized) === "agent" && path.basename(candidateAgentsRoot) === "agents"; const agentsRoot = looksLikeStandardLayout ? candidateAgentsRoot : path.join(resolveStateDir(), "agents"); const entries = (() => { try { return fs.readdirSync(agentsRoot, { withFileTypes: true }); } catch { return []; } })(); // Include both directories and symlinks-to-directories. const discovered = entries .filter((entry) => entry.isDirectory() || entry.isSymbolicLink()) .map((entry) => path.join(agentsRoot, entry.name, "agent")); // Deduplicate via realpath to handle symlinks and path normalization. const seen = new Set(); const result: string[] = []; for (const dir of [normalized, ...discovered]) { const real = safeRealpathSync(dir); if (real && !seen.has(real)) { seen.add(real); result.push(real); } } return result; } export async function writeOAuthCredentials( provider: string, creds: OAuthCredentials, agentDir?: string, options?: WriteOAuthCredentialsOptions, ): Promise { const email = typeof creds.email === "string" && creds.email.trim() ? creds.email.trim() : "default"; const profileId = `${provider}:${email}`; const resolvedAgentDir = path.resolve(resolveAuthAgentDir(agentDir)); const targetAgentDirs = options?.syncSiblingAgents ? resolveSiblingAgentDirs(resolvedAgentDir) : [resolvedAgentDir]; const credential = { type: "oauth" as const, provider, ...creds, }; // Primary write must succeed — let it throw on failure. upsertAuthProfile({ profileId, credential, agentDir: resolvedAgentDir, }); // Sibling sync is best-effort — log and ignore individual failures. if (options?.syncSiblingAgents) { const primaryReal = safeRealpathSync(resolvedAgentDir); for (const targetAgentDir of targetAgentDirs) { const targetReal = safeRealpathSync(targetAgentDir); if (targetReal && primaryReal && targetReal === primaryReal) { continue; } try { upsertAuthProfile({ profileId, credential, agentDir: targetAgentDir, }); } catch { // Best-effort: sibling sync failure must not block primary onboarding. } } } return profileId; } export async function setAnthropicApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. upsertAuthProfile({ profileId: "anthropic:default", credential: { type: "api_key", provider: "anthropic", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setGeminiApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. upsertAuthProfile({ profileId: "google:default", credential: { type: "api_key", provider: "google", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setMinimaxApiKey( key: string, agentDir?: string, profileId: string = "minimax:default", ) { const provider = profileId.split(":")[0] ?? "minimax"; // Write to resolved agent dir so gateway finds credentials on startup. upsertAuthProfile({ profileId, credential: { type: "api_key", provider, key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setMoonshotApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. upsertAuthProfile({ profileId: "moonshot:default", credential: { type: "api_key", provider: "moonshot", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setKimiCodingApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. upsertAuthProfile({ profileId: "kimi-coding:default", credential: { type: "api_key", provider: "kimi-coding", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setSyntheticApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. upsertAuthProfile({ profileId: "synthetic:default", credential: { type: "api_key", provider: "synthetic", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setVeniceApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. upsertAuthProfile({ profileId: "venice:default", credential: { type: "api_key", provider: "venice", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export const ZAI_DEFAULT_MODEL_REF = "zai/glm-5"; export const XIAOMI_DEFAULT_MODEL_REF = "xiaomi/mimo-v2-flash"; export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; export const HUGGINGFACE_DEFAULT_MODEL_REF = "huggingface/deepseek-ai/DeepSeek-R1"; export const TOGETHER_DEFAULT_MODEL_REF = "together/moonshotai/Kimi-K2.5"; export const LITELLM_DEFAULT_MODEL_REF = "litellm/claude-opus-4-6"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.6"; export async function setZaiApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. upsertAuthProfile({ profileId: "zai:default", credential: { type: "api_key", provider: "zai", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setXiaomiApiKey(key: string, agentDir?: string) { upsertAuthProfile({ profileId: "xiaomi:default", credential: { type: "api_key", provider: "xiaomi", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setOpenrouterApiKey(key: string, agentDir?: string) { // Never persist the literal "undefined" (e.g. when prompt returns undefined and caller used String(key)). const safeKey = key === "undefined" ? "" : key; upsertAuthProfile({ profileId: "openrouter:default", credential: { type: "api_key", provider: "openrouter", key: safeKey, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setCloudflareAiGatewayConfig( accountId: string, gatewayId: string, apiKey: string, agentDir?: string, ) { const normalizedAccountId = accountId.trim(); const normalizedGatewayId = gatewayId.trim(); const normalizedKey = apiKey.trim(); upsertAuthProfile({ profileId: "cloudflare-ai-gateway:default", credential: { type: "api_key", provider: "cloudflare-ai-gateway", key: normalizedKey, metadata: { accountId: normalizedAccountId, gatewayId: normalizedGatewayId, }, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setLitellmApiKey(key: string, agentDir?: string) { upsertAuthProfile({ profileId: "litellm:default", credential: { type: "api_key", provider: "litellm", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setVercelAiGatewayApiKey(key: string, agentDir?: string) { upsertAuthProfile({ profileId: "vercel-ai-gateway:default", credential: { type: "api_key", provider: "vercel-ai-gateway", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setOpencodeZenApiKey(key: string, agentDir?: string) { upsertAuthProfile({ profileId: "opencode:default", credential: { type: "api_key", provider: "opencode", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setTogetherApiKey(key: string, agentDir?: string) { upsertAuthProfile({ profileId: "together:default", credential: { type: "api_key", provider: "together", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export async function setHuggingfaceApiKey(key: string, agentDir?: string) { upsertAuthProfile({ profileId: "huggingface:default", credential: { type: "api_key", provider: "huggingface", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export function setQianfanApiKey(key: string, agentDir?: string) { upsertAuthProfile({ profileId: "qianfan:default", credential: { type: "api_key", provider: "qianfan", key, }, agentDir: resolveAuthAgentDir(agentDir), }); } export function setXaiApiKey(key: string, agentDir?: string) { upsertAuthProfile({ profileId: "xai:default", credential: { type: "api_key", provider: "xai", key, }, agentDir: resolveAuthAgentDir(agentDir), }); }