"use strict"; import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { homedir } from "node:os"; /** 平台端点映射 */ const PROVIDER_BASE_URLS: Record = { // 智谱(bigmodel.cn) ZHIPU: "https://open.bigmodel.cn/api/paas/v4/", ZHIPU_CODING: "https://open.bigmodel.cn/api/coding/paas/v4/", // Z.AI ZAI: "https://api.z.ai/api/paas/v4/", ZAI_CODING: "https://api.z.ai/api/coding/paas/v4/", }; /** 配置文件路径:~/.pi/agent/pi-zai-vision.json */ const CONFIG_FILE = join(homedir(), ".pi", "agent", "pi-zai-vision.json"); export interface VisionConfig { apiKey: string; baseUrl: string; mode: string; model: string; thinking: boolean; temperature: number; topP: number; maxTokens: number; timeoutSecs: number; maxImageSizeMB: number; maxVideoSizeMB: number; } interface ConfigFileContent { apiKey?: string; mode?: string; model?: string; thinking?: boolean; temperature?: number; topP?: number; maxTokens?: number; timeoutSecs?: number; maxImageSizeMB?: number; maxVideoSizeMB?: number; } /** 读取 ~/.pi/agent/pi-zai-vision.json(如果存在) */ function readConfigFile(): ConfigFileContent | null { try { if (!existsSync(CONFIG_FILE)) return null; const raw = readFileSync(CONFIG_FILE, "utf-8"); return JSON.parse(raw) as ConfigFileContent; } catch { return null; } } /** 优先级:环境变量 > 配置文件 > 默认值 */ function resolve(key: string, envKey: string, fileVal: unknown, defaultVal: string): string; function resolve(key: string, envKey: string, fileVal: unknown, defaultVal: number): number; function resolve(key: string, envKey: string, fileVal: unknown, defaultVal: boolean): boolean; function resolve( _key: string, envKey: string, fileVal: unknown, defaultVal: string | number | boolean ): string | number | boolean { // 1. 环境变量最高优先级 const envVal = process.env[envKey]; if (envVal !== undefined && envVal !== "") { if (typeof defaultVal === "number") return Number(envVal); if (typeof defaultVal === "boolean") return envVal !== "false" && envVal !== "0"; return envVal; } // 2. 配置文件 if (fileVal !== undefined && fileVal !== null) return fileVal as any; // 3. 默认值 return defaultVal; } /** * 加载配置。 * 优先级:环境变量 > ~/.pi/agent/pi-zai-vision.json > 默认值。 * Z_AI_API_KEY 至少需要在环境变量或配置文件中设置一处。 */ export function loadConfig(): VisionConfig { const file = readConfigFile(); const apiKey = resolve("apiKey", "Z_AI_API_KEY", file?.apiKey, ""); if (!apiKey) { throw new Error( "Z_AI_API_KEY is required. Set it via environment variable or in ~/.pi/agent/pi-zai-vision.json:\n" + ' {"apiKey": "your-api-key", "mode": "ZAI"}' ); } const mode = (resolve("mode", "Z_AI_MODE", file?.mode, "ZAI") as string).toUpperCase(); const baseUrl = PROVIDER_BASE_URLS[mode]; if (!baseUrl) { throw new Error( `Unknown mode: "${mode}". Valid values: ZHIPU, ZHIPU_CODING, ZAI, ZAI_CODING.` ); } return { apiKey: apiKey as string, baseUrl, mode, model: resolve("model", "Z_AI_MODEL", file?.model, "glm-4.6v") as string, thinking: resolve("thinking", "Z_AI_THINKING", file?.thinking, true) as boolean, temperature: resolve("temperature", "Z_AI_TEMPERATURE", file?.temperature, 0.8) as number, topP: resolve("topP", "Z_AI_TOP_P", file?.topP, 0.6) as number, maxTokens: resolve("maxTokens", "Z_AI_MAX_TOKENS", file?.maxTokens, 32768) as number, timeoutSecs: resolve("timeoutSecs", "Z_AI_TIMEOUT", file?.timeoutSecs, 300) as number, maxImageSizeMB: resolve("maxImageSizeMB", "Z_AI_MAX_IMAGE_SIZE_MB", file?.maxImageSizeMB, 5) as number, maxVideoSizeMB: resolve("maxVideoSizeMB", "Z_AI_MAX_VIDEO_SIZE_MB", file?.maxVideoSizeMB, 8) as number, }; } let _config: VisionConfig | null = null; /** 获取缓存的配置(懒加载) */ export function getConfig(): VisionConfig { if (!_config) { _config = loadConfig(); } return _config; } /** 重置缓存(用于测试或重新加载配置) */ export function resetConfig(): void { _config = null; } /** 返回配置文件路径,方便用户知晓位置 */ export function getConfigFilePath(): string { return CONFIG_FILE; }