flowmind 1.2.3 → 1.4.0

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +1 -1
  3. package/bin/flowmind.js +69 -16
  4. package/core/ai/base-model.js +47 -28
  5. package/core/ai/model-manager.js +100 -90
  6. package/core/ai/providers/anthropic.js +21 -14
  7. package/core/ai/providers/deepseek.js +12 -2
  8. package/core/ai/providers/ernie.js +2 -2
  9. package/core/ai/providers/glm.js +12 -2
  10. package/core/ai/providers/mimo.js +12 -2
  11. package/core/ai/providers/ollama.js +5 -4
  12. package/core/ai/providers/openai.js +8 -12
  13. package/core/ai/providers/qwen.js +12 -2
  14. package/core/config-manager.js +1 -0
  15. package/core/index.js +183 -6
  16. package/core/learning-engine.js +122 -30
  17. package/mcp/server.js +2 -1
  18. package/package.json +1 -1
  19. package/skills/api-sync/index.js +130 -0
  20. package/skills/archive-change/index.js +104 -0
  21. package/skills/auto-flow/index.js +124 -0
  22. package/skills/code-review/index.js +79 -0
  23. package/skills/code-review-audit/index.js +77 -0
  24. package/skills/data-logic-validation/index.js +108 -0
  25. package/skills/data-validation/index.js +72 -0
  26. package/skills/git-review/index.js +73 -0
  27. package/skills/learning-engine/index.js +50 -0
  28. package/skills/learning-feedback/index.js +83 -0
  29. package/skills/log-audit/index.js +88 -0
  30. package/skills/project-review/index.js +105 -0
  31. package/skills/requirement-analyst/index.js +88 -0
  32. package/skills/resource-bind/index.js +60 -0
  33. package/skills/sls-log-audit/index.js +120 -0
  34. package/skills/yapi-sync-interface/index.js +101 -0
  35. package/skills/yuque-sync-design/index.js +133 -0
  36. package/tui/app.jsx +1 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,46 @@
