singularity-forge/packages/mcp-server/src/tool-credentials.ts
2026-05-05 14:31:16 +02:00

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;
}