autosnippet 3.0.1 → 3.0.2

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 (290) hide show
  1. package/README.md +230 -324
  2. package/bin/api-server.js +1 -1
  3. package/bin/cli.js +204 -244
  4. package/bin/mcp-server.js +5 -3
  5. package/config/knowledge-base.config.js +132 -132
  6. package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
  7. package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
  8. package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
  9. package/dashboard/dist/index.html +3 -3
  10. package/lib/bootstrap.js +8 -8
  11. package/lib/cli/AiScanService.js +86 -40
  12. package/lib/cli/KnowledgeSyncService.js +113 -74
  13. package/lib/cli/SetupService.js +439 -277
  14. package/lib/cli/UpgradeService.js +63 -100
  15. package/lib/core/AstAnalyzer.js +276 -597
  16. package/lib/core/ast/ProjectGraph.js +101 -40
  17. package/lib/core/ast/ensure-grammars.js +232 -0
  18. package/lib/core/ast/index.js +115 -0
  19. package/lib/core/ast/lang-dart.js +661 -0
  20. package/lib/core/ast/lang-go.js +530 -0
  21. package/lib/core/ast/lang-java.js +435 -0
  22. package/lib/core/ast/lang-javascript.js +272 -0
  23. package/lib/core/ast/lang-kotlin.js +423 -0
  24. package/lib/core/ast/lang-objc.js +388 -0
  25. package/lib/core/ast/lang-python.js +371 -0
  26. package/lib/core/ast/lang-swift.js +337 -0
  27. package/lib/core/ast/lang-typescript.js +503 -0
  28. package/lib/core/capability/CapabilityProbe.js +18 -9
  29. package/lib/core/constitution/Constitution.js +2 -3
  30. package/lib/core/constitution/ConstitutionValidator.js +65 -24
  31. package/lib/core/discovery/DartDiscoverer.js +534 -0
  32. package/lib/core/discovery/DiscovererRegistry.js +83 -0
  33. package/lib/core/discovery/GenericDiscoverer.js +225 -0
  34. package/lib/core/discovery/GoDiscoverer.js +541 -0
  35. package/lib/core/discovery/JvmDiscoverer.js +506 -0
  36. package/lib/core/discovery/NodeDiscoverer.js +466 -0
  37. package/lib/core/discovery/ProjectDiscoverer.js +93 -0
  38. package/lib/core/discovery/PythonDiscoverer.js +338 -0
  39. package/lib/core/discovery/SpmDiscoverer.js +5 -0
  40. package/lib/core/discovery/index.js +53 -0
  41. package/lib/core/enhancement/EnhancementPack.js +71 -0
  42. package/lib/core/enhancement/EnhancementRegistry.js +47 -0
  43. package/lib/core/enhancement/android-enhancement.js +102 -0
  44. package/lib/core/enhancement/django-enhancement.js +70 -0
  45. package/lib/core/enhancement/fastapi-enhancement.js +63 -0
  46. package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
  47. package/lib/core/enhancement/go-web-enhancement.js +201 -0
  48. package/lib/core/enhancement/index.js +65 -0
  49. package/lib/core/enhancement/node-server-enhancement.js +88 -0
  50. package/lib/core/enhancement/react-enhancement.js +86 -0
  51. package/lib/core/enhancement/spring-enhancement.js +112 -0
  52. package/lib/core/enhancement/vue-enhancement.js +96 -0
  53. package/lib/core/gateway/Gateway.js +8 -9
  54. package/lib/core/gateway/GatewayActionRegistry.js +1 -1
  55. package/lib/core/permission/PermissionManager.js +12 -8
  56. package/lib/domain/index.js +13 -9
  57. package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
  58. package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
  59. package/lib/domain/knowledge/Lifecycle.js +22 -22
  60. package/lib/domain/knowledge/index.js +9 -12
  61. package/lib/domain/knowledge/values/Constraints.js +31 -21
  62. package/lib/domain/knowledge/values/Content.js +21 -13
  63. package/lib/domain/knowledge/values/Quality.js +31 -18
  64. package/lib/domain/knowledge/values/Reasoning.js +20 -12
  65. package/lib/domain/knowledge/values/Relations.js +37 -25
  66. package/lib/domain/knowledge/values/Stats.js +18 -12
  67. package/lib/domain/knowledge/values/index.js +4 -3
  68. package/lib/domain/snippet/Snippet.js +35 -10
  69. package/lib/external/ai/AiFactory.js +48 -16
  70. package/lib/external/ai/AiProvider.js +184 -90
  71. package/lib/external/ai/providers/ClaudeProvider.js +25 -12
  72. package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
  73. package/lib/external/ai/providers/MockProvider.js +9 -3
  74. package/lib/external/ai/providers/OpenAiProvider.js +51 -29
  75. package/lib/external/mcp/McpServer.js +66 -36
  76. package/lib/external/mcp/errorHandler.js +23 -11
  77. package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
  78. package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
  79. package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
  80. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
  81. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
  82. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
  83. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
  84. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
  85. package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
  86. package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
  87. package/lib/external/mcp/handlers/bootstrap.js +691 -168
  88. package/lib/external/mcp/handlers/browse.js +66 -22
  89. package/lib/external/mcp/handlers/candidate.js +118 -35
  90. package/lib/external/mcp/handlers/consolidated.js +49 -17
  91. package/lib/external/mcp/handlers/guard.js +104 -39
  92. package/lib/external/mcp/handlers/knowledge.js +60 -36
  93. package/lib/external/mcp/handlers/search.js +43 -14
  94. package/lib/external/mcp/handlers/skill.js +120 -45
  95. package/lib/external/mcp/handlers/structure.js +240 -86
  96. package/lib/external/mcp/handlers/system.js +42 -12
  97. package/lib/external/mcp/handlers/wiki.js +58 -33
  98. package/lib/external/mcp/tools.js +306 -123
  99. package/lib/http/HttpServer.js +72 -47
  100. package/lib/http/middleware/RateLimiter.js +5 -3
  101. package/lib/http/middleware/errorHandler.js +6 -1
  102. package/lib/http/middleware/requestLogger.js +14 -3
  103. package/lib/http/middleware/roleResolver.js +30 -23
  104. package/lib/http/routes/ai.js +387 -265
  105. package/lib/http/routes/auth.js +81 -61
  106. package/lib/http/routes/candidates.js +430 -320
  107. package/lib/http/routes/commands.js +289 -189
  108. package/lib/http/routes/extract.js +158 -125
  109. package/lib/http/routes/guardRules.js +309 -217
  110. package/lib/http/routes/knowledge.js +213 -154
  111. package/lib/http/routes/modules.js +578 -0
  112. package/lib/http/routes/monitoring.js +6 -6
  113. package/lib/http/routes/recipes.js +104 -93
  114. package/lib/http/routes/search.js +361 -305
  115. package/lib/http/routes/skills.js +145 -98
  116. package/lib/http/routes/snippets.js +42 -30
  117. package/lib/http/routes/spm.js +3 -405
  118. package/lib/http/routes/violations.js +113 -93
  119. package/lib/http/routes/wiki.js +211 -170
  120. package/lib/http/utils/routeHelpers.js +3 -1
  121. package/lib/http/utils/sse-sessions.js +16 -6
  122. package/lib/http/utils/sse.js +15 -5
  123. package/lib/infrastructure/audit/AuditLogger.js +5 -2
  124. package/lib/infrastructure/audit/AuditStore.js +10 -7
  125. package/lib/infrastructure/cache/CacheService.js +3 -1
  126. package/lib/infrastructure/cache/GraphCache.js +8 -4
  127. package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
  128. package/lib/infrastructure/config/ConfigLoader.js +9 -5
  129. package/lib/infrastructure/config/Defaults.js +30 -10
  130. package/lib/infrastructure/config/Paths.js +28 -8
  131. package/lib/infrastructure/config/TriggerSymbol.js +22 -10
  132. package/lib/infrastructure/database/DatabaseConnection.js +15 -10
  133. package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
  134. package/lib/infrastructure/external/ClipboardManager.js +6 -2
  135. package/lib/infrastructure/external/NativeUi.js +50 -43
  136. package/lib/infrastructure/external/OpenBrowser.js +14 -17
  137. package/lib/infrastructure/external/XcodeAutomation.js +14 -258
  138. package/lib/infrastructure/logging/Logger.js +46 -30
  139. package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
  140. package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
  141. package/lib/infrastructure/paths/HeaderResolver.js +25 -9
  142. package/lib/infrastructure/paths/PathFinder.js +34 -12
  143. package/lib/infrastructure/plugin/PluginManager.js +26 -8
  144. package/lib/infrastructure/realtime/RealtimeService.js +2 -2
  145. package/lib/infrastructure/vector/Chunker.js +22 -7
  146. package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
  147. package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
  148. package/lib/infrastructure/vector/VectorStore.js +28 -10
  149. package/lib/injection/ServiceContainer.js +247 -93
  150. package/lib/platform/ios/index.js +63 -0
  151. package/lib/platform/ios/routes/spm.js +437 -0
  152. package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
  153. package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
  154. package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
  155. package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
  156. package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
  157. package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
  158. package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
  159. package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
  160. package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
  161. package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
  162. package/lib/repository/base/BaseRepository.js +7 -9
  163. package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
  164. package/lib/repository/token/TokenUsageStore.js +4 -2
  165. package/lib/service/automation/ActionPipeline.js +1 -1
  166. package/lib/service/automation/AutomationOrchestrator.js +8 -4
  167. package/lib/service/automation/ContextCollector.js +7 -5
  168. package/lib/service/automation/DirectiveDetector.js +23 -16
  169. package/lib/service/automation/FileWatcher.js +112 -56
  170. package/lib/service/automation/TriggerResolver.js +6 -4
  171. package/lib/service/automation/handlers/AlinkHandler.js +24 -12
  172. package/lib/service/automation/handlers/CreateHandler.js +19 -20
  173. package/lib/service/automation/handlers/DraftHandler.js +14 -8
  174. package/lib/service/automation/handlers/GuardHandler.js +93 -63
  175. package/lib/service/automation/handlers/HeaderHandler.js +1 -6
  176. package/lib/service/automation/handlers/SearchHandler.js +155 -88
  177. package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
  178. package/lib/service/candidate/SimilarityService.js +25 -9
  179. package/lib/service/chat/AnalystAgent.js +50 -24
  180. package/lib/service/chat/CandidateGuardrail.js +143 -17
  181. package/lib/service/chat/ChatAgent.js +655 -260
  182. package/lib/service/chat/ContextWindow.js +116 -71
  183. package/lib/service/chat/ConversationStore.js +77 -36
  184. package/lib/service/chat/EpisodicConsolidator.js +47 -23
  185. package/lib/service/chat/HandoffProtocol.js +98 -22
  186. package/lib/service/chat/Memory.js +34 -14
  187. package/lib/service/chat/ProducerAgent.js +40 -20
  188. package/lib/service/chat/ProjectSemanticMemory.js +109 -78
  189. package/lib/service/chat/ReasoningLayer.js +148 -70
  190. package/lib/service/chat/ReasoningTrace.js +44 -32
  191. package/lib/service/chat/TaskPipeline.js +39 -19
  192. package/lib/service/chat/ToolRegistry.js +48 -29
  193. package/lib/service/chat/WorkingMemory.js +44 -18
  194. package/lib/service/chat/tools.js +1096 -494
  195. package/lib/service/context/RecipeExtractor.js +132 -51
  196. package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
  197. package/lib/service/cursor/KnowledgeCompressor.js +25 -22
  198. package/lib/service/cursor/RulesGenerator.js +13 -7
  199. package/lib/service/cursor/SkillsSyncer.js +77 -27
  200. package/lib/service/cursor/TokenBudget.js +2 -2
  201. package/lib/service/cursor/TopicClassifier.js +54 -20
  202. package/lib/service/guard/ComplianceReporter.js +55 -43
  203. package/lib/service/guard/ExclusionManager.js +67 -29
  204. package/lib/service/guard/GuardCheckEngine.js +381 -86
  205. package/lib/service/guard/GuardFeedbackLoop.js +22 -10
  206. package/lib/service/guard/GuardService.js +29 -19
  207. package/lib/service/guard/RuleLearner.js +55 -23
  208. package/lib/service/guard/SourceFileCollector.js +27 -20
  209. package/lib/service/guard/ViolationsStore.js +43 -38
  210. package/lib/service/knowledge/CodeEntityGraph.js +147 -82
  211. package/lib/service/knowledge/ConfidenceRouter.js +12 -10
  212. package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
  213. package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
  214. package/lib/service/knowledge/KnowledgeService.js +222 -112
  215. package/lib/service/module/ModuleService.js +969 -0
  216. package/lib/service/quality/FeedbackCollector.js +27 -15
  217. package/lib/service/quality/QualityScorer.js +78 -24
  218. package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
  219. package/lib/service/recipe/RecipeParser.js +78 -45
  220. package/lib/service/search/CoarseRanker.js +43 -28
  221. package/lib/service/search/CrossEncoderReranker.js +32 -21
  222. package/lib/service/search/InvertedIndex.js +21 -7
  223. package/lib/service/search/MultiSignalRanker.js +90 -28
  224. package/lib/service/search/RetrievalFunnel.js +45 -24
  225. package/lib/service/search/SearchEngine.js +255 -103
  226. package/lib/service/skills/EventAggregator.js +32 -15
  227. package/lib/service/skills/SignalCollector.js +140 -64
  228. package/lib/service/skills/SkillAdvisor.js +79 -42
  229. package/lib/service/skills/SkillHooks.js +16 -14
  230. package/lib/service/snippet/PlaceholderConverter.js +5 -0
  231. package/lib/service/snippet/SnippetFactory.js +116 -99
  232. package/lib/service/snippet/SnippetInstaller.js +234 -62
  233. package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
  234. package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
  235. package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
  236. package/lib/service/wiki/WikiGenerator.js +637 -263
  237. package/lib/shared/DimensionCopyRegistry.js +472 -0
  238. package/lib/shared/LanguageService.js +399 -0
  239. package/lib/shared/PathGuard.js +45 -28
  240. package/lib/shared/RecipeReadinessChecker.js +72 -12
  241. package/lib/shared/constants.js +41 -41
  242. package/lib/shared/errors/BaseError.js +2 -2
  243. package/lib/shared/errors/index.js +4 -4
  244. package/lib/shared/similarity.js +25 -8
  245. package/lib/shared/token-utils.js +6 -2
  246. package/lib/shared/utils/common.js +12 -4
  247. package/package.json +49 -13
  248. package/scripts/bench-real-projects.mjs +256 -0
  249. package/scripts/build-native-ui.js +30 -30
  250. package/scripts/clear-old-vector-index.js +5 -35
  251. package/scripts/clear-vector-cache.js +7 -37
  252. package/scripts/collect-test-project-stats.mjs +160 -0
  253. package/scripts/diagnose-mcp.js +41 -32
  254. package/scripts/ensure-parse-package.js +6 -9
  255. package/scripts/generate-recipe-drafts.js +116 -77
  256. package/scripts/init-db.js +3 -20
  257. package/scripts/init-snippets.js +305 -0
  258. package/scripts/init-vector-db.js +173 -170
  259. package/scripts/install-cursor-skill.js +148 -104
  260. package/scripts/install-full.js +8 -21
  261. package/scripts/install-vscode-copilot.js +146 -145
  262. package/scripts/migrate-md-to-knowledge.mjs +139 -151
  263. package/scripts/postinstall-safe.js +5 -17
  264. package/scripts/recipe-audit.js +106 -82
  265. package/scripts/release.js +283 -323
  266. package/scripts/setup-mcp-config.js +60 -52
  267. package/scripts/verify-context-api.js +20 -20
  268. package/skills/autosnippet-analysis/SKILL.md +10 -6
  269. package/skills/autosnippet-candidates/SKILL.md +27 -26
  270. package/skills/autosnippet-coldstart/SKILL.md +555 -38
  271. package/skills/autosnippet-concepts/SKILL.md +349 -337
  272. package/skills/autosnippet-create/SKILL.md +5 -5
  273. package/skills/autosnippet-reference-dart/SKILL.md +543 -0
  274. package/skills/autosnippet-reference-go/SKILL.md +539 -0
  275. package/skills/autosnippet-reference-java/SKILL.md +534 -0
  276. package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
  277. package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
  278. package/skills/autosnippet-reference-objc/SKILL.md +29 -6
  279. package/skills/autosnippet-reference-python/SKILL.md +800 -0
  280. package/skills/autosnippet-reference-swift/SKILL.md +70 -14
  281. package/skills/autosnippet-structure/SKILL.md +4 -4
  282. package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
  283. package/templates/recipes-setup/README.md +2 -2
  284. package/templates/recipes-setup/_template.md +1 -1
  285. package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
  286. package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
  287. package/resources/asd-entry/main.swift +0 -159
  288. package/scripts/build-asd-entry.js +0 -51
  289. package/scripts/init-xcode-snippets.js +0 -311
  290. package/template.json +0 -39
