refactor(config): 移除环境变量依赖,统一使用 Provider 配置系统

- 移除 .env.example 文件
- 简化 resolveApiKey 函数,只从配置文件读取 API Key
- 重构 loadConfig/loadVisionConfig/loadSummaryConfig 使用 ProviderRegistry
- 更新测试以 mock Provider 系统
This commit is contained in:
2025-12-14 21:29:36 +08:00
parent c9d0cbce5b
commit 9e011476c8
6 changed files with 143 additions and 451 deletions
+30 -112
View File
@@ -2,15 +2,13 @@ import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import type { AgentConfig, ProviderType } from '../types/index.js';
import { providerRegistry, resolveApiKey } from '../provider/index.js';
const CONFIG_DIR = path.join(os.homedir(), '.ai-terminal-assistant');
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
interface StoredConfig {
provider?: ProviderType;
apiKey?: string;
deepseekApiKey?: string;
openaiApiKey?: string;
model?: string;
maxTokens?: number;
tavilyApiKey?: string;
@@ -19,15 +17,11 @@ interface StoredConfig {
// Vision 配置
visionProvider?: ProviderType;
visionModel?: string;
/** Vision 专用的 API Key(可选,不设置则使用对应 provider 的 key */
visionApiKey?: string;
/** Vision 专用的 Base URL(用于 OpenAI 兼容的 Vision 服务) */
visionBaseUrl?: string;
// Summary 配置(用于对话压缩摘要生成)
summaryProvider?: ProviderType;
summaryModel?: string;
/** Summary 专用的 API Key(可选,不设置则使用对应 provider 的 key */
summaryApiKey?: string;
/** Summary 专用的 Base URL(用于 OpenAI 兼容的 Summary 服务) */
summaryBaseUrl?: string;
}
@@ -106,63 +100,33 @@ export function getConfig(): StoredConfig {
// 加载配置
export function loadConfig(): AgentConfig {
// 从环境变量获取
const provider = (process.env.AI_PROVIDER as ProviderType) || 'anthropic';
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
const deepseekApiKey = process.env.DEEPSEEK_API_KEY;
const openaiApiKey = process.env.OPENAI_API_KEY;
const model = process.env.AI_MODEL;
const maxTokens = parseInt(process.env.AI_MAX_TOKENS || '4096', 10);
const baseUrl = process.env.AI_BASE_URL;
// 从配置文件读取
let storedConfig: StoredConfig = {};
if (fs.existsSync(CONFIG_FILE)) {
try {
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
storedConfig = JSON.parse(content);
} catch {
// 忽略解析错误
}
}
const storedConfig = getConfig();
// 确定最终的 provider
const finalProvider = storedConfig.provider || provider;
const finalProvider = storedConfig.provider || 'anthropic';
// 根据 provider 获取对应的 API Key
let finalApiKey: string | undefined;
if (finalProvider === 'anthropic') {
finalApiKey = anthropicApiKey || storedConfig.apiKey;
} else if (finalProvider === 'deepseek') {
finalApiKey = deepseekApiKey || storedConfig.deepseekApiKey;
} else if (finalProvider === 'openai') {
finalApiKey = openaiApiKey || storedConfig.openaiApiKey;
}
// 通过 ProviderRegistry 获取 API Key
const providerConfig = providerRegistry.getConfig(finalProvider);
const finalApiKey = resolveApiKey(providerConfig);
if (!finalApiKey) {
const envVarMap: Record<ProviderType, string> = {
anthropic: 'ANTHROPIC_API_KEY',
deepseek: 'DEEPSEEK_API_KEY',
openai: 'OPENAI_API_KEY',
};
const envVar = envVarMap[finalProvider];
console.error(`❌ 错误: 未设置 ${envVar}`);
console.error(`请设置环境变量: export ${envVar}=your-api-key`);
console.error('或运行: ai-assist init 进行初始化配置');
console.error(`❌ 错误: 未配置 API Key`);
console.error(`请在设置中配置 ${finalProvider} 的 API Key`);
process.exit(1);
}
// 确定模型
const finalModel = model || storedConfig.model || DEFAULT_MODELS[finalProvider];
const finalModel = storedConfig.model || DEFAULT_MODELS[finalProvider];
// 确定 baseUrl(环境变量优先)
const finalBaseUrl = baseUrl || storedConfig.baseUrl;
// 确定 baseUrl
const finalBaseUrl = storedConfig.baseUrl || providerConfig?.baseUrl;
return {
provider: finalProvider,
apiKey: finalApiKey,
model: finalModel,
maxTokens: storedConfig.maxTokens || maxTokens,
maxTokens: storedConfig.maxTokens || 4096,
systemPrompt: DEFAULT_SYSTEM_PROMPT,
baseUrl: finalBaseUrl,
};
@@ -171,38 +135,18 @@ export function loadConfig(): AgentConfig {
/**
* 加载 Vision 配置
* Vision 用于图片理解,当主模型不支持 vision 时使用
* 优先级:环境变量 > 配置文件 > 默认使用 Anthropic Claude
* 通过 ProviderRegistry 获取 API Key
*/
export function loadVisionConfig(): VisionConfig | null {
// 从环境变量获取
const visionProvider = process.env.VISION_PROVIDER as ProviderType | undefined;
const visionModel = process.env.VISION_MODEL;
const visionApiKey = process.env.VISION_API_KEY;
const visionBaseUrl = process.env.VISION_BASE_URL;
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
const deepseekApiKey = process.env.DEEPSEEK_API_KEY;
const openaiApiKey = process.env.OPENAI_API_KEY;
// 从配置文件读取
const storedConfig = getConfig();
// 确定 vision provider(默认使用 anthropic,因为 Claude 支持 vision
const finalProvider = visionProvider || storedConfig.visionProvider || 'anthropic';
const finalProvider = storedConfig.visionProvider || 'anthropic';
// 获取 Vision 专用的 API Key(优先级:环境变量 > 配置文件专用 key > provider 对应的 key
let finalApiKey: string | undefined;
finalApiKey = visionApiKey || storedConfig.visionApiKey;
// 如果没有专用 key,回退到对应 provider 的 key
if (!finalApiKey) {
if (finalProvider === 'anthropic') {
finalApiKey = anthropicApiKey || storedConfig.apiKey;
} else if (finalProvider === 'deepseek') {
finalApiKey = deepseekApiKey || storedConfig.deepseekApiKey;
} else if (finalProvider === 'openai') {
finalApiKey = openaiApiKey || storedConfig.openaiApiKey;
}
}
// 通过 ProviderRegistry 获取 API Key
const providerConfig = providerRegistry.getConfig(finalProvider);
const finalApiKey = resolveApiKey(providerConfig);
// 如果没有 API Key,返回 null
if (!finalApiKey) {
@@ -210,10 +154,10 @@ export function loadVisionConfig(): VisionConfig | null {
}
// 确定模型
const finalModel = visionModel || storedConfig.visionModel || DEFAULT_VISION_MODELS[finalProvider];
const finalModel = storedConfig.visionModel || DEFAULT_VISION_MODELS[finalProvider];
// 确定 baseUrlVision 专用)
const finalBaseUrl = visionBaseUrl || storedConfig.visionBaseUrl;
// 确定 baseUrl
const finalBaseUrl = storedConfig.visionBaseUrl || providerConfig?.baseUrl;
return {
provider: finalProvider,
@@ -226,52 +170,26 @@ export function loadVisionConfig(): VisionConfig | null {
/**
* 加载 Summary 配置
* Summary 用于对话压缩时生成摘要,推荐使用成本较低的小模型
* 优先级:环境变量 > 配置文件 > null(使用主模型)
* 通过 ProviderRegistry 获取 API Key
*/
export function loadSummaryConfig(): SummaryConfig | null {
// 从环境变量获取
const summaryProvider = process.env.SUMMARY_PROVIDER as ProviderType | undefined;
const summaryModel = process.env.SUMMARY_MODEL;
const summaryApiKey = process.env.SUMMARY_API_KEY;
const summaryBaseUrl = process.env.SUMMARY_BASE_URL;
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
const deepseekApiKey = process.env.DEEPSEEK_API_KEY;
const openaiApiKey = process.env.OPENAI_API_KEY;
// 从配置文件读取
const storedConfig = getConfig();
// 如果没有任何 summary 相关配置,返回 null(使用主模型)
const hasSummaryConfig =
summaryProvider ||
summaryModel ||
summaryApiKey ||
storedConfig.summaryProvider ||
storedConfig.summaryModel ||
storedConfig.summaryApiKey;
const hasSummaryConfig = storedConfig.summaryProvider || storedConfig.summaryModel;
if (!hasSummaryConfig) {
return null;
}
// 确定 summary provider(默认使用主配置的 provider
const mainProvider = (process.env.AI_PROVIDER as ProviderType) || storedConfig.provider || 'anthropic';
const finalProvider = summaryProvider || storedConfig.summaryProvider || mainProvider;
const mainProvider = storedConfig.provider || 'anthropic';
const finalProvider = storedConfig.summaryProvider || mainProvider;
// 获取 Summary 专用的 API Key(优先级:环境变量 > 配置文件专用 key > provider 对应的 key
let finalApiKey: string | undefined;
finalApiKey = summaryApiKey || storedConfig.summaryApiKey;
// 如果没有专用 key,回退到对应 provider 的 key
if (!finalApiKey) {
if (finalProvider === 'anthropic') {
finalApiKey = anthropicApiKey || storedConfig.apiKey;
} else if (finalProvider === 'deepseek') {
finalApiKey = deepseekApiKey || storedConfig.deepseekApiKey;
} else if (finalProvider === 'openai') {
finalApiKey = openaiApiKey || storedConfig.openaiApiKey;
}
}
// 通过 ProviderRegistry 获取 API Key
const providerConfig = providerRegistry.getConfig(finalProvider);
const finalApiKey = resolveApiKey(providerConfig);
// 如果没有 API Key,返回 null
if (!finalApiKey) {
@@ -279,10 +197,10 @@ export function loadSummaryConfig(): SummaryConfig | null {
}
// 确定模型
const finalModel = summaryModel || storedConfig.summaryModel || DEFAULT_SUMMARY_MODELS[finalProvider];
const finalModel = storedConfig.summaryModel || DEFAULT_SUMMARY_MODELS[finalProvider];
// 确定 baseUrlSummary 专用)
const finalBaseUrl = summaryBaseUrl || storedConfig.summaryBaseUrl;
// 确定 baseUrl
const finalBaseUrl = storedConfig.summaryBaseUrl || providerConfig?.baseUrl;
return {
provider: finalProvider,