9376887995
- 移除 config.json,所有配置统一从 agents.json 和 providers.json 读取 - config-loader.ts 从全局目录 ~/.ai-terminal-assistant/ 加载配置 - loadConfig() 从 agentRegistry.getGlobalConfig() 获取 defaults.model - 添加 loadVisionConfig() 支持 Vision 模型配置 - Tavily API Key 仅从环境变量读取 - UI AgentDefaultsEditor 添加 Vision 模型配置界面 - 更新相关测试
213 lines
6.0 KiB
TypeScript
213 lines
6.0 KiB
TypeScript
/**
|
||
* 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<ProviderType, string> = {
|
||
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,
|
||
};
|
||
}
|