feat(provider): 添加独立的 Provider 模块管理模型提供商
实现可扩展的 Provider 系统,支持动态注册自定义提供商: Core 模块 (packages/core/src/provider/): - types.ts: Provider 相关类型定义 - builtin/: 内置提供商 (Anthropic, OpenAI, DeepSeek) - registry.ts: ProviderRegistry 单例类 - config.ts: 配置持久化 (~/.ai-terminal-assistant/providers.json) - utils.ts: 连接测试等工具函数 Server API (packages/server/src/routes/providers.ts): - GET/POST/PUT/DELETE /providers 提供商管理 - POST /providers/:id/test 连接测试 - 自定义模型管理接口 Frontend (packages/ui/): - ProvidersPanel 组件用于管理提供商 - API client 函数和类型定义 主要功能: - 支持动态注册 OpenAI 兼容服务 (Ollama, vLLM 等) - 每个提供商独立的 API Key 配置 - 预设模型列表 + 自定义模型输入 - 连接测试验证
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Provider Configuration Persistence
|
||||
*
|
||||
* 提供商配置持久化
|
||||
*/
|
||||
|
||||
import { existsSync } from 'node:fs';
|
||||
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import type { ProvidersConfigFile, CustomProviderDefinition, ProviderConfig } from './types.js';
|
||||
|
||||
/** 配置目录名 */
|
||||
const CONFIG_DIR = '.ai-terminal-assistant';
|
||||
|
||||
/** 配置文件名 */
|
||||
const CONFIG_FILE = 'providers.json';
|
||||
|
||||
/**
|
||||
* 获取配置目录路径
|
||||
*/
|
||||
export function getConfigDir(workdir?: string): string {
|
||||
return workdir ? join(workdir, CONFIG_DIR) : join(homedir(), CONFIG_DIR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置文件路径
|
||||
*/
|
||||
export function getConfigPath(workdir?: string): string {
|
||||
return join(getConfigDir(workdir), CONFIG_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载提供商配置
|
||||
*/
|
||||
export async function loadProvidersConfig(workdir?: string): Promise<ProvidersConfigFile> {
|
||||
const configPath = getConfigPath(workdir);
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
return { providers: {}, configs: {} };
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await readFile(configPath, 'utf-8');
|
||||
const config = JSON.parse(content) as ProvidersConfigFile;
|
||||
return {
|
||||
providers: config.providers ?? {},
|
||||
configs: config.configs ?? {},
|
||||
};
|
||||
} catch {
|
||||
// 解析失败返回空配置
|
||||
return { providers: {}, configs: {} };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存提供商配置
|
||||
*/
|
||||
export async function saveProvidersConfig(
|
||||
config: ProvidersConfigFile,
|
||||
workdir?: string
|
||||
): Promise<void> {
|
||||
const configDir = getConfigDir(workdir);
|
||||
const configPath = getConfigPath(workdir);
|
||||
|
||||
// 确保目录存在
|
||||
if (!existsSync(configDir)) {
|
||||
await mkdir(configDir, { recursive: true });
|
||||
}
|
||||
|
||||
await writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提供商的 API Key
|
||||
* 优先从环境变量获取,其次从配置获取
|
||||
*/
|
||||
export function resolveApiKey(config?: ProviderConfig, envVar?: string): string | undefined {
|
||||
// 优先使用配置中指定的环境变量
|
||||
if (config?.apiKeyEnvVar) {
|
||||
const key = process.env[config.apiKeyEnvVar];
|
||||
if (key) return key;
|
||||
}
|
||||
|
||||
// 其次使用默认环境变量
|
||||
if (envVar) {
|
||||
const key = process.env[envVar];
|
||||
if (key) return key;
|
||||
}
|
||||
|
||||
// 最后使用直接配置的 apiKey
|
||||
return config?.apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并自定义提供商定义和配置
|
||||
*/
|
||||
export function mergeProviderConfig(
|
||||
definition: CustomProviderDefinition,
|
||||
config?: ProviderConfig
|
||||
): CustomProviderDefinition & { enabled: boolean } {
|
||||
return {
|
||||
...definition,
|
||||
// 配置可以覆盖 baseUrl
|
||||
baseUrl: config?.baseUrl ?? definition.baseUrl,
|
||||
// 合并模型列表
|
||||
models: [...(definition.models ?? []), ...(config?.customModels ?? [])],
|
||||
enabled: config?.enabled ?? true,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user