@@ -3,14 +3,14 @@
3
3
  * AI 提供商管理、摘要、翻译、对话、.env LLM 配置
4
4
  */
5
5
 
6
- import express from 'express';
7
6
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
8
7
  import { join } from 'node:path';
9
- import { asyncHandler } from '../middleware/errorHandler.js';
10
- import { getServiceContainer } from '../../injection/ServiceContainer.js';
8
+ import express from 'express';
11
9
  import { createProvider } from '../../external/ai/AiFactory.js';
12
- import { ValidationError } from '../../shared/errors/index.js';
13
10
  import Logger from '../../infrastructure/logging/Logger.js';
11
+ import { getServiceContainer } from '../../injection/ServiceContainer.js';
12
+ import { ValidationError } from '../../shared/errors/index.js';
13
+ import { asyncHandler } from '../middleware/errorHandler.js';
14
14
  import { createStreamSession, getStreamSession } from '../utils/sse-sessions.js';
15
15
 
16
16
  const router = express.Router();
@@ -22,231 +22,298 @@ function getChatAgent() {
22
22
  return container.get('chatAgent');
23
23
  }
24
24
 
25
+ // ═══════════════════════════════════════════════════════
26
+ // UI 语言偏好 — 前端 ↔ 服务端同步
27
+ // ═══════════════════════════════════════════════════════
28
+
29
+ /**
30
+ * GET /api/v1/ai/lang
31
+ * 获取当前默认 UI 语言(由系统环境变量初始化,前端可覆盖)
32
+ */
33
+ router.get(
34
+ '/lang',
35
+ asyncHandler(async (req, res) => {
36
+ const chatAgent = getChatAgent();
37
+ res.json({ success: true, data: { lang: chatAgent.getLang() || 'zh' } });
38
+ })
39
+ );
40
+
41
+ /**
42
+ * POST /api/v1/ai/lang
43
+ * 更新默认 UI 语言(前端切语言时同步到服务端)
44
+ */
45
+ router.post(
46
+ '/lang',
47
+ asyncHandler(async (req, res) => {
48
+ const { lang } = req.body;
49
+ if (lang !== 'zh' && lang !== 'en') {
50
+ throw new ValidationError('lang must be "zh" or "en"');
51
+ }
52
+ const chatAgent = getChatAgent();
53
+ chatAgent.setLang(lang);
54
+ logger.info(`UI language preference updated to "${lang}"`);
55
+ res.json({ success: true, data: { lang } });
56
+ })
57
+ );
58
+
25
59
  /**
26
60
  * GET /api/v1/ai/providers
27
61
  * 获取可用的 AI 提供商列表
28
62
  */
