@yeepay/coderocket-mcp
Version:
CodeRocket MCP - Independent AI-powered code review server for Model Context Protocol
322 lines • 12.3 kB
JavaScript
import { GoogleGenerativeAI } from '@google/generative-ai';
import Anthropic from '@anthropic-ai/sdk';
import { ConfigManager } from '../config/ConfigManager.js';
import { AIServiceError, logger } from '../logger.js';
/**
* 智能AI服务管理器
*
* 功能:
* 1. 多AI服务支持(Gemini、ClaudeCode)
* 2. 智能故障转移
* 3. 自动重试机制
* 4. 服务状态监控
*/
export class SmartAIManager {
geminiClient;
claudeClient;
serviceStatus = new Map();
constructor() {
// 不在构造函数中初始化客户端,而是在需要时懒加载
// 这样可以确保 ConfigManager 已正确加载配置
}
/**
* 初始化AI客户端(懒加载)
*/
async initializeClients() {
try {
// 强制重新初始化 ConfigManager 以确保最新配置
await ConfigManager.initialize(true);
this.initializeGeminiClient();
this.initializeClaudeClient();
}
catch (error) {
logger.error('ConfigManager 重新初始化失败', error instanceof Error ? error : new Error(String(error)));
}
}
/**
* 初始化 Gemini 客户端
*/
initializeGeminiClient() {
const geminiKey = ConfigManager.getAPIKey('gemini');
if (geminiKey) {
try {
this.geminiClient = new GoogleGenerativeAI(geminiKey);
// 初始化时先标记为可用,实际可用性通过 testService 检测
this.serviceStatus.set('gemini', true);
logger.debug('Gemini 客户端初始化成功');
}
catch (error) {
logger.warn('Gemini 客户端初始化失败', {
error: error instanceof Error ? error.message : String(error),
});
this.serviceStatus.set('gemini', false);
}
}
else {
// 没有 API 密钥时标记为不可用
this.serviceStatus.set('gemini', false);
}
}
/**
* 初始化 Claude 客户端
*/
initializeClaudeClient() {
const claudeKey = ConfigManager.getAPIKey('claudecode');
if (claudeKey) {
try {
this.claudeClient = new Anthropic({ apiKey: claudeKey });
this.serviceStatus.set('claudecode', true);
logger.debug('Claude 客户端初始化成功');
}
catch (error) {
logger.warn('Claude 客户端初始化失败', {
error: error instanceof Error ? error.message : String(error),
});
this.serviceStatus.set('claudecode', false);
}
}
else {
this.serviceStatus.set('claudecode', false);
}
}
/**
* 智能调用AI服务(带故障转移)
*/
async intelligentCall(primaryService, prompt) {
const maxRetries = ConfigManager.getMaxRetries();
const timeout = ConfigManager.getTimeout() * 1000; // 转换为毫秒
const autoSwitch = ConfigManager.isAutoSwitchEnabled();
// 首先尝试主要服务(带重试)
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await this.callWithTimeout(primaryService, prompt, timeout);
return { result, usedService: primaryService };
}
catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
logger.warn(`主要AI服务 ${primaryService} 调用失败 (尝试 ${attempt}/${maxRetries})`, {
error: lastError.message,
});
// 如果不是最后一次尝试,等待一小段时间再重试
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
// 如果启用自动切换,尝试备用服务(带重试)
if (autoSwitch) {
const fallbackService = this.getFallbackService(primaryService);
if (fallbackService && this.isServiceAvailable(fallbackService)) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
logger.info(`切换到备用AI服务: ${fallbackService} (尝试 ${attempt}/${maxRetries})`);
const result = await this.callWithTimeout(fallbackService, prompt, timeout);
return { result, usedService: fallbackService };
}
catch (fallbackError) {
const error = fallbackError instanceof Error
? fallbackError
: new Error(String(fallbackError));
logger.warn(`备用AI服务 ${fallbackService} 调用失败 (尝试 ${attempt}/${maxRetries})`, {
error: error.message,
});
// 如果不是最后一次尝试,等待一小段时间再重试
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
}
}
// 如果所有服务都失败,抛出错误
throw new AIServiceError(`所有AI服务都不可用。主要服务 ${primaryService} 错误: ${lastError?.message || '未知错误'}`, primaryService);
}
/**
* 带超时的AI服务调用
*/
async callWithTimeout(service, prompt, timeout) {
return new Promise(async (resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new AIServiceError(`AI服务调用超时 (${timeout}ms)`, service));
}, timeout);
try {
const result = await this.callAIService(service, prompt);
clearTimeout(timeoutId);
resolve(result);
}
catch (error) {
clearTimeout(timeoutId);
reject(error);
}
});
}
/**
* 调用指定的AI服务
*/
async callAIService(service, prompt) {
if (!this.isServiceAvailable(service)) {
throw new AIServiceError(`AI服务 ${service} 不可用`, service);
}
switch (service) {
case 'gemini':
return await this.callGemini(prompt);
case 'claudecode':
return await this.callClaude(prompt);
default:
throw new AIServiceError(`不支持的AI服务: ${service}`, service);
}
}
/**
* 调用 Gemini API
*/
async callGemini(prompt) {
if (!this.geminiClient) {
throw new AIServiceError('Gemini 客户端未初始化', 'gemini');
}
try {
const model = this.geminiClient.getGenerativeModel({
model: ConfigManager.get('GEMINI_MODEL', 'gemini-1.5-flash'),
});
const result = await model.generateContent(prompt);
const response = await result.response;
const text = response.text();
if (!text) {
throw new AIServiceError('Gemini 返回空响应', 'gemini');
}
return text;
}
catch (error) {
throw new AIServiceError(`Gemini API 调用失败: ${error instanceof Error ? error.message : String(error)}`, 'gemini');
}
}
/**
* 调用 Claude API
*/
async callClaude(prompt) {
if (!this.claudeClient) {
throw new AIServiceError('Claude 客户端未初始化', 'claudecode');
}
try {
const response = await this.claudeClient.messages.create({
model: ConfigManager.get('CLAUDE_MODEL', 'claude-3-sonnet-20240229'),
max_tokens: 4000,
messages: [{ role: 'user', content: prompt }],
});
// 检查响应内容是否存在且非空
if (!response.content || response.content.length === 0) {
throw new AIServiceError('Claude 返回空内容数组', 'claudecode');
}
const content = response.content[0];
if (content.type !== 'text') {
throw new AIServiceError('Claude 返回非文本响应', 'claudecode');
}
return content.text;
}
catch (error) {
throw new AIServiceError(`Claude API 调用失败: ${error instanceof Error ? error.message : String(error)}`, 'claudecode');
}
}
/**
* 获取备用服务
*/
getFallbackService(primaryService) {
const fallbackMap = {
gemini: 'claudecode',
claudecode: 'gemini',
};
return fallbackMap[primaryService] || null;
}
/**
* 检查服务是否可用
*/
isServiceAvailable(service) {
// 检查状态标记
const statusOk = this.serviceStatus.get(service) === true;
// 检查客户端是否真的存在
let clientOk = false;
switch (service) {
case 'gemini':
clientOk = !!this.geminiClient;
break;
case 'claudecode':
clientOk = !!this.claudeClient;
break;
}
return statusOk && clientOk;
}
/**
* 获取所有服务状态
*/
getServiceStatus() {
return Object.fromEntries(this.serviceStatus);
}
/**
* 测试服务连接
*/
async testService(service) {
// 首先检查服务是否已配置(有 API 密钥)
const apiKey = ConfigManager.getAPIKey(service);
if (!apiKey) {
this.serviceStatus.set(service, false);
logger.debug(`AI服务 ${service} 未配置 API 密钥`);
return false;
}
try {
// 使用简单的测试提示词,设置较短的超时时间
await this.callAIService(service, '测试');
this.serviceStatus.set(service, true);
logger.debug(`AI服务 ${service} 连接测试成功`);
return true;
}
catch (error) {
// 连接失败时不修改状态,保持配置状态
// 这样用户仍然可以看到服务已配置,只是当前不可用
logger.warn(`AI服务 ${service} 连接测试失败,但服务已配置`, {
error: error instanceof Error ? error.message : String(error),
});
return false;
}
}
/**
* 刷新所有服务状态
*/
async refreshServiceStatus() {
const services = ['gemini', 'claudecode'];
// 首先重新初始化客户端以确保使用最新配置
await this.initializeClients();
// 基于配置状态初始化服务状态
services.forEach(service => {
const apiKey = ConfigManager.getAPIKey(service);
if (apiKey) {
// 有 API 密钥则标记为已配置(可能可用)
this.serviceStatus.set(service, true);
}
else {
// 没有 API 密钥则标记为不可用
this.serviceStatus.set(service, false);
}
});
// 然后异步测试实际连接状态(不阻塞响应)
Promise.all(services.map(service => this.testService(service))).catch(error => {
logger.warn('服务状态测试过程中出现错误', {
error: error instanceof Error ? error.message : String(error),
});
});
}
/**
* 获取可用的服务列表
*/
getAvailableServices() {
return Array.from(this.serviceStatus.entries())
.filter(([, available]) => available)
.map(([service]) => service);
}
/**
* 重新初始化客户端(用于配置更新后)
*/
reinitialize() {
this.initializeClients();
}
}
//# sourceMappingURL=SmartAIManager.js.map