/** * Agent 配置加载 * * 从 agents.json 的 defaults.model 读取全局模型配置 * 从 providers.json 读取 API Key 和 baseUrl */ import type { AgentConfig, ProviderType } from '../types/index.js'; import { providerRegistry, resolveApiKey } from '../provider/index.js'; import { agentRegistry } from '../agent/registry.js'; import type { AgentModelConfig } from '../agent/types.js'; /** * 配置错误异常 * * 当配置缺失或无效时抛出,用于替代 process.exit() * 允许调用方优雅地处理错误并向用户显示友好提示 */ export class ConfigurationError extends Error { constructor( message: string, public readonly provider: string, public readonly missingKey: 'apiKey' | 'provider' ) { super(message); this.name = 'ConfigurationError'; } } // 默认模型配置 const DEFAULT_MODELS: Record = { anthropic: 'claude-sonnet-4-20250514', deepseek: 'deepseek-chat', openai: 'gpt-4o', }; // 默认系统提示词 const DEFAULT_SYSTEM_PROMPT = `你是一个运行在终端中的 AI 编程助手。你可以帮助用户: - 读取和写入文件 - 执行 bash 命令 - 搜索代码和文件 - 回答编程问题 使用工具时请注意: 1. 在修改文件前,先读取文件内容 2. 执行可能有风险的命令前,先向用户确认 3. 给出清晰、简洁的回答 重要的工具使用规则: - 创建或修改文件时,必须使用 write_file 或 edit_file 工具,不要使用 bash 命令(如 cat、echo 等) - write_file 和 edit_file 工具集成了代码诊断功能,可以在写入后自动检查代码错误 - bash 工具仅用于运行命令、安装依赖、执行脚本等操作,不要用于文件内容的创建和修改 当前工作目录: ${process.cwd()} 操作系统: ${process.platform}`; /** * 查找第一个已配置 API Key 的 Provider * 作为未配置 defaults.model 时的 fallback */ function findFirstConfiguredProvider(): ProviderType | null { const providers: ProviderType[] = ['anthropic', 'openai', 'deepseek']; for (const providerId of providers) { const config = providerRegistry.getConfig(providerId); const apiKey = resolveApiKey(config); if (apiKey) { return providerId; } } return null; } /** * 加载 Agent 配置 * * 配置来源优先级: * 1. agents.json 的 defaults.model(用户配置的全局默认模型) * 2. 第一个已配置 API Key 的 Provider(fallback) * 3. 抛出 ConfigurationError(无可用配置) */ export function loadConfig(): AgentConfig { // 1. 从 AgentRegistry 获取 defaults.model 配置 const globalConfig = agentRegistry.getGlobalConfig(); const modelConfig = globalConfig?.model; // 2. 确定 provider(优先使用配置,否则使用第一个有 API Key 的 provider) let provider: ProviderType; let model: string; if (modelConfig?.provider) { // 用户配置了 defaults.model.provider provider = modelConfig.provider; model = modelConfig.model || DEFAULT_MODELS[provider]; } else { // 未配置,尝试找第一个有 API Key 的 provider const fallbackProvider = findFirstConfiguredProvider(); if (!fallbackProvider) { throw new ConfigurationError( '请先在 Providers 面板配置一个模型提供商的 API Key', 'unknown', 'apiKey' ); } provider = fallbackProvider; model = DEFAULT_MODELS[provider]; } // 3. 从 ProviderRegistry 获取 API Key 和 baseUrl const providerConfig = providerRegistry.getConfig(provider); const apiKey = resolveApiKey(providerConfig); if (!apiKey) { throw new ConfigurationError( `请在 Providers 面板配置 ${provider} 的 API Key`, provider, 'apiKey' ); } // 4. 获取模型的 contextWindow const modelInfo = providerRegistry.getModelInfo(provider, model); const contextWindow = modelInfo?.contextWindow; // 5. 获取 maxTokens(从 modelConfig 或默认值) const maxTokens = modelConfig?.maxTokens || 4096; return { provider, apiKey, model, maxTokens, systemPrompt: DEFAULT_SYSTEM_PROMPT, baseUrl: providerConfig?.baseUrl, contextWindow, }; } // Vision 配置接口 export interface VisionConfig { provider: ProviderType; apiKey: string; model: string; baseUrl?: string; } /** * 加载 Vision 配置 * * 从 agents.json 的 defaults.vision 读取配置 * Vision 用于图片理解,当主模型不支持 vision 时使用 */ export function loadVisionConfig(): VisionConfig | null { // 从 AgentRegistry 获取 defaults.vision 配置 const globalConfig = agentRegistry.getGlobalConfig(); const visionConfig = globalConfig?.vision; if (!visionConfig?.provider) { // 未配置 vision,返回 null return null; } // 从 ProviderRegistry 获取 API Key const providerConfig = providerRegistry.getConfig(visionConfig.provider); const apiKey = resolveApiKey(providerConfig); if (!apiKey) { // 没有 API Key,返回 null return null; } // 确定模型(使用配置的或默认值) const model = visionConfig.model || DEFAULT_MODELS[visionConfig.provider]; return { provider: visionConfig.provider, apiKey, model, baseUrl: providerConfig?.baseUrl, }; } /** * 从 AgentModelConfig 构建完整配置 * 用于 Task 工具动态构建 Agent 配置 */ export function buildConfigFromModelConfig(modelConfig: AgentModelConfig): AgentConfig | null { if (!modelConfig.provider) { return null; } const providerConfig = providerRegistry.getConfig(modelConfig.provider); const apiKey = resolveApiKey(providerConfig); if (!apiKey) { return null; } const model = modelConfig.model || DEFAULT_MODELS[modelConfig.provider]; const modelInfo = providerRegistry.getModelInfo(modelConfig.provider, model); return { provider: modelConfig.provider, apiKey, model, maxTokens: modelConfig.maxTokens || 4096, systemPrompt: DEFAULT_SYSTEM_PROMPT, baseUrl: providerConfig?.baseUrl, contextWindow: modelInfo?.contextWindow, }; }