29
- router.get('/providers', asyncHandler(async (req, res) => {
30
- const providers = [
31
- { id: 'google', label: 'Google Gemini', defaultModel: 'gemini-3-flash-preview' },
32
- { id: 'openai', label: 'OpenAI', defaultModel: 'gpt-4o' },
33
- { id: 'deepseek', label: 'DeepSeek', defaultModel: 'deepseek-chat' },
34
- { id: 'claude', label: 'Claude', defaultModel: 'claude-3-5-sonnet-20240620' },
35
- { id: 'ollama', label: 'Ollama', defaultModel: 'llama3' },
36
- { id: 'mock', label: 'Mock (测试)', defaultModel: 'mock-l3' },
37
- ];
38
-
39
- res.json({ success: true, data: providers });
40
- }));
63
+ router.get(
64
+ '/providers',
65
+ asyncHandler(async (req, res) => {
66
+ const providers = [
67
+ { id: 'google', label: 'Google Gemini', defaultModel: 'gemini-3-flash-preview' },
68
+ { id: 'openai', label: 'OpenAI', defaultModel: 'gpt-4o' },
69
+ { id: 'deepseek', label: 'DeepSeek', defaultModel: 'deepseek-chat' },
70
+ { id: 'claude', label: 'Claude', defaultModel: 'claude-3-5-sonnet-20240620' },
71
+ { id: 'ollama', label: 'Ollama', defaultModel: 'llama3' },
72
+ { id: 'mock', label: 'Mock (测试)', defaultModel: 'mock-l3' },
73
+ ];
74
+
75
+ res.json({ success: true, data: providers });
76
+ })
77
+ );
41
78
 
