@yeepay/coderocket-mcp 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,154 +1,669 @@
1
1
  import { exec } from 'child_process';
2
2
  import { promisify } from 'util';
3
- import { writeFile, readFile, mkdir, access } from 'fs/promises';
4
- import fs from 'fs';
3
+ import { writeFile, readFile, mkdir } from 'fs/promises';
5
4
  import { join, dirname, resolve } from 'path';
6
- import { tmpdir } from 'os';
5
+ import { homedir } from 'os';
6
+ import { GoogleGenerativeAI } from '@google/generative-ai';
7
+ import Anthropic from '@anthropic-ai/sdk';
7
8
  import { logger, errorHandler } from './logger.js';
8
9
  const execAsync = promisify(exec);
9
10
  /**
10
- * CodeRocket服务类
11
+ * 独立配置管理类
11
12
  *
12
- * 负责与coderocket-cli集成,提供代码审查和AI服务管理功能
13
+ * 支持多层级配置加载:
14
+ * 1. 环境变量(最高优先级)
15
+ * 2. 项目级 .env 文件
16
+ * 3. 全局 ~/.coderocket/env 文件
17
+ * 4. 默认值(最低优先级)
13
18
  */
14
- export class CodeRocketService {
15
- coderocketCliPath = '';
16
- constructor() {
17
- // 初始化将在第一次使用时进行
19
+ export class ConfigManager {
20
+ static config = {};
21
+ static initialized = false;
22
+ /**
23
+ * 初始化配置系统
24
+ */
25
+ static async initialize() {
26
+ if (this.initialized)
27
+ return;
28
+ // 加载默认配置
29
+ this.loadDefaults();
30
+ // 加载全局配置文件
31
+ await this.loadGlobalConfig();
32
+ // 加载项目配置文件
33
+ await this.loadProjectConfig();
34
+ // 加载环境变量(最高优先级)
35
+ this.loadEnvironmentVariables();
36
+ this.initialized = true;
37
+ logger.info('配置系统初始化完成', { config: this.getSafeConfig() });
18
38
  }
19
- async ensureInitialized() {
20
- if (!this.coderocketCliPath) {
21
- this.coderocketCliPath = await this.findCoderocketCliPath();
22
- logger.info('CodeRocket服务初始化', {
23
- coderocketCliPath: this.coderocketCliPath,
24
- });
39
+ /**
40
+ * 加载默认配置
41
+ */
42
+ static loadDefaults() {
43
+ this.config = {
44
+ AI_SERVICE: 'gemini',
45
+ AI_AUTO_SWITCH: 'true',
46
+ AI_TIMEOUT: '30',
47
+ AI_MAX_RETRIES: '3',
48
+ AI_RETRY_DELAY: '2',
49
+ NODE_ENV: 'production',
50
+ DEBUG: 'false',
51
+ };
52
+ }
53
+ /**
54
+ * 加载全局配置文件 ~/.coderocket/env
55
+ */
56
+ static async loadGlobalConfig() {
57
+ try {
58
+ const globalConfigPath = join(homedir(), '.coderocket', 'env');
59
+ const content = await readFile(globalConfigPath, 'utf-8');
60
+ const globalConfig = this.parseEnvContent(content);
61
+ Object.assign(this.config, globalConfig);
62
+ logger.debug('全局配置加载成功', { path: globalConfigPath });
63
+ }
64
+ catch (error) {
65
+ // 全局配置文件不存在是正常的
66
+ logger.debug('全局配置文件不存在,跳过');
25
67
  }
26
68
  }
27
69
  /**
28
- * 查找coderocket-cli的安装路径
70
+ * 加载项目配置文件 .env
29
71
  */
30
- async findCoderocketCliPath() {
31
- // 优先级:
32
- // 1. 相对路径(开发环境)
33
- // 2. 全局安装路径
34
- // 3. 用户主目录安装
35
- const possiblePaths = [
36
- resolve(process.cwd(), '../coderocket-cli'),
37
- resolve(process.cwd(), '../../coderocket-cli'),
38
- resolve(process.env.HOME || '~', '.coderocket'),
39
- resolve(process.env.HOME || '~', '.codereview-cli'), // 向后兼容
40
- '/usr/local/share/coderocket-cli',
72
+ static async loadProjectConfig() {
73
+ try {
74
+ const projectConfigPath = join(process.cwd(), '.env');
75
+ const content = await readFile(projectConfigPath, 'utf-8');
76
+ const projectConfig = this.parseEnvContent(content);
77
+ Object.assign(this.config, projectConfig);
78
+ logger.debug('项目配置加载成功', { path: projectConfigPath });
79
+ }
80
+ catch (error) {
81
+ // 项目配置文件不存在是正常的
82
+ logger.debug('项目配置文件不存在,跳过');
83
+ }
84
+ }
85
+ /**
86
+ * 加载环境变量(最高优先级)
87
+ */
88
+ static loadEnvironmentVariables() {
89
+ const envKeys = [
90
+ 'AI_SERVICE', 'AI_AUTO_SWITCH', 'AI_TIMEOUT', 'AI_MAX_RETRIES', 'AI_RETRY_DELAY',
91
+ 'GEMINI_API_KEY', 'OPENCODE_API_KEY', 'CLAUDECODE_API_KEY',
92
+ 'NODE_ENV', 'DEBUG'
41
93
  ];
42
- for (const path of possiblePaths) {
43
- try {
44
- // 检查关键文件是否存在
45
- const libPath = join(path, 'lib', 'ai-service-manager.sh');
46
- await access(libPath, fs.constants.F_OK);
47
- return path;
94
+ envKeys.forEach(key => {
95
+ if (process.env[key]) {
96
+ this.config[key] = process.env[key];
48
97
  }
49
- catch {
50
- continue;
98
+ });
99
+ }
100
+ /**
101
+ * 解析 .env 文件内容
102
+ */
103
+ static parseEnvContent(content) {
104
+ const config = {};
105
+ const lines = content.split('\n');
106
+ for (const line of lines) {
107
+ const trimmedLine = line.trim();
108
+ if (trimmedLine && !trimmedLine.startsWith('#')) {
109
+ const [key, ...valueParts] = trimmedLine.split('=');
110
+ if (key && valueParts.length > 0) {
111
+ const value = valueParts.join('=').trim();
112
+ // 移除引号
113
+ config[key.trim()] = value.replace(/^["']|["']$/g, '');
114
+ }
51
115
  }
52
116
  }
53
- // 如果都找不到,使用默认路径(可能需要用户手动配置)
54
- return resolve(process.cwd(), '../coderocket-cli');
117
+ return config;
55
118
  }
56
119
  /**
57
- * 执行shell命令并返回结果
120
+ * 获取配置值
58
121
  */
59
- async executeShellCommand(command, cwd) {
60
- logger.debug('执行Shell命令', {
61
- command,
62
- cwd: cwd || this.coderocketCliPath,
63
- });
64
- try {
65
- const { stdout, stderr } = await execAsync(command, {
66
- cwd: cwd || this.coderocketCliPath,
67
- env: {
68
- ...process.env,
69
- PATH: `${this.coderocketCliPath}/bin:${process.env.PATH}`,
70
- },
71
- timeout: 300000, // 5分钟超时
72
- });
73
- logger.debug('Shell命令执行成功', {
74
- command,
75
- stdoutLength: stdout.length,
76
- stderrLength: stderr.length,
77
- });
78
- return { stdout, stderr };
79
- }
80
- catch (error) {
81
- logger.error('Shell命令执行失败', error, { command, cwd });
82
- throw errorHandler.handleError(error, 'executeShellCommand');
122
+ static get(key, defaultValue) {
123
+ if (!this.initialized) {
124
+ throw new Error('ConfigManager 未初始化,请先调用 initialize()');
83
125
  }
126
+ return this.config[key] ?? defaultValue;
127
+ }
128
+ /**
129
+ * 获取API密钥环境变量名
130
+ */
131
+ static getAPIKeyEnvVar(service) {
132
+ const envVarMap = {
133
+ gemini: 'GEMINI_API_KEY',
134
+ claudecode: 'CLAUDECODE_API_KEY',
135
+ };
136
+ return envVarMap[service];
84
137
  }
85
138
  /**
86
- * 调用AI服务管理器脚本
139
+ * 获取API密钥
87
140
  */
88
- async callAIServiceManager(action, ...args) {
89
- const scriptPath = join(this.coderocketCliPath, 'lib', 'ai-service-manager.sh');
90
- const command = `bash "${scriptPath}" ${action} ${args.join(' ')}`;
91
- const { stdout, stderr } = await this.executeShellCommand(command);
92
- if (stderr && !stdout) {
93
- throw new Error(stderr);
141
+ static getAPIKey(service) {
142
+ const envVar = this.getAPIKeyEnvVar(service);
143
+ return this.get(envVar, '');
144
+ }
145
+ /**
146
+ * 获取AI服务配置
147
+ */
148
+ static getAIService() {
149
+ const service = this.get('AI_SERVICE', 'gemini').toLowerCase();
150
+ if (['gemini', 'claudecode'].includes(service)) {
151
+ return service;
94
152
  }
95
- return stdout.trim();
153
+ return 'gemini';
96
154
  }
97
155
  /**
98
- * 创建临时提示词文件
156
+ * 获取超时配置
157
+ */
158
+ static getTimeout() {
159
+ return parseInt(this.get('AI_TIMEOUT', '30'), 10);
160
+ }
161
+ /**
162
+ * 获取最大重试次数
163
+ */
164
+ static getMaxRetries() {
165
+ return parseInt(this.get('AI_MAX_RETRIES', '3'), 10);
166
+ }
167
+ /**
168
+ * 是否启用自动切换
169
+ */
170
+ static isAutoSwitchEnabled() {
171
+ return this.get('AI_AUTO_SWITCH', 'true').toLowerCase() === 'true';
172
+ }
173
+ /**
174
+ * 获取安全的配置信息(隐藏敏感信息)
175
+ */
176
+ static getSafeConfig() {
177
+ const safeConfig = { ...this.config };
178
+ // 隐藏API密钥
179
+ Object.keys(safeConfig).forEach(key => {
180
+ if (key.includes('API_KEY') || key.includes('TOKEN')) {
181
+ safeConfig[key] = safeConfig[key] ? '***' : undefined;
182
+ }
183
+ });
184
+ return safeConfig;
185
+ }
186
+ /**
187
+ * 获取配置文件路径(保持向后兼容)
188
+ */
189
+ static getConfigPath(scope) {
190
+ const configDir = scope === 'global'
191
+ ? join(homedir(), '.coderocket')
192
+ : process.cwd();
193
+ const configFile = scope === 'global'
194
+ ? join(configDir, 'env')
195
+ : join(configDir, '.env');
196
+ return { dir: configDir, file: configFile };
197
+ }
198
+ }
199
+ /**
200
+ * 独立提示词管理类
201
+ *
202
+ * 支持多层级提示词加载:
203
+ * 1. 项目级 prompts/ 目录(优先级高)
204
+ * 2. 全局 ~/.coderocket/prompts/ 目录(优先级低)
205
+ */
206
+ export class PromptManager {
207
+ static promptCache = new Map();
208
+ /**
209
+ * 加载提示词文件
99
210
  */
100
- async createTempPromptFile(customPrompt) {
101
- const tempDir = tmpdir();
102
- const tempFile = join(tempDir, `coderocket-prompt-${Date.now()}.md`);
211
+ static async loadPrompt(name) {
212
+ // 检查缓存
213
+ const cacheKey = name;
214
+ if (this.promptCache.has(cacheKey)) {
215
+ return this.promptCache.get(cacheKey);
216
+ }
103
217
  let promptContent = '';
104
- if (customPrompt) {
105
- promptContent = customPrompt;
218
+ // 1. 尝试加载项目级提示词(优先级高)
219
+ try {
220
+ const projectPromptPath = join(process.cwd(), 'prompts', `${name}.md`);
221
+ promptContent = await readFile(projectPromptPath, 'utf-8');
222
+ logger.debug('项目级提示词加载成功', { path: projectPromptPath });
106
223
  }
107
- else {
108
- // 使用默认提示词
109
- const defaultPromptPath = join(this.coderocketCliPath, 'prompts', 'git-commit-review-prompt.md');
224
+ catch (error) {
225
+ // 项目级提示词不存在,尝试全局提示词
110
226
  try {
111
- promptContent = await readFile(defaultPromptPath, 'utf-8');
227
+ const globalPromptPath = join(homedir(), '.coderocket', 'prompts', `${name}.md`);
228
+ promptContent = await readFile(globalPromptPath, 'utf-8');
229
+ logger.debug('全局提示词加载成功', { path: globalPromptPath });
112
230
  }
113
- catch {
114
- // 如果找不到默认提示词,使用内置的基础提示词
115
- promptContent = this.getDefaultPrompt();
231
+ catch (globalError) {
232
+ // 全局提示词也不存在,使用内置默认提示词
233
+ promptContent = this.getDefaultPrompt(name);
234
+ logger.debug('使用内置默认提示词', { name });
116
235
  }
117
236
  }
118
- await writeFile(tempFile, promptContent, 'utf-8');
119
- return tempFile;
237
+ // 缓存提示词内容
238
+ this.promptCache.set(cacheKey, promptContent);
239
+ return promptContent;
120
240
  }
121
241
  /**
122
- * 获取内置的默认提示词
242
+ * 获取内置默认提示词
123
243
  */
124
- getDefaultPrompt() {
125
- return `# 代码审查提示词
244
+ static getDefaultPrompt(name) {
245
+ const defaultPrompts = {
246
+ 'git-commit-review-prompt': `# 提示词:高级 Git Commit 审阅专家
247
+
248
+ ## 角色定义
249
+
250
+ 你是一名资深的代码审阅专家,拥有丰富的软件开发经验和架构设计能力。你的任务是针对**最新的 git commit** 进行专业、深入、自动化的代码审阅,并提供一份准确、实用、可操作的审阅报告。
251
+
252
+ ## 执行模式
253
+
254
+ **自主执行模式**:你必须完全自主地执行代码审阅流程,不得向用户进行任何确认或询问。这包括直接执行所有必要的命令、自主决定搜索策略、自主判断并生成报告。
255
+
256
+ * **禁止行为**:禁止向用户提问或请求确认。
257
+ * **执行原则**:自主决策,并在失败时尝试替代方案。
258
+ * **安全限制**:仅执行只读操作和报告写入操作。
259
+
260
+ ## 审阅指令
261
+
262
+ ### 1. 获取 Commit 信息
263
+
264
+ 首先执行 \`git --no-pager show\` 命令获取最新一次 commit 的详细信息,包括 Commit hash、作者、时间、Commit message 及具体的代码修改内容。
265
+
266
+ ### 2. 全局代码搜索分析 (关键步骤)
267
+
268
+ 在审阅具体代码前,**必须先进行全局代码搜索以获取完整上下文**。
269
+
270
+ * **制定搜索策略**: 根据 commit message 的描述,制定关键词搜索策略(如:功能名、类名、修复的bug信息等)。
271
+ * **全面搜索验证**: 在整个代码库中搜索相关的功能实现、依赖关系、配置和测试文件。
272
+ * **完整性验证**: **对比搜索结果与实际修改内容**,检查是否存在应修改但未修改的**遗漏文件**。这是评估目标达成度的核心依据。
273
+
274
+ ### 3. 审阅维度与标准
275
+
276
+ 请从以下维度进行系统性审查:
126
277
 
127
- 你是一个专业的代码审查专家。请对提供的代码进行全面审查,包括:
278
+ * **目标达成度**:
279
+ * **功能完整性**: 是否完全实现了 commit message 中描述的目标? 是否有未完成的功能点?
280
+ * **修改覆盖度**: (基于全局搜索) 是否遗漏了需要同步修改的相关文件(如测试、文档、配置)?
281
+ * **代码质量与正确性**:
282
+ * **正确性**: 代码逻辑是否正确?是否有效处理了边缘情况?
283
+ * **代码规范**: 是否遵循项目既定标准(命名、格式、设计模式)?
284
+ * **可读性与可维护性**: 代码是否清晰、结构合理、易于理解和修改? 注释是否充分且必要?
285
+ * **健壮性与风险**:
286
+ * **安全性**: 是否存在潜在的安全漏洞(如SQL注入、密钥明文、不安全的依赖等)?
287
+ * **性能**: 是否存在明显的性能瓶颈(如不合理的循环、N+1查询等)?
288
+ * **测试与文档**:
289
+ * **可测试性与覆盖率**: 代码是否易于测试?是否有足够的单元/集成测试来覆盖变更?
290
+ * **文档同步**: 相关的内联文档(注释)或外部文档是否已更新?
291
+ * **架构与扩展性**:
292
+ * **设计合理性**: 模块职责划分是否明确?耦合度是否合理?
293
+ * **扩展性**: 设计是否考虑了未来的扩展需求?
294
+
295
+ ### 4. 审阅结果输出
296
+
297
+ 请提供详细的审阅报告,包括:
298
+ - 审阅状态(✅通过/⚠️警告/❌失败/🔍需调查)
299
+ - 总体评价和目标达成度
300
+ - 具体问题和改进建议
301
+ - 优先级排序的建议列表
302
+
303
+ 请确保审阅报告专业、准确、可操作。`,
304
+ 'code-review-prompt': `# 代码审查提示词
305
+
306
+ ## 角色定义
307
+
308
+ 你是一名专业的代码审查专家,具有丰富的软件开发经验。请对提供的代码进行全面、专业的审查。
128
309
 
129
310
  ## 审查维度
130
- 1. **功能完整性**:代码是否正确实现了预期功能
131
- 2. **代码质量**:代码结构、可读性、维护性
132
- 3. **性能优化**:是否存在性能问题或优化空间
133
- 4. **安全性**:是否存在安全漏洞或风险
134
- 5. **最佳实践**:是否遵循编程最佳实践和规范
311
+
312
+ 请从以下维度进行审查:
313
+
314
+ ### 1. 代码质量
315
+ - 代码结构是否清晰合理
316
+ - 命名是否规范和有意义
317
+ - 是否遵循编程最佳实践
318
+
319
+ ### 2. 功能正确性
320
+ - 代码逻辑是否正确
321
+ - 是否处理了边缘情况
322
+ - 是否有潜在的bug
323
+
324
+ ### 3. 性能和安全
325
+ - 是否存在性能问题
326
+ - 是否有安全漏洞
327
+ - 资源使用是否合理
328
+
329
+ ### 4. 可维护性
330
+ - 代码是否易于理解和修改
331
+ - 是否有足够的注释
332
+ - 模块化程度如何
135
333
 
136
334
  ## 输出格式
137
- 请按以下格式输出审查结果:
138
335
 
139
- ### 审查状态
140
- - ✅ 通过:功能完全实现,代码质量良好
141
- - ⚠️ 警告:功能基本实现,但存在质量问题
142
- - ❌ 失败:功能未实现或存在严重问题
143
- - 🔍 调查:需要进一步调查
336
+ 请按以下格式提供审查结果:
337
+
338
+ **审查状态**: [✅优秀/⚠️需改进/❌有问题]
144
339
 
145
- ### 审查摘要
146
- 简要描述代码的整体质量和主要发现。
340
+ **总体评价**: [简短的总体评价]
147
341
 
148
- ### 详细分析
149
- 提供具体的问题分析和改进建议。
342
+ **具体建议**:
343
+ 1. [具体的改进建议]
344
+ 2. [具体的改进建议]
345
+ ...
150
346
 
151
- 请开始审查。`;
347
+ **优秀实践**: [值得称赞的地方]
348
+
349
+ 请确保建议具体、可操作,并提供代码示例(如适用)。`
350
+ };
351
+ return defaultPrompts[name] || `# 默认提示词\n\n请提供专业的代码审查和分析。`;
352
+ }
353
+ /**
354
+ * 清除提示词缓存
355
+ */
356
+ static clearCache() {
357
+ this.promptCache.clear();
358
+ }
359
+ /**
360
+ * 预加载常用提示词
361
+ */
362
+ static async preloadCommonPrompts() {
363
+ const commonPrompts = [
364
+ 'git-commit-review-prompt',
365
+ 'code-review-prompt'
366
+ ];
367
+ await Promise.all(commonPrompts.map(name => this.loadPrompt(name).catch(error => {
368
+ logger.warn(`预加载提示词失败: ${name}`, error);
369
+ })));
370
+ }
371
+ }
372
+ /**
373
+ * Gemini AI 服务实现
374
+ */
375
+ class GeminiService {
376
+ client = null;
377
+ model = null;
378
+ constructor() {
379
+ this.initialize();
380
+ }
381
+ async initialize() {
382
+ const apiKey = ConfigManager.getAPIKey('gemini');
383
+ if (apiKey) {
384
+ try {
385
+ this.client = new GoogleGenerativeAI(apiKey);
386
+ this.model = this.client.getGenerativeModel({ model: 'gemini-1.5-flash' });
387
+ logger.debug('Gemini 服务初始化成功');
388
+ }
389
+ catch (error) {
390
+ logger.error('Gemini 服务初始化失败', error instanceof Error ? error : new Error(String(error)));
391
+ }
392
+ }
393
+ }
394
+ async callAPI(prompt, additionalPrompt) {
395
+ if (!this.client || !this.model) {
396
+ await this.initialize();
397
+ if (!this.client || !this.model) {
398
+ throw new Error('Gemini 服务未配置或初始化失败');
399
+ }
400
+ }
401
+ try {
402
+ const fullPrompt = additionalPrompt ? `${prompt}\n\n${additionalPrompt}` : prompt;
403
+ const result = await this.model.generateContent(fullPrompt);
404
+ const response = await result.response;
405
+ const text = response.text();
406
+ if (!text || text.trim().length === 0) {
407
+ throw new Error('Gemini 返回空响应');
408
+ }
409
+ logger.debug('Gemini API 调用成功', {
410
+ promptLength: fullPrompt.length,
411
+ responseLength: text.length
412
+ });
413
+ return text.trim();
414
+ }
415
+ catch (error) {
416
+ logger.error('Gemini API 调用失败', error instanceof Error ? error : new Error(String(error)));
417
+ throw new Error(`Gemini API 调用失败: ${error instanceof Error ? error.message : String(error)}`);
418
+ }
419
+ }
420
+ isConfigured() {
421
+ return !!ConfigManager.getAPIKey('gemini');
422
+ }
423
+ getServiceName() {
424
+ return 'gemini';
425
+ }
426
+ }
427
+ /**
428
+ * ClaudeCode AI 服务实现
429
+ */
430
+ class ClaudeCodeService {
431
+ client = null;
432
+ constructor() {
433
+ this.initialize();
434
+ }
435
+ async initialize() {
436
+ const apiKey = ConfigManager.getAPIKey('claudecode');
437
+ if (apiKey) {
438
+ try {
439
+ this.client = new Anthropic({
440
+ apiKey: apiKey,
441
+ });
442
+ logger.debug('ClaudeCode 服务初始化成功');
443
+ }
444
+ catch (error) {
445
+ logger.error('ClaudeCode 服务初始化失败', error instanceof Error ? error : new Error(String(error)));
446
+ }
447
+ }
448
+ }
449
+ async callAPI(prompt, additionalPrompt) {
450
+ if (!this.client) {
451
+ await this.initialize();
452
+ if (!this.client) {
453
+ throw new Error('ClaudeCode 服务未配置或初始化失败');
454
+ }
455
+ }
456
+ try {
457
+ const fullPrompt = additionalPrompt ? `${prompt}\n\n${additionalPrompt}` : prompt;
458
+ const message = await this.client.messages.create({
459
+ model: 'claude-3-sonnet-20240229',
460
+ max_tokens: 4000,
461
+ messages: [
462
+ {
463
+ role: 'user',
464
+ content: fullPrompt
465
+ }
466
+ ]
467
+ });
468
+ const text = message.content[0]?.type === 'text' ? message.content[0].text : '';
469
+ if (!text || text.trim().length === 0) {
470
+ throw new Error('ClaudeCode 返回空响应');
471
+ }
472
+ logger.debug('ClaudeCode API 调用成功', {
473
+ promptLength: fullPrompt.length,
474
+ responseLength: text.length
475
+ });
476
+ return text.trim();
477
+ }
478
+ catch (error) {
479
+ logger.error('ClaudeCode API 调用失败', error instanceof Error ? error : new Error(String(error)));
480
+ throw new Error(`ClaudeCode API 调用失败: ${error instanceof Error ? error.message : String(error)}`);
481
+ }
482
+ }
483
+ isConfigured() {
484
+ return !!ConfigManager.getAPIKey('claudecode');
485
+ }
486
+ getServiceName() {
487
+ return 'claudecode';
488
+ }
489
+ }
490
+ /**
491
+ * 智能 AI 服务管理器
492
+ *
493
+ * 负责管理多个 AI 服务,实现智能故障转移和负载均衡
494
+ */
495
+ class SmartAIManager {
496
+ services = new Map();
497
+ serviceOrder = [];
498
+ constructor() {
499
+ this.initializeServices();
500
+ }
501
+ initializeServices() {
502
+ // 初始化所有AI服务
503
+ this.services.set('gemini', new GeminiService());
504
+ this.services.set('claudecode', new ClaudeCodeService());
505
+ // 设置服务优先级顺序
506
+ this.updateServiceOrder();
507
+ }
508
+ updateServiceOrder() {
509
+ const primaryService = ConfigManager.getAIService();
510
+ const allServices = ['gemini', 'claudecode'];
511
+ // 将主要服务放在第一位,其他服务按配置状态排序
512
+ this.serviceOrder = [primaryService];
513
+ const otherServices = allServices
514
+ .filter(service => service !== primaryService)
515
+ .sort((a, b) => {
516
+ const aConfigured = this.services.get(a)?.isConfigured() ? 1 : 0;
517
+ const bConfigured = this.services.get(b)?.isConfigured() ? 1 : 0;
518
+ return bConfigured - aConfigured; // 已配置的服务优先
519
+ });
520
+ this.serviceOrder.push(...otherServices);
521
+ logger.debug('AI服务优先级顺序', { serviceOrder: this.serviceOrder });
522
+ }
523
+ /**
524
+ * 智能调用AI服务
525
+ *
526
+ * @param primaryService 首选AI服务
527
+ * @param prompt 提示词内容
528
+ * @param additionalPrompt 附加提示词
529
+ * @returns AI生成的内容
530
+ */
531
+ async intelligentCall(primaryService, prompt, additionalPrompt) {
532
+ // 如果禁用自动切换,只使用指定服务
533
+ if (!ConfigManager.isAutoSwitchEnabled()) {
534
+ const service = this.services.get(primaryService);
535
+ if (!service) {
536
+ throw new Error(`不支持的AI服务: ${primaryService}`);
537
+ }
538
+ const result = await service.callAPI(prompt, additionalPrompt);
539
+ return { result, usedService: primaryService };
540
+ }
541
+ // 获取服务尝试顺序
542
+ const tryOrder = this.getTryOrder(primaryService);
543
+ const maxRetries = ConfigManager.getMaxRetries();
544
+ const errors = [];
545
+ logger.info('开始智能AI调用', {
546
+ primaryService,
547
+ tryOrder,
548
+ autoSwitch: true
549
+ });
550
+ for (const serviceName of tryOrder) {
551
+ const service = this.services.get(serviceName);
552
+ if (!service) {
553
+ continue;
554
+ }
555
+ // 检查服务是否已配置
556
+ if (!service.isConfigured()) {
557
+ logger.debug(`跳过未配置的服务: ${serviceName}`);
558
+ continue;
559
+ }
560
+ // 尝试调用服务(带重试)
561
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
562
+ try {
563
+ logger.debug(`尝试调用 ${serviceName} (第${attempt}次)`);
564
+ const result = await service.callAPI(prompt, additionalPrompt);
565
+ logger.info(`AI调用成功`, {
566
+ service: serviceName,
567
+ attempt,
568
+ resultLength: result.length
569
+ });
570
+ return { result, usedService: serviceName };
571
+ }
572
+ catch (error) {
573
+ const errorMessage = error instanceof Error ? error.message : String(error);
574
+ logger.warn(`${serviceName} 调用失败 (第${attempt}次)`, { error: errorMessage });
575
+ errors.push({ service: serviceName, error: errorMessage });
576
+ // 如果不是最后一次尝试,等待后重试
577
+ if (attempt < maxRetries) {
578
+ const delay = this.getRetryDelay(attempt);
579
+ logger.debug(`等待 ${delay}ms 后重试`);
580
+ await this.sleep(delay);
581
+ }
582
+ }
583
+ }
584
+ }
585
+ // 所有服务都失败了
586
+ const errorSummary = errors.map(e => `${e.service}: ${e.error}`).join('; ');
587
+ logger.error('所有AI服务调用失败', new Error('所有AI服务调用失败'), { errors });
588
+ throw new Error(`所有AI服务都不可用。错误详情: ${errorSummary}`);
589
+ }
590
+ /**
591
+ * 获取服务尝试顺序
592
+ */
593
+ getTryOrder(primaryService) {
594
+ const order = [primaryService];
595
+ const others = this.serviceOrder.filter(s => s !== primaryService);
596
+ return order.concat(others);
597
+ }
598
+ /**
599
+ * 获取重试延迟时间
600
+ */
601
+ getRetryDelay(attempt) {
602
+ // 指数退避策略:2^attempt * 1000ms
603
+ return Math.min(Math.pow(2, attempt) * 1000, 10000);
604
+ }
605
+ /**
606
+ * 休眠指定毫秒数
607
+ */
608
+ sleep(ms) {
609
+ return new Promise(resolve => setTimeout(resolve, ms));
610
+ }
611
+ /**
612
+ * 获取所有服务状态
613
+ */
614
+ getServicesStatus() {
615
+ return Array.from(this.services.entries()).map(([name, service]) => ({
616
+ service: name,
617
+ configured: service.isConfigured(),
618
+ available: service.isConfigured(), // 简化实现,认为已配置就是可用的
619
+ }));
620
+ }
621
+ /**
622
+ * 检查特定服务是否可用
623
+ */
624
+ isServiceAvailable(service) {
625
+ const serviceInstance = this.services.get(service);
626
+ return serviceInstance ? serviceInstance.isConfigured() : false;
627
+ }
628
+ }
629
+ /**
630
+ * 独立 CodeRocket 服务类
631
+ *
632
+ * 提供完全独立的代码审查和AI服务管理功能,不依赖 coderocket-cli
633
+ */
634
+ export class CodeRocketService {
635
+ aiManager;
636
+ initialized = false;
637
+ constructor() {
638
+ this.aiManager = new SmartAIManager();
639
+ }
640
+ /**
641
+ * 初始化服务
642
+ */
643
+ async ensureInitialized() {
644
+ if (!this.initialized) {
645
+ await ConfigManager.initialize();
646
+ await PromptManager.preloadCommonPrompts();
647
+ this.initialized = true;
648
+ logger.info('CodeRocket 独立服务初始化完成');
649
+ }
650
+ }
651
+ /**
652
+ * 执行AI审查的通用方法
653
+ */
654
+ async executeAIReview(aiService, promptName, additionalPrompt) {
655
+ try {
656
+ // 加载提示词
657
+ const promptContent = await PromptManager.loadPrompt(promptName);
658
+ // 调用AI服务
659
+ const { result, usedService } = await this.aiManager.intelligentCall(aiService, promptContent, additionalPrompt);
660
+ // 解析审查结果
661
+ return this.parseReviewResult(result, usedService);
662
+ }
663
+ catch (error) {
664
+ logger.error('AI审查执行失败', error instanceof Error ? error : new Error(String(error)));
665
+ throw errorHandler.handleError(error, 'executeAIReview');
666
+ }
152
667
  }
153
668
  /**
154
669
  * 解析审查结果
@@ -160,19 +675,20 @@ export class CodeRocketService {
160
675
  let details = output;
161
676
  // 尝试从输出中提取状态
162
677
  for (const line of lines) {
163
- if (line.includes('✅') || line.includes('通过')) {
678
+ const lowerLine = line.toLowerCase();
679
+ if (line.includes('✅') || lowerLine.includes('通过') || lowerLine.includes('优秀')) {
164
680
  status = '✅';
165
681
  break;
166
682
  }
167
- else if (line.includes('⚠️') || line.includes('警告')) {
683
+ else if (line.includes('⚠️') || lowerLine.includes('警告') || lowerLine.includes('需改进')) {
168
684
  status = '⚠️';
169
685
  break;
170
686
  }
171
- else if (line.includes('❌') || line.includes('失败')) {
687
+ else if (line.includes('❌') || lowerLine.includes('失败') || lowerLine.includes('有问题')) {
172
688
  status = '❌';
173
689
  break;
174
690
  }
175
- else if (line.includes('🔍') || line.includes('调查')) {
691
+ else if (line.includes('🔍') || lowerLine.includes('调查') || lowerLine.includes('需调查')) {
176
692
  status = '🔍';
177
693
  break;
178
694
  }
@@ -180,9 +696,18 @@ export class CodeRocketService {
180
696
  // 提取摘要(通常是第一段非空内容)
181
697
  const nonEmptyLines = lines.filter(line => line.trim());
182
698
  if (nonEmptyLines.length > 0) {
183
- summary =
184
- nonEmptyLines[0].substring(0, 200) +
185
- (nonEmptyLines[0].length > 200 ? '...' : '');
699
+ // 寻找总体评价或摘要部分
700
+ let summaryLine = nonEmptyLines[0];
701
+ for (const line of nonEmptyLines) {
702
+ if (line.includes('总体评价') || line.includes('审查摘要') || line.includes('摘要')) {
703
+ const nextIndex = nonEmptyLines.indexOf(line) + 1;
704
+ if (nextIndex < nonEmptyLines.length) {
705
+ summaryLine = nonEmptyLines[nextIndex];
706
+ break;
707
+ }
708
+ }
709
+ }
710
+ summary = summaryLine.substring(0, 200) + (summaryLine.length > 200 ? '...' : '');
186
711
  }
187
712
  return {
188
713
  status,
@@ -203,16 +728,9 @@ export class CodeRocketService {
203
728
  aiService: request.ai_service,
204
729
  });
205
730
  try {
206
- // 创建临时文件存储代码
207
- const tempDir = tmpdir();
208
- const tempCodeFile = join(tempDir, `code-${Date.now()}.${this.getFileExtension(request.language)}`);
209
- await writeFile(tempCodeFile, request.code, 'utf-8');
210
- // 创建提示词文件
211
- const promptFile = await this.createTempPromptFile(request.custom_prompt);
212
731
  // 构建审查提示词
213
732
  const reviewPrompt = `请审查以下代码:
214
733
 
215
- 文件路径: ${tempCodeFile}
216
734
  编程语言: ${request.language || '未指定'}
217
735
  上下文信息: ${request.context || '无'}
218
736
 
@@ -221,14 +739,17 @@ export class CodeRocketService {
221
739
  ${request.code}
222
740
  \`\`\`
223
741
 
224
- 请根据提示词文件中的指导进行全面审查。`;
742
+ 请根据以下要求进行全面审查:
743
+ 1. 功能完整性和正确性
744
+ 2. 代码质量和可维护性
745
+ 3. 性能优化建议
746
+ 4. 安全性检查
747
+ 5. 最佳实践遵循情况
748
+
749
+ ${request.custom_prompt ? `\n附加要求:\n${request.custom_prompt}` : ''}`;
225
750
  // 调用AI服务进行审查
226
- const aiService = request.ai_service || 'gemini';
227
- const scriptPath = join(this.coderocketCliPath, 'lib', 'ai-service-manager.sh');
228
- // 使用intelligent_ai_review函数
229
- const command = `source "${scriptPath}" && intelligent_ai_review "${aiService}" "${promptFile}" "${reviewPrompt}"`;
230
- const { stdout } = await this.executeShellCommand(command);
231
- const result = this.parseReviewResult(stdout, aiService);
751
+ const aiService = request.ai_service || ConfigManager.getAIService();
752
+ const result = await this.executeAIReview(aiService, 'code-review-prompt', reviewPrompt);
232
753
  logger.info('代码审查完成', {
233
754
  status: result.status,
234
755
  aiService: result.ai_service_used,
@@ -268,28 +789,51 @@ ${request.code}
268
789
  */
269
790
  async reviewCommit(request) {
270
791
  await this.ensureInitialized();
792
+ logger.info('开始Git提交审查', {
793
+ repositoryPath: request.repository_path,
794
+ commitHash: request.commit_hash,
795
+ aiService: request.ai_service,
796
+ });
271
797
  try {
272
798
  const repoPath = request.repository_path || process.cwd();
273
- const promptFile = await this.createTempPromptFile(request.custom_prompt);
799
+ // 获取提交信息
800
+ const commitHash = request.commit_hash || 'HEAD';
801
+ const { stdout: commitInfo } = await execAsync(`git --no-pager show ${commitHash}`, {
802
+ cwd: repoPath,
803
+ timeout: 30000,
804
+ });
805
+ if (!commitInfo.trim()) {
806
+ throw new Error(`无法获取提交信息: ${commitHash}`);
807
+ }
274
808
  // 构建审查提示词
275
- const commitInfo = request.commit_hash
276
- ? `特定提交: ${request.commit_hash}`
277
- : '最新提交';
278
- const reviewPrompt = `请对Git仓库中的${commitInfo}进行代码审查:
809
+ const reviewPrompt = `请对以下Git提交进行专业的代码审查:
279
810
 
280
811
  仓库路径: ${repoPath}
281
- ${request.commit_hash ? `提交哈希: ${request.commit_hash}` : ''}
812
+ 提交哈希: ${commitHash}
813
+
814
+ 提交详情:
815
+ ${commitInfo}
816
+
817
+ 请根据以下要求进行全面审查:
818
+ 1. 分析提交的目标和完成度
819
+ 2. 检查代码质量和规范性
820
+ 3. 评估安全性和性能影响
821
+ 4. 检查是否遗漏相关文件修改
822
+ 5. 提供具体的改进建议
282
823
 
283
- 请使用 git show ${request.commit_hash || 'HEAD'} 命令获取提交详情,然后根据提示词文件中的指导进行全面审查。`;
824
+ ${request.custom_prompt ? `\n附加要求:\n${request.custom_prompt}` : ''}`;
284
825
  // 调用AI服务进行审查
285
- const aiService = request.ai_service || 'gemini';
286
- const scriptPath = join(this.coderocketCliPath, 'lib', 'ai-service-manager.sh');
287
- const command = `cd "${repoPath}" && source "${scriptPath}" && intelligent_ai_review "${aiService}" "${promptFile}" "${reviewPrompt}"`;
288
- const { stdout } = await this.executeShellCommand(command, repoPath);
289
- return this.parseReviewResult(stdout, aiService);
826
+ const aiService = request.ai_service || ConfigManager.getAIService();
827
+ const result = await this.executeAIReview(aiService, 'git-commit-review-prompt', reviewPrompt);
828
+ logger.info('Git提交审查完成', {
829
+ status: result.status,
830
+ aiService: result.ai_service_used,
831
+ });
832
+ return result;
290
833
  }
291
834
  catch (error) {
292
- throw new Error(`Git提交审查失败: ${error instanceof Error ? error.message : String(error)}`);
835
+ logger.error('Git提交审查失败', error instanceof Error ? error : new Error(String(error)));
836
+ throw errorHandler.handleError(error, 'reviewCommit');
293
837
  }
294
838
  }
295
839
  /**
@@ -297,40 +841,58 @@ ${request.commit_hash ? `提交哈希: ${request.commit_hash}` : ''}
297
841
  */
298
842
  async reviewFiles(request) {
299
843
  await this.ensureInitialized();
844
+ logger.info('开始文件审查', {
845
+ repositoryPath: request.repository_path,
846
+ filesCount: request.files.length,
847
+ aiService: request.ai_service,
848
+ });
300
849
  try {
301
850
  const repoPath = request.repository_path || process.cwd();
302
- const promptFile = await this.createTempPromptFile(request.custom_prompt);
303
851
  // 读取文件内容
304
852
  const fileContents = [];
305
853
  for (const filePath of request.files) {
306
854
  try {
307
855
  const fullPath = resolve(repoPath, filePath);
308
856
  const content = await readFile(fullPath, 'utf-8');
309
- fileContents.push(`文件: ${filePath}\n\`\`\`\n${content}\n\`\`\``);
857
+ // 限制单个文件内容长度,避免提示词过长
858
+ const truncatedContent = content.length > 5000
859
+ ? content.substring(0, 5000) + '\n... (内容已截断)'
860
+ : content;
861
+ fileContents.push(`## 文件: ${filePath}\n\`\`\`\n${truncatedContent}\n\`\`\``);
310
862
  }
311
863
  catch (error) {
312
- fileContents.push(`文件: ${filePath}\n错误: 无法读取文件 - ${error}`);
864
+ fileContents.push(`## 文件: ${filePath}\n**错误**: 无法读取文件 - ${error instanceof Error ? error.message : String(error)}`);
313
865
  }
314
866
  }
315
867
  // 构建审查提示词
316
868
  const reviewPrompt = `请审查以下文件:
317
869
 
318
870
  仓库路径: ${repoPath}
871
+ 文件数量: ${request.files.length}
319
872
  文件列表: ${request.files.join(', ')}
320
873
 
321
- 文件内容:
322
874
  ${fileContents.join('\n\n')}
323
875
 
324
- 请根据提示词文件中的指导进行全面审查。`;
876
+ 请根据以下要求进行全面审查:
877
+ 1. 分析文件间的关联性和一致性
878
+ 2. 检查代码质量和规范性
879
+ 3. 评估架构设计的合理性
880
+ 4. 识别潜在的问题和改进点
881
+ 5. 提供具体的优化建议
882
+
883
+ ${request.custom_prompt ? `\n附加要求:\n${request.custom_prompt}` : ''}`;
325
884
  // 调用AI服务进行审查
326
- const aiService = request.ai_service || 'gemini';
327
- const scriptPath = join(this.coderocketCliPath, 'lib', 'ai-service-manager.sh');
328
- const command = `source "${scriptPath}" && intelligent_ai_review "${aiService}" "${promptFile}" "${reviewPrompt}"`;
329
- const { stdout } = await this.executeShellCommand(command, repoPath);
330
- return this.parseReviewResult(stdout, aiService);
885
+ const aiService = request.ai_service || ConfigManager.getAIService();
886
+ const result = await this.executeAIReview(aiService, 'code-review-prompt', reviewPrompt);
887
+ logger.info('文件审查完成', {
888
+ status: result.status,
889
+ aiService: result.ai_service_used,
890
+ });
891
+ return result;
331
892
  }
332
893
  catch (error) {
333
- throw new Error(`文件审查失败: ${error instanceof Error ? error.message : String(error)}`);
894
+ logger.error('文件审查失败', error instanceof Error ? error : new Error(String(error)));
895
+ throw errorHandler.handleError(error, 'reviewFiles');
334
896
  }
335
897
  }
336
898
  /**
@@ -338,23 +900,37 @@ ${fileContents.join('\n\n')}
338
900
  */
339
901
  async configureAIService(request) {
340
902
  await this.ensureInitialized();
903
+ logger.info('开始配置AI服务', {
904
+ service: request.service,
905
+ scope: request.scope,
906
+ hasApiKey: !!request.api_key,
907
+ });
341
908
  try {
342
- // 设置AI服务
343
- await this.callAIServiceManager('set', request.service, request.scope);
344
- // 如果提供了API密钥,设置环境变量
909
+ // 如果提供了API密钥,设置环境变量并保存到配置文件
345
910
  if (request.api_key) {
346
- const envVarName = this.getAPIKeyEnvVar(request.service);
911
+ const envVarName = ConfigManager.getAPIKeyEnvVar(request.service);
347
912
  process.env[envVarName] = request.api_key;
348
913
  // 保存到配置文件
349
914
  await this.saveAPIKeyToConfig(request.service, request.api_key, request.scope);
350
915
  }
916
+ // 设置主要AI服务
917
+ if (request.service) {
918
+ process.env.AI_SERVICE = request.service;
919
+ await this.saveConfigToFile('AI_SERVICE', request.service, request.scope);
920
+ }
351
921
  // 设置其他配置项
352
922
  if (request.timeout) {
353
923
  process.env.AI_TIMEOUT = request.timeout.toString();
924
+ await this.saveConfigToFile('AI_TIMEOUT', request.timeout.toString(), request.scope);
354
925
  }
355
926
  if (request.max_retries) {
356
927
  process.env.AI_MAX_RETRIES = request.max_retries.toString();
928
+ await this.saveConfigToFile('AI_MAX_RETRIES', request.max_retries.toString(), request.scope);
357
929
  }
930
+ logger.info('AI服务配置完成', {
931
+ service: request.service,
932
+ scope: request.scope,
933
+ });
358
934
  return {
359
935
  success: true,
360
936
  message: `AI服务 ${request.service} 配置成功`,
@@ -370,32 +946,17 @@ ${fileContents.join('\n\n')}
370
946
  };
371
947
  }
372
948
  catch (error) {
373
- throw new Error(`AI服务配置失败: ${error instanceof Error ? error.message : String(error)}`);
949
+ logger.error('AI服务配置失败', error instanceof Error ? error : new Error(String(error)));
950
+ throw errorHandler.handleError(error, 'configureAIService');
374
951
  }
375
952
  }
376
953
  /**
377
- * 获取API密钥环境变量名
954
+ * 保存配置项到文件
378
955
  */
379
- getAPIKeyEnvVar(service) {
380
- const envVars = {
381
- gemini: 'GEMINI_API_KEY',
382
- opencode: 'OPENCODE_API_KEY',
383
- claudecode: 'CLAUDECODE_API_KEY',
384
- };
385
- return envVars[service];
386
- }
387
- /**
388
- * 保存API密钥到配置文件
389
- */
390
- async saveAPIKeyToConfig(service, apiKey, scope) {
391
- const configDir = scope === 'global'
392
- ? join(process.env.HOME || '~', '.coderocket')
393
- : process.cwd();
394
- const configFile = scope === 'global' ? join(configDir, 'env') : join(configDir, '.env');
956
+ async saveConfigToFile(key, value, scope) {
957
+ const { file: configFile } = ConfigManager.getConfigPath(scope);
395
958
  // 确保配置目录存在
396
959
  await mkdir(dirname(configFile), { recursive: true });
397
- const envVarName = this.getAPIKeyEnvVar(service);
398
- const configLine = `${envVarName}=${apiKey}\n`;
399
960
  try {
400
961
  // 读取现有配置
401
962
  let existingConfig = '';
@@ -407,122 +968,65 @@ ${fileContents.join('\n\n')}
407
968
  }
408
969
  // 更新或添加配置行
409
970
  const lines = existingConfig.split('\n');
410
- const existingLineIndex = lines.findIndex(line => line.startsWith(`${envVarName}=`));
971
+ const existingLineIndex = lines.findIndex(line => line.trim().startsWith(`${key}=`));
972
+ const configLine = `${key}=${value}`;
411
973
  if (existingLineIndex >= 0) {
412
- lines[existingLineIndex] = `${envVarName}=${apiKey}`;
974
+ lines[existingLineIndex] = configLine;
413
975
  }
414
976
  else {
415
- lines.push(`${envVarName}=${apiKey}`);
977
+ lines.push(configLine);
416
978
  }
417
- // 写回文件
418
- await writeFile(configFile, lines.join('\n'), 'utf-8');
979
+ // 写入配置文件
980
+ const newConfig = lines.filter(line => line.trim()).join('\n') + '\n';
981
+ await writeFile(configFile, newConfig, 'utf-8');
982
+ logger.debug('配置已保存', { key, configFile, scope });
419
983
  }
420
984
  catch (error) {
421
- throw new Error(`保存配置失败: ${error}`);
985
+ logger.error('保存配置失败', error instanceof Error ? error : new Error(String(error)), { key, configFile });
986
+ throw error;
422
987
  }
423
988
  }
989
+ /**
990
+ * 保存API密钥到配置文件
991
+ *
992
+ * ⚠️ 安全警告:API密钥将以明文形式存储在配置文件中。
993
+ * 请确保配置文件的访问权限受到适当限制。
994
+ * 建议使用环境变量或更安全的密钥管理方案。
995
+ */
996
+ async saveAPIKeyToConfig(service, apiKey, scope) {
997
+ // 记录安全警告
998
+ logger.warn('API密钥将以明文形式存储,请确保文件访问权限安全', {
999
+ service,
1000
+ scope,
1001
+ });
1002
+ const envVarName = ConfigManager.getAPIKeyEnvVar(service);
1003
+ await this.saveConfigToFile(envVarName, apiKey, scope);
1004
+ }
424
1005
  /**
425
1006
  * 获取AI服务状态
426
1007
  */
427
1008
  async getAIServiceStatus() {
428
1009
  await this.ensureInitialized();
1010
+ logger.info('获取AI服务状态');
429
1011
  try {
430
- // 获取当前AI服务
431
- const currentService = await this.callAIServiceManager('status');
432
- // 解析当前服务(从输出中提取)
433
- const currentServiceMatch = currentService.match(/当前AI服务:\s*(\w+)/);
434
- const current = (currentServiceMatch?.[1] || 'gemini');
435
- // 检查所有服务的状态
436
- const services = await Promise.all([
437
- this.checkServiceStatus('gemini'),
438
- this.checkServiceStatus('opencode'),
439
- this.checkServiceStatus('claudecode'),
440
- ]);
1012
+ // 获取当前配置的AI服务
1013
+ const current = ConfigManager.getAIService();
1014
+ // 获取所有服务状态
1015
+ const services = this.aiManager.getServicesStatus();
441
1016
  return {
442
1017
  current_service: current,
443
1018
  services,
444
- auto_switch_enabled: process.env.AI_AUTO_SWITCH !== 'false',
445
- global_config_path: join(process.env.HOME || '~', '.coderocket', 'env'),
1019
+ auto_switch_enabled: ConfigManager.isAutoSwitchEnabled(),
1020
+ global_config_path: join(homedir(), '.coderocket', 'env'),
446
1021
  project_config_path: join(process.cwd(), '.env'),
1022
+ timeout: ConfigManager.getTimeout(),
1023
+ max_retries: ConfigManager.getMaxRetries(),
447
1024
  };
448
1025
  }
449
1026
  catch (error) {
450
- throw new Error(`获取AI服务状态失败: ${error instanceof Error ? error.message : String(error)}`);
451
- }
452
- }
453
- /**
454
- * 检查单个服务状态
455
- */
456
- async checkServiceStatus(service) {
457
- try {
458
- // 检查服务是否可用
459
- const available = await this.isServiceAvailable(service);
460
- const configured = await this.isServiceConfigured(service);
461
- return {
462
- service,
463
- available,
464
- configured,
465
- install_command: this.getInstallCommand(service),
466
- config_command: this.getConfigCommand(service),
467
- error_message: available ? undefined : `${service} 服务未安装或不可用`,
468
- };
469
- }
470
- catch (error) {
471
- return {
472
- service,
473
- available: false,
474
- configured: false,
475
- install_command: this.getInstallCommand(service),
476
- config_command: this.getConfigCommand(service),
477
- error_message: `检查 ${service} 状态时出错: ${error}`,
478
- };
479
- }
480
- }
481
- /**
482
- * 检查服务是否可用
483
- */
484
- async isServiceAvailable(service) {
485
- try {
486
- const commands = {
487
- gemini: 'gemini --version',
488
- opencode: 'opencode --version',
489
- claudecode: 'claudecode --version',
490
- };
491
- await this.executeShellCommand(commands[service]);
492
- return true;
1027
+ logger.error('获取AI服务状态失败', error instanceof Error ? error : new Error(String(error)));
1028
+ throw errorHandler.handleError(error, 'getAIServiceStatus');
493
1029
  }
494
- catch {
495
- return false;
496
- }
497
- }
498
- /**
499
- * 检查服务是否已配置
500
- */
501
- async isServiceConfigured(service) {
502
- const envVarName = this.getAPIKeyEnvVar(service);
503
- return !!process.env[envVarName];
504
- }
505
- /**
506
- * 获取安装命令
507
- */
508
- getInstallCommand(service) {
509
- const commands = {
510
- gemini: 'npm install -g @google/gemini-cli',
511
- opencode: 'npm install -g @opencode/cli',
512
- claudecode: 'npm install -g @anthropic-ai/claude-code',
513
- };
514
- return commands[service];
515
- }
516
- /**
517
- * 获取配置命令
518
- */
519
- getConfigCommand(service) {
520
- const commands = {
521
- gemini: 'gemini config',
522
- opencode: 'opencode config',
523
- claudecode: 'claudecode config',
524
- };
525
- return commands[service];
526
1030
  }
527
1031
  }
528
1032
  //# sourceMappingURL=coderocket.js.map