From 9858576abe56c4870f49feade2b36d290ab51296 Mon Sep 17 00:00:00 2001 From: yushen Date: Fri, 30 Jan 2026 13:12:24 +0800 Subject: [PATCH] feat(agent): support custom API key and base URL configuration Add apiKey and baseUrl options to AgentOptions, with support for: - Explicit values via CLI args (--api-key, --base-url) - Provider-specific environment variables (e.g., OPENAI_BASE_URL) - Generic fallback format (PROVIDER_BASE_URL) Priority: CLI args > env vars > library defaults. Co-Authored-By: Claude Opus 4.5 --- src/agent/cli.ts | 14 +++++++++++ src/agent/runner.ts | 59 ++++++++++++++++++++++++++++++++++++++++----- src/agent/types.ts | 4 +++ 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/agent/cli.ts b/src/agent/cli.ts index ab017aceeb..0cbd6662a0 100644 --- a/src/agent/cli.ts +++ b/src/agent/cli.ts @@ -5,6 +5,8 @@ type CliOptions = { profile?: string | undefined; provider?: string | undefined; model?: string | undefined; + apiKey?: string | undefined; + baseUrl?: string | undefined; system?: string | undefined; thinking?: string | undefined; cwd?: string | undefined; @@ -21,6 +23,8 @@ function printUsage() { console.log(" --profile ID Load agent profile (identity, soul, tools, memory)"); console.log(" --provider NAME LLM provider (e.g., openai, anthropic, kimi)"); console.log(" --model NAME Model name"); + console.log(" --api-key KEY API key (overrides environment variable)"); + console.log(" --base-url URL Custom base URL for the provider"); console.log(" --system TEXT System prompt (ignored if --profile is set)"); console.log(" --thinking LEVEL Thinking level"); console.log(" --cwd DIR Working directory for commands"); @@ -53,6 +57,14 @@ function parseArgs(argv: string[]) { opts.model = args.shift(); continue; } + if (arg === "--api-key") { + opts.apiKey = args.shift(); + continue; + } + if (arg === "--base-url") { + opts.baseUrl = args.shift(); + continue; + } if (arg === "--system") { opts.system = args.shift(); continue; @@ -112,6 +124,8 @@ async function main() { profileId: opts.profile, provider: opts.provider, model: opts.model, + apiKey: opts.apiKey, + baseUrl: opts.baseUrl, systemPrompt: opts.system, thinkingLevel: opts.thinking as any, cwd: opts.cwd, diff --git a/src/agent/runner.ts b/src/agent/runner.ts index 34cb90de20..8c0d7413bb 100644 --- a/src/agent/runner.ts +++ b/src/agent/runner.ts @@ -13,9 +13,12 @@ import { } from "./context-window/index.js"; /** - * Get API Key based on provider + * Get API Key based on provider. + * Priority: explicit key > provider-specific env var > generic env var format. */ -function resolveApiKey(provider: string): string | undefined { +function resolveApiKey(provider: string, explicitKey?: string): string | undefined { + if (explicitKey) return explicitKey; + const providerEnvMap: Record = { openai: "OPENAI_API_KEY", anthropic: "ANTHROPIC_API_KEY", @@ -39,6 +42,36 @@ function resolveApiKey(provider: string): string | undefined { return process.env[`${normalizedProvider}_API_KEY`]; } +/** + * Get Base URL based on provider. + * Priority: explicit URL > provider-specific env var > generic env var format. + */ +function resolveBaseUrl(provider: string, explicitUrl?: string): string | undefined { + if (explicitUrl) return explicitUrl; + + const providerEnvMap: Record = { + openai: "OPENAI_BASE_URL", + anthropic: "ANTHROPIC_BASE_URL", + google: "GOOGLE_BASE_URL", + "google-genai": "GOOGLE_BASE_URL", + kimi: "MOONSHOT_BASE_URL", + "kimi-coding": "MOONSHOT_BASE_URL", + deepseek: "DEEPSEEK_BASE_URL", + groq: "GROQ_BASE_URL", + mistral: "MISTRAL_BASE_URL", + together: "TOGETHER_BASE_URL", + }; + + const envVar = providerEnvMap[provider]; + if (envVar) { + return process.env[envVar]; + } + + // Try generic format: PROVIDER_BASE_URL + const normalizedProvider = provider.toUpperCase().replace(/-/g, "_"); + return process.env[`${normalizedProvider}_BASE_URL`]; +} + export class Agent { private readonly agent: PiAgentCore; private readonly output; @@ -57,7 +90,15 @@ export class Agent { this.output = createAgentOutput({ stdout, stderr }); this.debug = options.debug ?? false; - this.agent = new PiAgentCore(); + // Resolve provider for API key and base URL + const resolvedProvider = options.provider ?? "kimi-coding"; + const apiKey = resolveApiKey(resolvedProvider, options.apiKey); + + this.agent = new PiAgentCore( + apiKey + ? { getApiKey: (_provider: string) => apiKey } + : {}, + ); // Load Agent Profile (if profileId is specified) let systemPrompt: string | undefined; @@ -101,12 +142,18 @@ export class Agent { return tempSession.getMeta(); })(); - const model = options.provider && options.model ? resolveModel(options) : resolveModel({ + let model = options.provider && options.model ? resolveModel(options) : resolveModel({ ...options, provider: storedMeta?.provider, model: storedMeta?.model, }); + // Override base URL if provided via options or environment variable + const baseUrl = resolveBaseUrl(model.provider, options.baseUrl); + if (baseUrl) { + model = { ...model, baseUrl }; + } + // === Context Window Guard === this.contextWindowGuard = checkContextWindow({ modelContextWindow: model.contextWindow, @@ -133,7 +180,7 @@ export class Agent { const compactionMode = options.compactionMode ?? "tokens"; // 默认使用 token 模式 // 获取 API Key(用于 summary 模式) - const apiKey = compactionMode === "summary" ? resolveApiKey(model.provider) : undefined; + const summaryApiKey = compactionMode === "summary" ? resolveApiKey(model.provider, options.apiKey) : undefined; // 创建 SessionManager(带 context window 配置) this.session = new SessionManager({ @@ -147,7 +194,7 @@ export class Agent { minKeepMessages: options.minKeepMessages, // Summary 模式参数 model: compactionMode === "summary" ? model : undefined, - apiKey, + apiKey: summaryApiKey, customInstructions: options.summaryInstructions, }); diff --git a/src/agent/types.ts b/src/agent/types.ts index 94ab9c451c..405f0cc741 100644 --- a/src/agent/types.ts +++ b/src/agent/types.ts @@ -17,6 +17,10 @@ export type AgentOptions = { profileBaseDir?: string | undefined; provider?: string | undefined; model?: string | undefined; + /** Custom API key (overrides environment variable) */ + apiKey?: string | undefined; + /** Custom base URL for the provider endpoint */ + baseUrl?: string | undefined; /** System prompt, if profileId is set will auto-construct from profile */ systemPrompt?: string | undefined; thinkingLevel?: ThinkingLevel | undefined;