42
79
  /**
43
80
  * GET /api/v1/ai/config
44
81
  * 获取当前 AI 配置
45
82
  */
46
- router.get('/config', asyncHandler(async (req, res) => {
47
- const container = getServiceContainer();
48
- const p = container.singletons?.aiProvider;
49
- res.json({
50
- success: true,
51
- data: {
52
- provider: p?.name || '',
53
- model: p?.model || '',
54
- },
55
- });
56
- }));
83
+ router.get(
84
+ '/config',
85
+ asyncHandler(async (req, res) => {
86
+ const container = getServiceContainer();
87
+ const p = container.singletons?.aiProvider;
88
+ res.json({
89
+ success: true,
90
+ data: {
91
+ provider: p?.name || '',
92
+ model: p?.model || '',
93
+ },
94
+ });
95
+ })
96
+ );
57
97
 
58
98
  /**
59
99
  * POST /api/v1/ai/config
60
100
  * 更新 AI 配置(切换提供商/模型)
61
101
  */
62
- router.post('/config', asyncHandler(async (req, res) => {
63
- const { provider, model } = req.body;
102
+ router.post(
103
+ '/config',
104
+ asyncHandler(async (req, res) => {
105
+ const { provider, model } = req.body;
64
106
 
65
- if (!provider || typeof provider !== 'string') {
66
- throw new ValidationError('provider is required');
67
- }
107
+ if (!provider || typeof provider !== 'string') {
108
+ throw new ValidationError('provider is required');
109
+ }
68
110
 
69
- // 创建新的 provider 实例验证配置有效
70
- let newProvider;
71
- try {
72
- newProvider = createProvider({
73
- provider: provider.toLowerCase(),
74
- model: model || undefined,
75
- });
76
- } catch (error) {
77
- throw new ValidationError(`Invalid provider: ${error.message}`);
78
- }
111
+ // 创建新的 provider 实例验证配置有效
112
+ let newProvider;
113
+ try {
114
+ newProvider = createProvider({
115
+ provider: provider.toLowerCase(),
116
+ model: model || undefined,
117
+ });
118
+ } catch (error) {
119
+ throw new ValidationError(`Invalid provider: ${error.message}`);
120
+ }
79
121
 
80
- // 同步到 DI 容器,使 SearchEngine / Agent / IndexingPipeline 等也使用新 provider
81
- try {
82
- const container = getServiceContainer();
83
- container.singletons.aiProvider = newProvider;
84
- logger.info('AI provider synced to DI container', { provider: provider.toLowerCase(), model: newProvider.model });
85
- } catch (err) {
86
- logger.debug('DI container 同步 AI provider 失败', { error: err.message });
87
- }
122
+ // 同步到 DI 容器,使 SearchEngine / Agent / IndexingPipeline 等也使用新 provider
123
+ try {
124
+ const container = getServiceContainer();
125
+ container.reloadAiProvider(newProvider);
126
+ logger.info('AI provider synced to DI container', {
127
+ provider: provider.toLowerCase(),
128
+ model: newProvider.model,
129
+ });
130
+ } catch (err) {
131
+ logger.debug('DI container 同步 AI provider 失败', { error: err.message });
132
+ }
88
133
 
89
- res.json({
90
- success: true,
91
- data: {
92
- provider: provider.toLowerCase(),
93
- model: newProvider.model,
94
- name: newProvider.name,
95
- },
96
- });
97
- }));
134
+ res.json({
135
+ success: true,
136
+ data: {
137
+ provider: provider.toLowerCase(),
138
+ model: newProvider.model,
139
+ name: newProvider.name,
140
+ },
141
+ });
142
+ })
143
+ );
98
144
 