1
+ # Changelog
2
+
3
+ ## [1.3.0] - 2026-06-26
4
+
5
+ ### Fixed
6
+ - Missing `os` import in config-manager.js
7
+ - MCP Server version hardcoded instead of reading from package.json
8
+ - `config --set` and `skill --set` CLI commands broken (Commander arg parsing)
9
+ - `learn --reset` and `learn --delete` were unimplemented stubs
10
+ - Learning engine race condition on concurrent file writes (added WriteQueue)
11
+ - TUI/Dashboard crash on non-TTY stdin (Ink raw mode error)
12
+
13
+ ### Added
14
+ - All 17 skills now have working `execute()` implementations
15
+ - Global uncaughtException/unhandledRejection handlers
16
+ - Graceful exit handling in TUI via onExit callback
17
+
18
+ ## [1.2.2] - 2026-06-26
19
+
20
+ ### Fixed
21
+ - ChatPanel React import issue with ink-text-input and ink-spinner ESM/CJS interop
22
+
23
+ ## [1.2.0] - 2026-06-26
24
+
25
+ ### Added
26
+ - TUI with split panels, skill browser, dragon display
27
+ - Dashboard with real-time activity feed and stats
28
+ - Event system for cross-component communication
29
+ - Update command for auto-updating flowmind
30
+
31
+ ## [1.1.0] - 2026-06-25
32
+
33
+ ### Added
34
+ - Mainstream AI model providers (GLM, MiMo, Qwen, ERNIE, DeepSeek)
35
+ - AI model integration with rule-based fallback
36
+ - MCP Server for Claude/Codex integration
37
+
38
+ ## [1.0.1] - 2026-06-24
39
+
40
+ ### Added
41
+ - Initial release
42
+ - Skill loading and execution engine
43
+ - Learning engine for user corrections and preferences
44
+ - Honor engine with dragon totem gamification
45
+ - Scene matcher for workflow automation
46
+ - Configuration manager
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
10
10
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
11
- [![Version](https://img.shields.io/badge/version-1.1.0-blue)](CHANGELOG.md)
11
+ [![Version](https://img.shields.io/badge/version-1.3.0-blue)](CHANGELOG.md)
12
12
 
13
13
  [中文](README_CN.md) | [Quick Start](#-quick-start) | [How It Works](#-how-it-works) | [Use Cases](#-use-cases) | [Architecture](#-architecture)
14
14
 
package/bin/flowmind.js CHANGED
@@ -536,11 +536,15 @@ program
536
536
  await fm.importLearnings(data);
537
537
  console.log(chalk.green('✓ Learnings imported successfully'));
538
538
  } else if (options.reset) {
539
- console.log(chalk.yellow('Resetting learnings for:'), options.reset);
540
- // Implementation would go here
539
+ const count = await fm.learning.resetSkill(options.reset);
540
+ console.log(chalk.green(`✓ Reset ${count} learning(s) for skill: ${options.reset}`));
541
541
  } else if (options.delete) {
542
- console.log(chalk.yellow('Deleting learning:'), options.delete);
543
- // Implementation would go here
542
+ const deleted = await fm.learning.deleteRecord(options.delete);
543
+ if (deleted) {
544
+ console.log(chalk.green(`✓ Deleted learning record: ${options.delete}`));
545
+ } else {
546
+ console.log(chalk.yellow(`Record not found: ${options.delete}`));
547
+ }
544
548
  } else {
545
549
  // Default to list
546
550
  const stats = await fm.getStats();
@@ -620,7 +624,7 @@ program
620
624
  .description('View or modify skill configuration')
621
625
  .option('-i, --info', 'Show skill info (default)')
622
626
  .option('-c, --config', 'Show/edit skill configuration')
623
- .option('-s, --set <key> <value>', 'Set config value')
627
+ .option('-s, --set <key>', 'Set config value (value as next argument)')
624
628
  .option('-r, --read', 'Read SKILL.md content')
625
629
  .option('-e, --edit', 'Open SKILL.md in editor')
626
630
  .option('-j, --json', 'Output as JSON (for tool integration)')
@@ -650,13 +654,10 @@ program
650
654
  } else if (options.config) {
651
655
  await showSkillConfig(skill, fm, options.json);
652
656
  } else if (options.set) {
653
- // options.set is the key, need value from next arg
654
- const value = options.set;
655
- const key = options.set;
656
- // Get key and value from command line
657
657
  const args = process.argv.slice(3);
658
- if (args.length >= 2) {
659
- await setSkillConfig(skill, fm, args[0], args[1]);
658
+ const value = args.find(a => !a.startsWith('-'));
659
+ if (value) {
660
+ await setSkillConfig(skill, fm, options.set, value);
660
661
  } else {
661
662
  console.error(chalk.red('Usage: flowmind skill <name> --set <key> <value>'));
662
663
  }
@@ -775,7 +776,7 @@ program
775
776
  .command('config')
776
777
  .description('Manage configuration')
777
778
  .option('-l, --list', 'List configuration')
778
- .option('-s, --set <key> <value>', 'Set configuration value')
779
+ .option('-s, --set <key>', 'Set configuration value (value as next argument)')
779
780
  .option('-g, --get <key>', 'Get configuration value')
780
781
  .action(async (options) => {
781
782
  try {
@@ -785,10 +786,16 @@ program
785
786
  const config = fm.config.getAll();
786
787
  console.log(chalk.cyan('\nFlowMind Configuration:'));
787
788
  console.log(JSON.stringify(config, null, 2));
788
- } else if (options.set && options.value) {
789
- fm.config.set(options.set, options.value);
790
- await fm.config.save();
791
- console.log(chalk.green('✓ Configuration updated'));
789
+ } else if (options.set) {
790
+ const args = process.argv.slice(3);
791
+ const value = args.find(a => !a.startsWith('-'));
792
+ if (value) {
793
+ fm.config.set(options.set, value);
794
+ await fm.config.save();
795
+ console.log(chalk.green('✓ Configuration updated'));
796
+ } else {
797
+ console.error(chalk.red('Usage: flowmind config --set <key> <value>'));
798
+ }
792
799
  } else if (options.get) {
793
800
  const value = fm.config.get(options.get);
794
801
  console.log(value);
@@ -1453,6 +1460,52 @@ program
1453
1460
  }
1454
1461
  });
1455
1462
 
1463
+ // Doctor command - System health check
1464
+ program
1465
+ .command('doctor')
1466
+ .description('Run system health checks and diagnostics')
1467
+ .option('-j, --json', 'Output as JSON')
1468
+ .action(async (options) => {
1469
+ try {
1470
+ const fm = await initFlowMind();
1471
+ const result = await fm.doctor();
1472
+
1473
+ if (options.json) {
1474
+ console.log(JSON.stringify(result, null, 2));
1475
+ return;
1476
+ }
1477
+
1478
+ console.log(chalk.cyan('\nFlowMind Health Check\n'));
1479
+ console.log('─'.repeat(60));
1480
+
1481
+ for (const check of result.checks) {
1482
+ const icon = check.status === 'ok' ? chalk.green('✓')
1483
+ : check.status === 'warning' ? chalk.yellow('⚠')
1484
+ : chalk.red('✗');
1485
+ console.log(` ${icon} ${check.name.padEnd(20)} ${check.message}`);
1486
+ }
1487
+
1488
+ console.log('─'.repeat(60));
1489
+ const { ok, warnings, errors } = result.summary;
1490
+ const summaryLine = [
1491
+ chalk.green(`${ok} ok`),
1492
+ warnings > 0 ? chalk.yellow(`${warnings} warning(s)`) : null,
1493
+ errors > 0 ? chalk.red(`${errors} error(s)`) : null
1494
+ ].filter(Boolean).join(', ');
1495
+ console.log(`\n Result: ${summaryLine}`);
1496
+
1497
+ if (errors > 0) {
1498
+ console.log(chalk.red('\n Some checks failed. Please fix the errors above.'));
1499
+ } else if (warnings > 0) {
1500
+ console.log(chalk.yellow('\n Some warnings detected. Review for potential issues.'));
1501
+ } else {
1502
+ console.log(chalk.green('\n All checks passed!'));
1503
+ }
1504
+ } catch (error) {
1505
+ console.error(chalk.red('Doctor Error:'), error.message);
1506
+ }
1507
+ });
1508
+
1456
1509
  // Update command - Auto-update flowmind
1457
1510
  program
1458
1511
  .command('update')
@@ -8,48 +8,27 @@ class BaseModel {
8
8
  this.name = name;
9
9
  this.config = config;
10
10
  this.initialized = false;
11
+ this.requestTimeout = config.requestTimeout || 30000;
12
+ this.maxRetries = config.maxRetries || 3;
13
+ this.retryDelay = config.retryDelay || 1000;
11
14
  }
12
15
 
13
- /**
14
- * 初始化模型
15
- * @returns {Promise<void>}
16
- */
17
16
  async init() {
18
17
  throw new Error('init() must be implemented by subclass');
19
18
  }
20
19
 
21
- /**
22
- * 发送聊天请求
23
- * @param {Array} messages - 消息数组 [{role, content}]
24
- * @param {Object} options - 请求选项
25
- * @returns {Promise<string>} 模型响应
26
- */
27
20
  async chat(messages, options = {}) {
28
21
  throw new Error('chat() must be implemented by subclass');
29
22
  }
30
23
 
31
- /**
32
- * 发送补全请求
33
- * @param {string} prompt - 提示词
34
- * @param {Object} options - 请求选项
35
- * @returns {Promise<string>} 模型响应
36
- */
37
24
  async complete(prompt, options = {}) {
38
25
  throw new Error('complete() must be implemented by subclass');
39
26
  }
40
27
 
41
- /**
42
- * 检查模型是否可用
43
- * @returns {Promise<boolean>}
44
- */
45
28
  async isAvailable() {
46
29
  throw new Error('isAvailable() must be implemented by subclass');
47
30
  }
48
31
 
49
- /**
50
- * 获取模型信息
51
- * @returns {Object}
52
- */
53
32
  getInfo() {
54
33
  return {
55
34
  name: this.name,
@@ -58,13 +37,53 @@ class BaseModel {
58
37
  };
59
38
  }
60
39
 
61
- /**
62
- * 验证配置
63
- * @returns {boolean}
64
- */
65
40
  validateConfig() {
66
41
  return true;
67
42
  }
43
+
44
+ /**
45
+ * Fetch with retry, timeout, and exponential backoff
46
+ */
47
+ async fetchWithRetry(url, options = {}) {
48
+ const timeout = options.timeout || this.requestTimeout;
49
+ const maxRetries = options.retries || this.maxRetries;
50
+
51
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
52
+ try {
53
+ const controller = new AbortController();
54
+ const timer = setTimeout(() => controller.abort(), timeout);
55
+
56
+ const response = await fetch(url, {
57
+ ...options,
58
+ signal: controller.signal
59
+ });
60
+
61
+ clearTimeout(timer);
62
+
63
+ // Retry on rate limit (429) and server errors (5xx)
64
+ if (response.status === 429 || response.status >= 500) {
65
+ if (attempt < maxRetries) {
66
+ const delay = this.retryDelay * Math.pow(2, attempt);
67
+ await new Promise(r => setTimeout(r, delay));
68
+ continue;
69
+ }
70
+ }
71
+
72
+ return response;
73
+ } catch (error) {
74
+ if (error.name === 'AbortError') {
75
+ if (attempt < maxRetries) continue;
76
+ throw new Error(`Request timeout after ${timeout}ms`);
77
+ }
78
+ if (attempt < maxRetries) {
79
+ const delay = this.retryDelay * Math.pow(2, attempt);
80
+ await new Promise(r => setTimeout(r, delay));
81
+ continue;
82
+ }
83
+ throw error;
84
+ }
85
+ }
86
+ }
68
87
  }
69
88
 
70
89
  module.exports = BaseModel;
@@ -28,6 +28,9 @@ class ModelManager {
28
28
  };
29
29
  this.fallbackToRules = config.fallbackToRules !== false;
30
30
  this.initialized = false;
31
+ this.cache = new Map();
32
+ this.cacheMaxAge = config.cacheMaxAge || 300000; // 5 minutes
33
+ this.cacheMaxSize = config.cacheMaxSize || 100;
31
34
  }
32
35
 
33
36
  /**
@@ -119,30 +122,16 @@ class ModelManager {
119
122
  * @returns {Promise<Object>} 意图分析结果
120
123
  */
121
124
  async understandIntent(input, context = {}) {
122
- if (!this.features.intentUnderstanding) {
123
- return null;
124
- }
125
-
126
- const provider = this.getDefaultProvider();
127
- if (!provider) {
128
- return this._fallback('intentUnderstanding', input, context);
129
- }
125
+ if (!this.features.intentUnderstanding) return null;
130
126
 
131
- try {
127
+ return this._callWithFailover(async (provider) => {
132
128
  const messages = [
133
129
  { role: 'system', content: prompts.intent.system },
134
130
  { role: 'user', content: prompts.intent.user(input, context) }
135
131
  ];
136
-
137
- const response = await provider.chat(messages, {
138
- responseFormat: { type: 'json_object' }
139
- });
140
-
132
+ const response = await provider.chat(messages, { responseFormat: { type: 'json_object' } });
141
133
  return JSON.parse(response);
142
- } catch (error) {
143
- console.warn(`AI intent understanding failed: ${error.message}`);
144
- return this._fallback('intentUnderstanding', input, context);
145
- }
134
+ }, 'intentUnderstanding');
146
135
  }
147
136
 
148
137
  /**
@@ -153,30 +142,16 @@ class ModelManager {
153
142
  * @returns {Promise<Object>} 提取的参数
154
143
  */
155
144
  async extractParameters(input, skillName, skillSchema = {}) {
156
- if (!this.features.parameterExtraction) {
157
- return {};
158
- }
145
+ if (!this.features.parameterExtraction) return {};
159
146
 
160
- const provider = this.getDefaultProvider();
161
- if (!provider) {
162
- return this._fallback('parameterExtraction', input, { skillName });
163
- }
164
-
165
- try {
147
+ return this._callWithFailover(async (provider) => {
166
148
  const messages = [
167
149
  { role: 'system', content: prompts.extraction.system },
168
150
  { role: 'user', content: prompts.extraction.user(input, skillName, skillSchema) }
169
151
  ];
170
-
171
- const response = await provider.chat(messages, {
172
- responseFormat: { type: 'json_object' }
173
- });
174
-
152
+ const response = await provider.chat(messages, { responseFormat: { type: 'json_object' } });
175
153
  return JSON.parse(response);
176
- } catch (error) {
177
- console.warn(`AI parameter extraction failed: ${error.message}`);
178
- return this._fallback('parameterExtraction', input, { skillName });
179
- }
154
+ }, 'parameterExtraction');
180
155
  }
181
156
 
182
157
  /**
@@ -186,30 +161,16 @@ class ModelManager {
186
161
  * @returns {Promise<Object>} 选择的技能
187
162
  */
188
163
  async selectSkill(input, candidates) {
189
- if (!this.features.skillSelection) {
190
- return null;
191
- }
164
+ if (!this.features.skillSelection) return null;
192
165
 
193
- const provider = this.getDefaultProvider();
194
- if (!provider) {
195
- return this._fallback('skillSelection', input, { candidates });
196
- }
197
-
198
- try {
166
+ return this._callWithFailover(async (provider) => {
199
167
  const messages = [
200
168
  { role: 'system', content: prompts.selection.system },
201
169
  { role: 'user', content: prompts.selection.user(input, candidates) }
202
170
  ];
203
-
204
- const response = await provider.chat(messages, {
205
- responseFormat: { type: 'json_object' }
206
- });
207
-
171
+ const response = await provider.chat(messages, { responseFormat: { type: 'json_object' } });
208
172
  return JSON.parse(response);
209
- } catch (error) {
210
- console.warn(`AI skill selection failed: ${error.message}`);
211
- return this._fallback('skillSelection', input, { candidates });
212
- }
173
+ }, 'skillSelection');
213
174
  }
214
175
 
215
176
  /**
@@ -219,26 +180,15 @@ class ModelManager {
219
180
  * @returns {Promise<string>} 摘要文本
220
181
  */
221
182
  async summarizeResult(result, context = {}) {
222
- if (!this.features.resultSummary) {
223
- return null;
224
- }
183
+ if (!this.features.resultSummary) return null;
225
184
 
226
- const provider = this.getDefaultProvider();
227
- if (!provider) {
228
- return this._fallback('resultSummary', result, context);
229
- }
230
-
231
- try {
185
+ return this._callWithFailover(async (provider) => {
232
186
  const messages = [
233
187
  { role: 'system', content: prompts.summary.system },
234
188
  { role: 'user', content: prompts.summary.user(result, context) }
235
189
  ];
236
-
237
190
  return await provider.chat(messages);
238
- } catch (error) {
239
- console.warn(`AI result summary failed: ${error.message}`);
240
- return this._fallback('resultSummary', result, context);
241
- }
191
+ }, 'resultSummary');
242
192
  }
243
193
 
244
194
  /**
@@ -248,42 +198,102 @@ class ModelManager {
248
198
  * @returns {Promise<Object>} 学习分析结果
249
199
  */
250
200
  async analyzeLearningFeedback(input, context = {}) {
251
- if (!this.features.learningFeedback) {
252
- return null;
253
- }
254
-
255
- const provider = this.getDefaultProvider();
256
- if (!provider) {
257
- return this._fallback('learningFeedback', input, context);
258
- }
201
+ if (!this.features.learningFeedback) return null;
259
202
 
260
- try {
203
+ return this._callWithFailover(async (provider) => {
261
204
  const messages = [
262
205
  { role: 'system', content: prompts.learning.system },
263
206
  { role: 'user', content: prompts.learning.user(input, context) }
264
207
  ];
208
+ const response = await provider.chat(messages, { responseFormat: { type: 'json_object' } });
209
+ return JSON.parse(response);
210
+ }, 'learningFeedback');
211
+ }
265
212
 
266
- const response = await provider.chat(messages, {
267
- responseFormat: { type: 'json_object' }
268
- });
213
+ /**
214
+ * Get cached result if available and not expired
215
+ * @private
216
+ */
217
+ _getCached(key) {
218
+ const entry = this.cache.get(key);
219
+ if (!entry) return null;
220
+ if (Date.now() - entry.timestamp > this.cacheMaxAge) {
221
+ this.cache.delete(key);
222
+ return null;
223
+ }
224
+ return entry.value;
225
+ }
269
226
 
270
- return JSON.parse(response);
271
- } catch (error) {
272
- console.warn(`AI learning feedback analysis failed: ${error.message}`);
273
- return this._fallback('learningFeedback', input, context);
227
+ /**
228
+ * Set cache entry, evicting oldest if at capacity
229
+ * @private
230
+ */
231
+ _setCache(key, value) {
232
+ if (this.cache.size >= this.cacheMaxSize) {
233
+ const oldest = this.cache.keys().next().value;
234
+ this.cache.delete(oldest);
235
+ }
236
+ this.cache.set(key, { value, timestamp: Date.now() });
237
+ }
238
+
239
+ /**
240
+ * Clear the AI response cache
241
+ */
242
+ clearCache() {
243
+ this.cache.clear();
244
+ }
245
+
246
+ /**
247
+ * Call AI with automatic failover to other providers
248
+ * @private
249
+ */
250
+ async _callWithFailover(fn, feature) {
251
+ // Try default provider first
252
+ const defaultProvider = this.getDefaultProvider();
253
+ if (defaultProvider) {
254
+ try {
255
+ return await fn(defaultProvider);
256
+ } catch (error) {
257
+ console.warn(`Default provider (${this.defaultProvider}) failed for ${feature}: ${error.message}`);
258
+ }
274
259
  }
260
+
261
+ // Try other providers
262
+ for (const [name, provider] of this.providers) {
263
+ if (name === this.defaultProvider || !provider.initialized) continue;
264
+ try {
265
+ console.warn(`Failing over to provider: ${name} for ${feature}`);
266
+ return await fn(provider);
267
+ } catch (error) {
268
+ console.warn(`Failover provider ${name} failed for ${feature}: ${error.message}`);
269
+ }
270
+ }
271
+
272
+ // All providers failed, use rule-based fallback
273
+ return this._fallback(feature);
275
274
  }
276
275
 
277
276
  /**
278
- * 降级处理
277
+ * Rule-based fallback when AI is unavailable
279
278
  * @private
280
279
  */
281
- _fallback(feature, input, context) {
282
- if (!this.fallbackToRules) {
283
- return null;
280
+ _fallback(feature) {
281
+ if (!this.fallbackToRules) return null;
282
+
283
+ switch (feature) {
284
+ case 'intentUnderstanding':
285
+ return { intent: 'unknown', confidence: 0, source: 'rules' };
286
+ case 'parameterExtraction':
287
+ return {};
288
+ case 'skillSelection':
289
+ return { selectedSkill: null, confidence: 0, source: 'rules' };
290
+ case 'resultSummary':
291
+ return null;
292
+ case 'learningFeedback':
293
+ return { isLearning: false, source: 'rules' };
294
+ default:
295
+ return null;
284
296
  }
285
- // 返回 null,让调用方使用规则引擎
286
- return null;
287
297
  }
288
298
 
289
299
  /**
@@ -8,7 +8,7 @@ class AnthropicProvider extends BaseModel {
8
8
  constructor(config = {}) {
9
9
  super('anthropic', config);
10
10
  this.apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY;
11
- this.model = config.model || 'claude-3-sonnet-20240229';
11
+ this.model = config.model || 'claude-sonnet-4-20250514';
12
12
  this.baseUrl = config.baseUrl || 'https://api.anthropic.com';
13
13
  this.maxTokens = config.maxTokens ?? 2000;
14
14
  this.temperature = config.temperature ?? 0.3;
@@ -26,14 +26,10 @@ class AnthropicProvider extends BaseModel {
26
26
  }
27
27
 
28
28
  async chat(messages, options = {}) {
29
- if (!this.initialized) {
30
- await this.init();
31
- }
29
+ if (!this.initialized) await this.init();
32
30
 
33
- // 转换消息格式:提取 system 消息
34
31
  let systemPrompt = '';
35
32
  const userMessages = [];
36
-
37
33
  for (const msg of messages) {
38
34
  if (msg.role === 'system') {
39
35
  systemPrompt = msg.content;
@@ -42,7 +38,7 @@ class AnthropicProvider extends BaseModel {
42
38
  }
43
39
  }
44
40
 
45
- const response = await fetch(`${this.baseUrl}/v1/messages`, {
41
+ const response = await this.fetchWithRetry(`${this.baseUrl}/v1/messages`, {
46
42
  method: 'POST',
47
43
  headers: {
48
44
  'Content-Type': 'application/json',
@@ -74,19 +70,30 @@ class AnthropicProvider extends BaseModel {
74
70
  async isAvailable() {
75
71
  try {
76
72
  if (!this.apiKey) return false;
77
- // Anthropic 没有 models 端点,直接尝试调用
78
- return true;
73
+ // Anthropic has no /models endpoint; make a lightweight messages call to verify
74
+ const response = await this.fetchWithRetry(`${this.baseUrl}/v1/messages`, {
75
+ method: 'POST',
76
+ headers: {
77
+ 'Content-Type': 'application/json',
78
+ 'x-api-key': this.apiKey,
79
+ 'anthropic-version': '2023-06-01'
80
+ },
81
+ body: JSON.stringify({
82
+ model: this.model,
83
+ max_tokens: 1,
84
+ messages: [{ role: 'user', content: 'hi' }]
85
+ }),
86
+ retries: 1,
87
+ timeout: 10000
88
+ });
89
+ return response.ok || response.status === 400; // 400 = bad request but API is reachable
79
90
  } catch {
80
91
  return false;
81
92
  }
82
93
  }
83
94
 
84
95
  getInfo() {
85
- return {
86
- ...super.getInfo(),
87
- model: this.model,
88
- baseUrl: this.baseUrl
89
- };
96
+ return { ...super.getInfo(), model: this.model, baseUrl: this.baseUrl };
90
97
  }
91
98
  }
92
99
 
@@ -31,7 +31,7 @@ class DeepSeekProvider extends BaseModel {
31
31
  await this.init();
32
32
  }
33
33
 
34
- const response = await fetch(`${this.baseUrl}/chat/completions`, {
34
+ const response = await this.fetchWithRetry(`${this.baseUrl}/chat/completions`, {
35
35
  method: 'POST',
36
36
  headers: {
37
37
  'Content-Type': 'application/json',
@@ -61,7 +61,17 @@ class DeepSeekProvider extends BaseModel {
61
61
  async isAvailable() {
62
62
  try {
63
63
  if (!this.apiKey) return false;
64
- return true;
64
+ const response = await this.fetchWithRetry(`${this.baseUrl}/chat/completions`, {
65
+ method: 'POST',
66
+ headers: {
67
+ 'Content-Type': 'application/json',
68
+ 'Authorization': `Bearer ${this.apiKey}`
69
+ },
70
+ body: JSON.stringify({ model: this.model, messages: [{ role: 'user', content: 'hi' }], max_tokens: 1 }),
71
+ retries: 1,
72
+ timeout: 10000
73
+ });
74
+ return response.ok || response.status === 400;
65
75
  } catch {
66
76
  return false;
67
77
  }
@@ -26,7 +26,7 @@ class ERNIEProvider extends BaseModel {
26
26
  }
27
27
 
28
28
  async refreshAccessToken() {
29
- const response = await fetch(
29
+ const response = await this.fetchWithRetry(
30
30
  `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${this.apiKey}&client_secret=${this.secretKey}`,
31
31
  { method: 'POST' }
32
32
  );
@@ -51,7 +51,7 @@ class ERNIEProvider extends BaseModel {
51
51
  const model = options.model || this.model;
52
52
  const endpoint = this.getEndpoint(model);
53
53
 
54
- const response = await fetch(`${this.baseUrl}${endpoint}?access_token=${this.accessToken}`, {
54
+ const response = await this.fetchWithRetry(`${this.baseUrl}${endpoint}?access_token=${this.accessToken}`, {
55
55
  method: 'POST',
56
56
  headers: {
57
57
  'Content-Type': 'application/json'