refactor(config): 移除环境变量依赖,统一使用 Provider 配置系统
- 移除 .env.example 文件 - 简化 resolveApiKey 函数,只从配置文件读取 API Key - 重构 loadConfig/loadVisionConfig/loadSummaryConfig 使用 ProviderRegistry - 更新测试以 mock Provider 系统
This commit is contained in:
@@ -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];
|
||||
|
||||
// 确定 baseUrl(Vision 专用)
|
||||
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];
|
||||
|
||||
// 确定 baseUrl(Summary 专用)
|
||||
const finalBaseUrl = summaryBaseUrl || storedConfig.summaryBaseUrl;
|
||||
// 确定 baseUrl
|
||||
const finalBaseUrl = storedConfig.summaryBaseUrl || providerConfig?.baseUrl;
|
||||
|
||||
return {
|
||||
provider: finalProvider,
|
||||
|
||||
Reference in New Issue
Block a user