99
145
  /**
100
146
  * POST /api/v1/ai/summarize
101
147
  * AI 摘要生成
102
148
  */
103
- router.post('/summarize', asyncHandler(async (req, res) => {
104
- const { code, language } = req.body;
149
+ router.post(
150
+ '/summarize',
151
+ asyncHandler(async (req, res) => {
152
+ const { code, language } = req.body;
105
153
 
106
- if (!code) {
107
- throw new ValidationError('code is required');
108
- }
154
+ if (!code) {
155
+ throw new ValidationError('code is required');
156
+ }
109
157
 
110
- const chatAgent = getChatAgent();
111
- const result = await chatAgent.executeTool('summarize_code', { code, language });
158
+ const chatAgent = getChatAgent();
159
+ const result = await chatAgent.executeTool('summarize_code', { code, language });
112
160
 
113
- if (result?.error) {
114
- throw new ValidationError(result.error);
115
- }
161
+ if (result?.error) {
162
+ throw new ValidationError(result.error);
163
+ }
116
164
 
117
- res.json({ success: true, data: result });
118
- }));
165
+ res.json({ success: true, data: result });
166
+ })
167
+ );
119
168
 
120
169
  /**
121
170
  * POST /api/v1/ai/translate
122
171
  * AI 翻译(中文 → 英文)
123
172
  */
