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 模型配置界面 - 更新相关测试
233 lines
7.4 KiB
TypeScript
233 lines
7.4 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
|
|
// Mock provider registry
|
|
vi.mock('../../../src/provider/index.js', () => ({
|
|
providerRegistry: {
|
|
getInfo: vi.fn(),
|
|
getConfig: vi.fn(),
|
|
getModelInfo: vi.fn(),
|
|
},
|
|
resolveApiKey: vi.fn(),
|
|
}));
|
|
|
|
// Mock agent registry
|
|
vi.mock('../../../src/agent/registry.js', () => ({
|
|
agentRegistry: {
|
|
getGlobalConfig: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
import { providerRegistry, resolveApiKey } from '../../../src/provider/index.js';
|
|
import { agentRegistry } from '../../../src/agent/registry.js';
|
|
import { loadConfig, loadVisionConfig, ConfigurationError } from '../../../src/utils/config.js';
|
|
|
|
describe('Config - 配置管理', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('loadConfig - 加载完整配置', () => {
|
|
it('从 AgentRegistry defaults.model 获取配置', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({
|
|
model: {
|
|
provider: 'openai',
|
|
model: 'gpt-4o',
|
|
maxTokens: 8192,
|
|
},
|
|
});
|
|
vi.mocked(providerRegistry.getConfig).mockReturnValue({
|
|
id: 'openai',
|
|
baseUrl: 'https://api.openai.com/v1',
|
|
});
|
|
vi.mocked(resolveApiKey).mockReturnValue('openai-api-key');
|
|
|
|
const config = loadConfig();
|
|
|
|
expect(config.provider).toBe('openai');
|
|
expect(config.model).toBe('gpt-4o');
|
|
expect(config.apiKey).toBe('openai-api-key');
|
|
expect(config.maxTokens).toBe(8192);
|
|
expect(config.baseUrl).toBe('https://api.openai.com/v1');
|
|
});
|
|
|
|
it('未配置 defaults.model 时使用第一个有 API Key 的 provider', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue(null);
|
|
// anthropic 没有 API Key
|
|
vi.mocked(providerRegistry.getConfig).mockImplementation((id) => {
|
|
if (id === 'openai') {
|
|
return { id: 'openai' };
|
|
}
|
|
return undefined;
|
|
});
|
|
vi.mocked(resolveApiKey).mockImplementation((config) => {
|
|
if (config?.id === 'openai') {
|
|
return 'openai-fallback-key';
|
|
}
|
|
return undefined;
|
|
});
|
|
|
|
const config = loadConfig();
|
|
|
|
expect(config.provider).toBe('openai');
|
|
expect(config.apiKey).toBe('openai-fallback-key');
|
|
expect(config.model).toBe('gpt-4o'); // 默认模型
|
|
});
|
|
|
|
it('没有任何 provider 配置 API Key 时抛出错误', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue(null);
|
|
vi.mocked(providerRegistry.getConfig).mockReturnValue(undefined);
|
|
vi.mocked(resolveApiKey).mockReturnValue(undefined);
|
|
|
|
expect(() => loadConfig()).toThrow(ConfigurationError);
|
|
expect(() => loadConfig()).toThrow('请先在 Providers 面板配置一个模型提供商的 API Key');
|
|
});
|
|
|
|
it('配置了 provider 但没有 API Key 时抛出错误', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({
|
|
model: {
|
|
provider: 'deepseek',
|
|
},
|
|
});
|
|
vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'deepseek' });
|
|
vi.mocked(resolveApiKey).mockReturnValue(undefined);
|
|
|
|
expect(() => loadConfig()).toThrow(ConfigurationError);
|
|
expect(() => loadConfig()).toThrow('请在 Providers 面板配置 deepseek 的 API Key');
|
|
});
|
|
|
|
it('包含系统提示词', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue(null);
|
|
vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'anthropic' });
|
|
vi.mocked(resolveApiKey).mockReturnValue('test-key');
|
|
|
|
const config = loadConfig();
|
|
|
|
expect(config.systemPrompt).toBeDefined();
|
|
expect(config.systemPrompt).toContain('终端');
|
|
});
|
|
|
|
it('默认 maxTokens 为 4096', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({
|
|
model: {
|
|
provider: 'anthropic',
|
|
},
|
|
});
|
|
vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'anthropic' });
|
|
vi.mocked(resolveApiKey).mockReturnValue('test-key');
|
|
|
|
const config = loadConfig();
|
|
|
|
expect(config.maxTokens).toBe(4096);
|
|
});
|
|
|
|
it('从 ProviderConfig 获取 baseUrl', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({
|
|
model: {
|
|
provider: 'openai',
|
|
},
|
|
});
|
|
vi.mocked(providerRegistry.getConfig).mockReturnValue({
|
|
id: 'openai',
|
|
baseUrl: 'https://custom.api.com/v1',
|
|
});
|
|
vi.mocked(resolveApiKey).mockReturnValue('test-key');
|
|
|
|
const config = loadConfig();
|
|
|
|
expect(config.baseUrl).toBe('https://custom.api.com/v1');
|
|
});
|
|
|
|
it('获取模型的 contextWindow', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({
|
|
model: {
|
|
provider: 'anthropic',
|
|
model: 'claude-sonnet-4-20250514',
|
|
},
|
|
});
|
|
vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'anthropic' });
|
|
vi.mocked(providerRegistry.getModelInfo).mockReturnValue({
|
|
id: 'claude-sonnet-4-20250514',
|
|
name: 'Claude Sonnet 4',
|
|
contextWindow: 200000,
|
|
});
|
|
vi.mocked(resolveApiKey).mockReturnValue('test-key');
|
|
|
|
const config = loadConfig();
|
|
|
|
expect(config.contextWindow).toBe(200000);
|
|
});
|
|
});
|
|
|
|
describe('loadVisionConfig - Vision 配置', () => {
|
|
it('返回 null 当没有配置 defaults.vision', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue(null);
|
|
|
|
const visionConfig = loadVisionConfig();
|
|
|
|
expect(visionConfig).toBeNull();
|
|
});
|
|
|
|
it('返回 null 当 vision provider 没有 API Key', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({
|
|
vision: {
|
|
provider: 'openai',
|
|
model: 'gpt-4o',
|
|
},
|
|
});
|
|
vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'openai' });
|
|
vi.mocked(resolveApiKey).mockReturnValue(undefined);
|
|
|
|
const visionConfig = loadVisionConfig();
|
|
|
|
expect(visionConfig).toBeNull();
|
|
});
|
|
|
|
it('从 defaults.vision 获取 Vision 配置', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({
|
|
vision: {
|
|
provider: 'openai',
|
|
model: 'gpt-4o',
|
|
},
|
|
});
|
|
vi.mocked(providerRegistry.getConfig).mockReturnValue({
|
|
id: 'openai',
|
|
baseUrl: 'https://api.openai.com/v1',
|
|
});
|
|
vi.mocked(resolveApiKey).mockReturnValue('vision-api-key');
|
|
|
|
const visionConfig = loadVisionConfig();
|
|
|
|
expect(visionConfig).not.toBeNull();
|
|
expect(visionConfig?.provider).toBe('openai');
|
|
expect(visionConfig?.apiKey).toBe('vision-api-key');
|
|
expect(visionConfig?.model).toBe('gpt-4o');
|
|
expect(visionConfig?.baseUrl).toBe('https://api.openai.com/v1');
|
|
});
|
|
|
|
it('使用默认模型当 vision.model 未指定', () => {
|
|
vi.mocked(agentRegistry.getGlobalConfig).mockReturnValue({
|
|
vision: {
|
|
provider: 'anthropic',
|
|
},
|
|
});
|
|
vi.mocked(providerRegistry.getConfig).mockReturnValue({ id: 'anthropic' });
|
|
vi.mocked(resolveApiKey).mockReturnValue('test-key');
|
|
|
|
const visionConfig = loadVisionConfig();
|
|
|
|
expect(visionConfig?.model).toBe('claude-sonnet-4-20250514');
|
|
});
|
|
});
|
|
|
|
describe('ConfigurationError', () => {
|
|
it('包含 provider 和 missingKey 信息', () => {
|
|
const error = new ConfigurationError('Test error', 'openai', 'apiKey');
|
|
|
|
expect(error.name).toBe('ConfigurationError');
|
|
expect(error.message).toBe('Test error');
|
|
expect(error.provider).toBe('openai');
|
|
expect(error.missingKey).toBe('apiKey');
|
|
});
|
|
});
|
|
});
|