105 lines
3.2 KiB
TypeScript
105 lines
3.2 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
type AuthCredential =
|
|
| { type?: unknown; key?: unknown }
|
|
| Array<{ type?: unknown; key?: unknown }>;
|
|
|
|
type AuthStorageData = Record<string, AuthCredential>;
|
|
|
|
const AUTH_ENV_KEYS = [
|
|
["anthropic", "ANTHROPIC_API_KEY"],
|
|
["openai", "OPENAI_API_KEY"],
|
|
["github-copilot", "GITHUB_TOKEN"],
|
|
["google", "GEMINI_API_KEY"],
|
|
["groq", "GROQ_API_KEY"],
|
|
["xai", "XAI_API_KEY"],
|
|
["openrouter", "OPENROUTER_API_KEY"],
|
|
["mistral", "MISTRAL_API_KEY"],
|
|
["xiaomi", "XIAOMI_API_KEY"],
|
|
["xiaomi-token-plan-ams", "XIAOMI_API_KEY"],
|
|
["xiaomi-token-plan-sgp", "XIAOMI_API_KEY"],
|
|
["xiaomi-token-plan-cn", "XIAOMI_API_KEY"],
|
|
["ollama-cloud", "OLLAMA_API_KEY"],
|
|
["custom-openai", "CUSTOM_OPENAI_API_KEY"],
|
|
["cerebras", "CEREBRAS_API_KEY"],
|
|
["azure-openai-responses", "AZURE_OPENAI_API_KEY"],
|
|
["vercel-ai-gateway", "AI_GATEWAY_API_KEY"],
|
|
["zai", "ZAI_API_KEY"],
|
|
["minimax", "MINIMAX_API_KEY"],
|
|
["minimax-cn", "MINIMAX_CN_API_KEY"],
|
|
["huggingface", "HF_TOKEN"],
|
|
["opencode", "OPENCODE_API_KEY"],
|
|
["opencode-go", "OPENCODE_API_KEY"],
|
|
["kimi-coding", "KIMI_API_KEY"],
|
|
["alibaba-coding-plan", "ALIBABA_API_KEY"],
|
|
["brave", "BRAVE_API_KEY"],
|
|
["brave_answers", "BRAVE_ANSWERS_KEY"],
|
|
["serper", "SERPER_API_KEY"],
|
|
["exa", "EXA_API_KEY"],
|
|
["context7", "CONTEXT7_API_KEY"],
|
|
["jina", "JINA_API_KEY"],
|
|
["tavily", "TAVILY_API_KEY"],
|
|
["slack_bot", "SLACK_BOT_TOKEN"],
|
|
["discord_bot", "DISCORD_BOT_TOKEN"],
|
|
["telegram_bot", "TELEGRAM_BOT_TOKEN"],
|
|
] as const;
|
|
|
|
function expandHome(pathValue: string): string {
|
|
if (pathValue === "~") return homedir();
|
|
if (pathValue.startsWith("~/")) return join(homedir(), pathValue.slice(2));
|
|
return pathValue;
|
|
}
|
|
|
|
function getStoredApiKey(
|
|
data: AuthStorageData,
|
|
providerId: string,
|
|
): string | undefined {
|
|
const raw = data[providerId];
|
|
const credentials = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
|
|
for (const credential of credentials) {
|
|
if (credential?.type !== "api_key") continue;
|
|
if (typeof credential.key !== "string") continue;
|
|
if (credential.key.trim().length === 0) continue;
|
|
return credential.key;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export function resolveAuthPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
const agentDir = env.SF_CODING_AGENT_DIR?.trim();
|
|
if (agentDir) return join(expandHome(agentDir), "auth.json");
|
|
return join(homedir(), ".sf", "agent", "auth.json");
|
|
}
|
|
|
|
export function loadStoredCredentialEnvKeys(
|
|
options: { env?: NodeJS.ProcessEnv; authPath?: string } = {},
|
|
): string[] {
|
|
const env = options.env ?? process.env;
|
|
const authPath = options.authPath ?? resolveAuthPath(env);
|
|
if (!existsSync(authPath)) return [];
|
|
|
|
let parsed: AuthStorageData;
|
|
try {
|
|
const raw = readFileSync(authPath, "utf-8");
|
|
const data = JSON.parse(raw) as unknown;
|
|
if (!data || typeof data !== "object" || Array.isArray(data)) return [];
|
|
parsed = data as AuthStorageData;
|
|
} catch {
|
|
return [];
|
|
}
|
|
|
|
const loaded: string[] = [];
|
|
for (const [providerId, envVar] of AUTH_ENV_KEYS) {
|
|
if (env[envVar]) continue;
|
|
const key = getStoredApiKey(parsed, providerId);
|
|
if (!key) continue;
|
|
env[envVar] = key;
|
|
loaded.push(envVar);
|
|
}
|
|
|
|
return loaded;
|
|
}
|