124
- router.post('/translate', asyncHandler(async (req, res) => {
125
- const { summary, usageGuide } = req.body;
173
+ router.post(
174
+ '/translate',
175
+ asyncHandler(async (req, res) => {
176
+ const { summary, usageGuide } = req.body;
126
177
 
127
- if (!summary && !usageGuide) {
128
- return res.json({
129
- success: true,
130
- data: { summaryEn: '', usageGuideEn: '' },
131
- });
132
- }
133
-
134
- try {
135
- const chatAgent = getChatAgent();
136
- const result = await chatAgent.executeTool('ai_translate', { summary, usageGuide });
137
-
138
- if (result?.error) {
139
- // AI 不可用,降级返回原文
140
- logger.warn('AI translate tool returned error', { error: result.error });
178
+ if (!summary && !usageGuide) {
141
179
  return res.json({
142
180
  success: true,
143
- data: { summaryEn: summary || '', usageGuideEn: usageGuide || '' },
144
- warning: result.error,
181
+ data: { summaryEn: '', usageGuideEn: '' },
145
182
  });
146
183
  }
147
184
 
148
- res.json({ success: true, data: result });
149
- } catch (err) {
150
- logger.warn('AI translate failed, returning original text', { error: err.message });
151
- res.json({
152
- success: true,
153
- data: { summaryEn: summary || '', usageGuideEn: usageGuide || '' },
154
- warning: `Translation failed: ${err.message}`,
155
- });
156
- }
157
- }));
185
+ try {
186
+ const chatAgent = getChatAgent();
187
+ const result = await chatAgent.executeTool('ai_translate', { summary, usageGuide });
188
+
189
+ if (result?.error) {
190
+ // AI 不可用,降级返回原文
191
+ logger.warn('AI translate tool returned error', { error: result.error });
192
+ return res.json({
193
+ success: true,
194
+ data: { summaryEn: summary || '', usageGuideEn: usageGuide || '' },
195
+ warning: result.error,
196
+ });
197
+ }
198
+
199
+ res.json({ success: true, data: result });
200
+ } catch (err) {
201
+ logger.warn('AI translate failed, returning original text', { error: err.message });
202
+ res.json({
203
+ success: true,
204
+ data: { summaryEn: summary || '', usageGuideEn: usageGuide || '' },
205
+ warning: `Translation failed: ${err.message}`,
206
+ });
207
+ }
208
+ })
209
+ );
158
210
 
159
211
  /**
160
212
  * POST /api/v1/ai/chat
161
213
  * AI 对话(RAG 模式,结合项目知识库)
162
214
  */
163
- router.post('/chat', asyncHandler(async (req, res) => {
164
- const { prompt, history = [] } = req.body;
215
+ router.post(
216
+ '/chat',
217
+ asyncHandler(async (req, res) => {
218
+ const { prompt, history = [], lang } = req.body;
165
219
 
166
- if (!prompt) {
167
- throw new ValidationError('prompt is required');
168
- }
220
+ if (!prompt) {
221
+ throw new ValidationError('prompt is required');
222
+ }
169
223
 
170
- const chatAgent = getChatAgent();
171
- const result = await chatAgent.execute(prompt, { history });
172
-
173
- res.json({
174
- success: true,
175
- data: {
176
- reply: result.reply,
177
- hasContext: result.hasContext,
178
- toolCalls: result.toolCalls,
179
- reasoningQuality: result.reasoningQuality || null,
180
- },
181
- });
182
- }));
224
+ const chatAgent = getChatAgent();
225
+ const result = await chatAgent.execute(prompt, { history, lang });
226
+
227
+ res.json({
228
+ success: true,
229
+ data: {
230
+ reply: result.reply,
231
+ hasContext: result.hasContext,
232
+ toolCalls: result.toolCalls,
233
+ reasoningQuality: result.reasoningQuality || null,
234
+ },
235
+ });
236
+ })
237
+ );
183
238
 
184
239
  /**
185
240
  * POST /api/v1/ai/agent/tool
186
241
  * 程序化直接调用 Agent 工具(跳过 ReAct 循环)
187
242
  * Body: { tool: string, params: object }
188
243
  */
189
- router.post('/agent/tool', asyncHandler(async (req, res) => {
190
- const { tool, params = {} } = req.body;
244
+ router.post(
245
+ '/agent/tool',
246
+ asyncHandler(async (req, res) => {
247
+ const { tool, params = {} } = req.body;
191
248
 
192
- if (!tool) {
193
- throw new ValidationError('tool name is required');
194
- }
249
+ if (!tool) {
250
+ throw new ValidationError('tool name is required');
251
+ }
195
252
 
196
- const chatAgent = getChatAgent();
197
- const result = await chatAgent.executeTool(tool, params);
253
+ const chatAgent = getChatAgent();
254
+ const result = await chatAgent.executeTool(tool, params);
198
255
 
199
- res.json({ success: true, data: result });
200
- }));
256
+ res.json({ success: true, data: result });
257
+ })
258
+ );
201
259
 
202
260
  /**
203
261
  * POST /api/v1/ai/agent/task
204
262
  * 执行预定义任务流(查重提交 / 批量关系发现 / 批量补全)
205
263
  * Body: { task: string, params: object }
206
264
  */
207
- router.post('/agent/task', asyncHandler(async (req, res) => {
208
- const { task, params = {} } = req.body;
265
+ router.post(
266
+ '/agent/task',
267
+ asyncHandler(async (req, res) => {
268
+ const { task, params = {} } = req.body;
209
269
 
210
- if (!task) {
211
- throw new ValidationError('task name is required');
212
- }
270
+ if (!task) {
271
+ throw new ValidationError('task name is required');
272
+ }
213
273
 
214
- const chatAgent = getChatAgent();
215
- const result = await chatAgent.runTask(task, params);
274
+ const chatAgent = getChatAgent();
275
+ const result = await chatAgent.runTask(task, params);
216
276
 
217
- res.json({ success: true, data: result });
218
- }));
277
+ res.json({ success: true, data: result });
278
+ })
279
+ );
219
280
 
220
281
  /**
221
282
  * GET /api/v1/ai/agent/capabilities
222
283
  * 获取 Agent 能力清单(工具列表 + 任务列表)
223
284
  */
224
- router.get('/agent/capabilities', asyncHandler(async (req, res) => {
225
- const chatAgent = getChatAgent();
226
- res.json({ success: true, data: chatAgent.getCapabilities() });
227
- }));
285
+ router.get(
286
+ '/agent/capabilities',
287
+ asyncHandler(async (req, res) => {
288
+ const chatAgent = getChatAgent();
289
+ res.json({ success: true, data: chatAgent.getCapabilities() });
290
+ })
291
+ );
228
292
 
229
293
  /**
230
294
  * POST /api/v1/ai/format-usage-guide
231
295
  * 格式化 usageGuide 文本(纯文本处理,不涉及 AI 调用)
232
296
  * 注:虽非 AI 功能,但前端从 /ai/ 路径调用,保留以维持 API 兼容
233
297
  */
234
- router.post('/format-usage-guide', asyncHandler(async (req, res) => {
235
- const { text, lang = 'cn' } = req.body;
298
+ router.post(
299
+ '/format-usage-guide',
300
+ asyncHandler(async (req, res) => {
301
+ const { text } = req.body;
236
302
 
237
- if (!text) {
238
- return res.json({ success: true, data: { formatted: '' } });
239
- }
303
+ if (!text) {
304
+ return res.json({ success: true, data: { formatted: '' } });
305
+ }
240
306
 
241
- // 简单文本格式化处理
242
- let formatted = text.trim();
243
- // 确保段落间有空行
244
- formatted = formatted.replace(/\n{3,}/g, '\n\n');
245
- // 确保代码块格式
246
- formatted = formatted.replace(/```(\w+)?\n/g, '\n```$1\n');
307
+ // 简单文本格式化处理
308
+ let formatted = text.trim();
309
+ // 确保段落间有空行
310
+ formatted = formatted.replace(/\n{3,}/g, '\n\n');
311
+ // 确保代码块格式
312
+ formatted = formatted.replace(/```(\w+)?\n/g, '\n```$1\n');
247
313
 
248
- res.json({ success: true, data: { formatted } });
249
- }));
314
+ res.json({ success: true, data: { formatted } });
315
+ })
316
+ );
250
317
 
251
318
  // ═══════════════════════════════════════════════════════
252
319
  // .env LLM 配置读写
@@ -255,7 +322,8 @@ router.post('/format-usage-guide', asyncHandler(async (req, res) => {
255
322
  /** 获取用户项目目录下 .env 的路径 */
256
323
  function _getProjectEnvPath() {
257
324
  const container = getServiceContainer();
258
- const projectRoot = container.singletons?._projectRoot || process.env.ASD_PROJECT_DIR || process.cwd();
325
+ const projectRoot =
326
+ container.singletons?._projectRoot || process.env.ASD_PROJECT_DIR || process.cwd();
259
327
  return join(projectRoot, '.env');
260
328
  }
261
329
 
@@ -286,11 +354,18 @@ function parseLlmEnv(envPath) {
286
354
  for (const line of raw.split('\n')) {
287
355
  const trimmed = line.trim();
288
356
  // 跳过注释和空行
289
- if (!trimmed || trimmed.startsWith('#')) continue;
357
+ if (!trimmed || trimmed.startsWith('#')) {
358
+ continue;
359
+ }
290
360
  const eqIdx = trimmed.indexOf('=');
291
- if (eqIdx === -1) continue;
361
+ if (eqIdx === -1) {
362
+ continue;
363
+ }
292
364
  const key = trimmed.slice(0, eqIdx).trim();
293
- const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
365
+ const val = trimmed
366
+ .slice(eqIdx + 1)
367
+ .trim()
368
+ .replace(/^["']|["']$/g, '');
294
369
  if (LLM_ENV_KEYS.includes(key)) {
295
370
  vars[key] = val;
296
371
  }
@@ -304,7 +379,7 @@ function parseLlmEnv(envPath) {
304
379
  claude: 'ASD_CLAUDE_API_KEY',
305
380
  deepseek: 'ASD_DEEPSEEK_API_KEY',
306
381
  ollama: '', // ollama 不需要 key
307
- mock: '', // mock 不需要 key
382
+ mock: '', // mock 不需要 key
308
383
  };
309
384
  const neededKey = keyMap[provider] || '';
310
385
  const llmReady = !!provider && (!neededKey || !!vars[neededKey]);
@@ -316,11 +391,14 @@ function parseLlmEnv(envPath) {
316
391
  * GET /api/v1/ai/env-config
317
392
  * 读取用户项目 .env 中的 LLM 配置
318
393
  */
319
- router.get('/env-config', asyncHandler(async (req, res) => {
320
- const envPath = _getProjectEnvPath();
321
- const result = parseLlmEnv(envPath);
322
- res.json({ success: true, data: result });
323
- }));
394
+ router.get(
395
+ '/env-config',
396
+ asyncHandler(async (req, res) => {
397
+ const envPath = _getProjectEnvPath();
398
+ const result = parseLlmEnv(envPath);
399
+ res.json({ success: true, data: result });
400
+ })
401
+ );
324
402
 
325
403
  /**
326
404
  * POST /api/v1/ai/env-config
@@ -328,77 +406,91 @@ router.get('/env-config', asyncHandler(async (req, res) => {
328
406
  *
329
407
  * Body: { provider, model, apiKey, proxy? }
330
408
  */
331
- router.post('/env-config', asyncHandler(async (req, res) => {
332
- const { provider, model, apiKey, proxy } = req.body;
333
- if (!provider || typeof provider !== 'string') {
334
- throw new ValidationError('provider is required');
335
- }
409
+ router.post(
410
+ '/env-config',
411
+ asyncHandler(async (req, res) => {
412
+ const { provider, model, apiKey, proxy } = req.body;
413
+ if (!provider || typeof provider !== 'string') {
414
+ throw new ValidationError('provider is required');
415
+ }
336
416
 
337
- const envPath = _getProjectEnvPath();
338
- let content = existsSync(envPath) ? readFileSync(envPath, 'utf8') : '';
417
+ const envPath = _getProjectEnvPath();
418
+ let content = existsSync(envPath) ? readFileSync(envPath, 'utf8') : '';
339
419
 
340
- // 构建 key-value 更新列表
341
- const updates = {
342
- ASD_AI_PROVIDER: provider,
343
- };
344
- if (model) updates.ASD_AI_MODEL = model;
345
- if (proxy) updates.ASD_AI_PROXY = proxy;
420
+ // 构建 key-value 更新列表
421
+ const updates = {
422
+ ASD_AI_PROVIDER: provider,
423
+ };
424
+ if (model) {
425
+ updates.ASD_AI_MODEL = model;
426
+ }
427
+ if (proxy) {
428
+ updates.ASD_AI_PROXY = proxy;
429
+ }
346
430
 
347
- // 根据 provider 决定写入哪个 API Key 变量
348
- const providerKeyMap = {
349
- google: 'ASD_GOOGLE_API_KEY',
350
- openai: 'ASD_OPENAI_API_KEY',
351
- claude: 'ASD_CLAUDE_API_KEY',
352
- deepseek: 'ASD_DEEPSEEK_API_KEY',
353
- };
354
- const keyName = providerKeyMap[provider];
355
- if (keyName && apiKey) {
356
- updates[keyName] = apiKey;
357
- }
431
+ // 根据 provider 决定写入哪个 API Key 变量
432
+ const providerKeyMap = {
433
+ google: 'ASD_GOOGLE_API_KEY',
434
+ openai: 'ASD_OPENAI_API_KEY',
435
+ claude: 'ASD_CLAUDE_API_KEY',
436
+ deepseek: 'ASD_DEEPSEEK_API_KEY',
437
+ };
438
+ const keyName = providerKeyMap[provider];
439
+ if (keyName && apiKey) {
440
+ updates[keyName] = apiKey;
441
+ }
358
442
 
359
- // 逐条合并到 .env 内容
360
- for (const [k, v] of Object.entries(updates)) {
361
- // 匹配已有行(包括被注释的行)
362
- const activeRe = new RegExp(`^${k}\\s*=.*$`, 'm');
363
- const commentedRe = new RegExp(`^#\\s*${k}\\s*=.*$`, 'm');
364
-
365
- if (activeRe.test(content)) {
366
- // 替换已有活动行
367
- content = content.replace(activeRe, `${k}=${v}`);
368
- } else if (commentedRe.test(content)) {
369
- // 取消注释并赋值
370
- content = content.replace(commentedRe, `${k}=${v}`);
371
- } else {
372
- // 追加到末尾
373
- if (!content.endsWith('\n')) content += '\n';
374
- content += `${k}=${v}\n`;
443
+ // 逐条合并到 .env 内容
444
+ for (const [k, v] of Object.entries(updates)) {
445
+ // 匹配已有行(包括被注释的行)
446
+ const activeRe = new RegExp(`^${k}\\s*=.*$`, 'm');
447
+ const commentedRe = new RegExp(`^#\\s*${k}\\s*=.*$`, 'm');
448
+
449
+ if (activeRe.test(content)) {
450
+ // 替换已有活动行
451
+ content = content.replace(activeRe, `${k}=${v}`);
452
+ } else if (commentedRe.test(content)) {
453
+ // 取消注释并赋值
454
+ content = content.replace(commentedRe, `${k}=${v}`);
455
+ } else {
456
+ // 追加到末尾
457
+ if (!content.endsWith('\n')) {
458
+ content += '\n';
459
+ }
460
+ content += `${k}=${v}\n`;
461
+ }
375
462
  }
376
- }
377
463
 
378
- writeFileSync(envPath, content);
379
- logger.info('LLM env config updated', { provider, model });
464
+ writeFileSync(envPath, content);
465
+ logger.info('LLM env config updated', { provider, model });
380
466
 
381
- // 同步到当前进程环境变量(热生效)
382
- for (const [k, v] of Object.entries(updates)) {
383
- process.env[k] = v;
384
- }
467
+ // 同步到当前进程环境变量(热生效)
468
+ for (const [k, v] of Object.entries(updates)) {
469
+ process.env[k] = v;
470
+ }
385
471
 
386
- // 尝试热切换 AI Provider
387
- try {
388
- const newProvider = createProvider({
389
- provider: provider.toLowerCase(),
390
- model: model || undefined,
391
- });
392
- const container = getServiceContainer();
393
- container.singletons.aiProvider = newProvider;
394
- logger.info('AI provider hot-swapped after env update', { provider, model: newProvider.model });
395
- } catch (err) {
396
- logger.debug('Hot-swap AI provider failed (will take effect on restart)', { error: err.message });
397
- }
472
+ // 尝试热切换 AI Provider(包括依赖 AI 的所有服务)
473
+ try {
474
+ const newProvider = createProvider({
475
+ provider: provider.toLowerCase(),
476
+ model: model || undefined,
477
+ });
478
+ const container = getServiceContainer();
479
+ container.reloadAiProvider(newProvider);
480
+ logger.info('AI provider hot-swapped after env update', {
481
+ provider,
482
+ model: newProvider.model,
483
+ });
484
+ } catch (err) {
485
+ logger.debug('Hot-swap AI provider failed (will take effect on restart)', {
486
+ error: err.message,
487
+ });
488
+ }
398
489
 
399
- const result = parseLlmEnv(envPath);
400
- res.json({ success: true, data: result });
401
- }));
490
+ const result = parseLlmEnv(envPath);
491
+ res.json({ success: true, data: result });
492
+ })
493
+ );
402
494
 
403
495
  // ═══════════════════════════════════════════════════════
404
496
  // SSE Streaming — 流式对话(Session + EventSource 架构)
@@ -424,35 +516,47 @@ router.post('/env-config', asyncHandler(async (req, res) => {
424
516
  * Body: { prompt: string, history?: Array<{role,content}> }
425
517
  * Response: { success: true, sessionId: string }
426
518
  */
427
- router.post('/chat/stream', asyncHandler(async (req, res) => {
428
- const { prompt, history = [] } = req.body;
429
- if (!prompt) throw new ValidationError('prompt is required');
430
-
431
- const chatAgent = getChatAgent();
432
- const session = createStreamSession('chat');
433
-
434
- logger.debug('SSE session created', { sessionId: session.sessionId });
435
-
436
- // 立即返回 sessionId(不等待 ChatAgent 执行)
437
- res.json({ success: true, sessionId: session.sessionId });
438
-
439
- // 后台执行 ChatAgent — 事件通过 session.send() 缓冲
440
- chatAgent.execute(prompt, {
441
- history,
442
- onProgress: (event) => session.send(event),
443
- }).then(result => {
444
- // text:start/delta/end 已由 ChatAgent.execute() 内部通过 onProgress 发送
445
- session.end({
446
- text: result.reply,
447
- toolCalls: result.toolCalls || [],
448
- hasContext: result.hasContext || false,
449
- });
450
- logger.debug('SSE session completed', { sessionId: session.sessionId, events: session.buffer.length });
451
- }).catch(err => {
452
- logger.warn('SSE session error', { sessionId: session.sessionId, error: err.message });
453
- session.error(err.message);
454
- });
455
- }));
519
+ router.post(
520
+ '/chat/stream',
521
+ asyncHandler(async (req, res) => {
522
+ const { prompt, history = [], lang } = req.body;
523
+ if (!prompt) {
524
+ throw new ValidationError('prompt is required');
525
+ }
526
+
527
+ const chatAgent = getChatAgent();
528
+ const session = createStreamSession('chat');
529
+
530
+ logger.debug('SSE session created', { sessionId: session.sessionId });
531
+
532
+ // 立即返回 sessionId(不等待 ChatAgent 执行)
533
+ res.json({ success: true, sessionId: session.sessionId });
534
+
535
+ // 后台执行 ChatAgent — 事件通过 session.send() 缓冲
536
+ chatAgent
537
+ .execute(prompt, {
538
+ history,
539
+ lang,
540
+ onProgress: (event) => session.send(event),
541
+ })
542
+ .then((result) => {
543
+ // text:start/delta/end 已由 ChatAgent.execute() 内部通过 onProgress 发送
544
+ session.end({
545
+ text: result.reply,
546
+ toolCalls: result.toolCalls || [],
547
+ hasContext: result.hasContext || false,
548
+ });
549
+ logger.debug('SSE session completed', {
550
+ sessionId: session.sessionId,
551
+ events: session.buffer.length,
552
+ });
553
+ })
554
+ .catch((err) => {
555
+ logger.warn('SSE session error', { sessionId: session.sessionId, error: err.message });
556
+ session.error(err.message);
557
+ });
558
+ })
559
+ );
456
560
 
457
561
  /**
458
562
  * GET /api/v1/ai/chat/events/:sessionId
@@ -485,7 +589,9 @@ router.get('/chat/events/:sessionId', (req, res) => {
485
589
 
486
590
  /** 写入一个 SSE data 行 */
487
591
  function writeEvent(event) {
488
- if (res.writableEnded) return;
592
+ if (res.writableEnded) {
593
+ return;
594
+ }
489
595
  const line = `data: ${JSON.stringify(event)}\n\n`;
490
596
  res.write(line);
491
597
  }
@@ -535,16 +641,32 @@ router.get('/chat/events/:sessionId', (req, res) => {
535
641
  * GET /api/v1/ai/token-usage
536
642
  * 近 7 日 Token 消耗报告(按日 + 按来源 + 总计)
537
643
  */
538
- router.get('/token-usage', asyncHandler(async (req, res) => {
539
- const container = getServiceContainer();
540
- let tokenStore;
541
- try {
542
- tokenStore = container.get('tokenUsageStore');
543
- } catch {
544
- return res.json({ success: true, data: { daily: [], bySource: [], summary: { input_tokens: 0, output_tokens: 0, total_tokens: 0, call_count: 0, avg_per_call: 0 } } });
545
- }
546
- const report = tokenStore.getLast7DaysReport();
547
- res.json({ success: true, data: report });
548
- }));
644
+ router.get(
645
+ '/token-usage',
646
+ asyncHandler(async (req, res) => {
647
+ const container = getServiceContainer();
648
+ let tokenStore;
649
+ try {
650
+ tokenStore = container.get('tokenUsageStore');
651
+ } catch {
652
+ return res.json({
653
+ success: true,
654
+ data: {
655
+ daily: [],
656
+ bySource: [],
657
+ summary: {
658
+ input_tokens: 0,
659
+ output_tokens: 0,
660
+ total_tokens: 0,
661
+ call_count: 0,
662
+ avg_per_call: 0,
663
+ },
664
+ },
665
+ });
666
+ }
667
+ const report = tokenStore.getLast7DaysReport();
668
+ res.json({ success: true, data: report });
669
+ })
670
+ );
549
671
 
550
672
  export default router;