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.
- package/README.md +230 -324
- package/bin/api-server.js +1 -1
- package/bin/cli.js +204 -244
- package/bin/mcp-server.js +5 -3
- package/config/knowledge-base.config.js +132 -132
- package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
- package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
- package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/bootstrap.js +8 -8
- package/lib/cli/AiScanService.js +86 -40
- package/lib/cli/KnowledgeSyncService.js +113 -74
- package/lib/cli/SetupService.js +439 -277
- package/lib/cli/UpgradeService.js +63 -100
- package/lib/core/AstAnalyzer.js +276 -597
- package/lib/core/ast/ProjectGraph.js +101 -40
- package/lib/core/ast/ensure-grammars.js +232 -0
- package/lib/core/ast/index.js +115 -0
- package/lib/core/ast/lang-dart.js +661 -0
- package/lib/core/ast/lang-go.js +530 -0
- package/lib/core/ast/lang-java.js +435 -0
- package/lib/core/ast/lang-javascript.js +272 -0
- package/lib/core/ast/lang-kotlin.js +423 -0
- package/lib/core/ast/lang-objc.js +388 -0
- package/lib/core/ast/lang-python.js +371 -0
- package/lib/core/ast/lang-swift.js +337 -0
- package/lib/core/ast/lang-typescript.js +503 -0
- package/lib/core/capability/CapabilityProbe.js +18 -9
- package/lib/core/constitution/Constitution.js +2 -3
- package/lib/core/constitution/ConstitutionValidator.js +65 -24
- package/lib/core/discovery/DartDiscoverer.js +534 -0
- package/lib/core/discovery/DiscovererRegistry.js +83 -0
- package/lib/core/discovery/GenericDiscoverer.js +225 -0
- package/lib/core/discovery/GoDiscoverer.js +541 -0
- package/lib/core/discovery/JvmDiscoverer.js +506 -0
- package/lib/core/discovery/NodeDiscoverer.js +466 -0
- package/lib/core/discovery/ProjectDiscoverer.js +93 -0
- package/lib/core/discovery/PythonDiscoverer.js +338 -0
- package/lib/core/discovery/SpmDiscoverer.js +5 -0
- package/lib/core/discovery/index.js +53 -0
- package/lib/core/enhancement/EnhancementPack.js +71 -0
- package/lib/core/enhancement/EnhancementRegistry.js +47 -0
- package/lib/core/enhancement/android-enhancement.js +102 -0
- package/lib/core/enhancement/django-enhancement.js +70 -0
- package/lib/core/enhancement/fastapi-enhancement.js +63 -0
- package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
- package/lib/core/enhancement/go-web-enhancement.js +201 -0
- package/lib/core/enhancement/index.js +65 -0
- package/lib/core/enhancement/node-server-enhancement.js +88 -0
- package/lib/core/enhancement/react-enhancement.js +86 -0
- package/lib/core/enhancement/spring-enhancement.js +112 -0
- package/lib/core/enhancement/vue-enhancement.js +96 -0
- package/lib/core/gateway/Gateway.js +8 -9
- package/lib/core/gateway/GatewayActionRegistry.js +1 -1
- package/lib/core/permission/PermissionManager.js +12 -8
- package/lib/domain/index.js +13 -9
- package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
- package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
- package/lib/domain/knowledge/Lifecycle.js +22 -22
- package/lib/domain/knowledge/index.js +9 -12
- package/lib/domain/knowledge/values/Constraints.js +31 -21
- package/lib/domain/knowledge/values/Content.js +21 -13
- package/lib/domain/knowledge/values/Quality.js +31 -18
- package/lib/domain/knowledge/values/Reasoning.js +20 -12
- package/lib/domain/knowledge/values/Relations.js +37 -25
- package/lib/domain/knowledge/values/Stats.js +18 -12
- package/lib/domain/knowledge/values/index.js +4 -3
- package/lib/domain/snippet/Snippet.js +35 -10
- package/lib/external/ai/AiFactory.js +48 -16
- package/lib/external/ai/AiProvider.js +184 -90
- package/lib/external/ai/providers/ClaudeProvider.js +25 -12
- package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
- package/lib/external/ai/providers/MockProvider.js +9 -3
- package/lib/external/ai/providers/OpenAiProvider.js +51 -29
- package/lib/external/mcp/McpServer.js +66 -36
- package/lib/external/mcp/errorHandler.js +23 -11
- package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
- package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
- package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
- package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
- package/lib/external/mcp/handlers/bootstrap.js +691 -168
- package/lib/external/mcp/handlers/browse.js +66 -22
- package/lib/external/mcp/handlers/candidate.js +118 -35
- package/lib/external/mcp/handlers/consolidated.js +49 -17
- package/lib/external/mcp/handlers/guard.js +104 -39
- package/lib/external/mcp/handlers/knowledge.js +60 -36
- package/lib/external/mcp/handlers/search.js +43 -14
- package/lib/external/mcp/handlers/skill.js +120 -45
- package/lib/external/mcp/handlers/structure.js +240 -86
- package/lib/external/mcp/handlers/system.js +42 -12
- package/lib/external/mcp/handlers/wiki.js +58 -33
- package/lib/external/mcp/tools.js +306 -123
- package/lib/http/HttpServer.js +72 -47
- package/lib/http/middleware/RateLimiter.js +5 -3
- package/lib/http/middleware/errorHandler.js +6 -1
- package/lib/http/middleware/requestLogger.js +14 -3
- package/lib/http/middleware/roleResolver.js +30 -23
- package/lib/http/routes/ai.js +387 -265
- package/lib/http/routes/auth.js +81 -61
- package/lib/http/routes/candidates.js +430 -320
- package/lib/http/routes/commands.js +289 -189
- package/lib/http/routes/extract.js +158 -125
- package/lib/http/routes/guardRules.js +309 -217
- package/lib/http/routes/knowledge.js +213 -154
- package/lib/http/routes/modules.js +578 -0
- package/lib/http/routes/monitoring.js +6 -6
- package/lib/http/routes/recipes.js +104 -93
- package/lib/http/routes/search.js +361 -305
- package/lib/http/routes/skills.js +145 -98
- package/lib/http/routes/snippets.js +42 -30
- package/lib/http/routes/spm.js +3 -405
- package/lib/http/routes/violations.js +113 -93
- package/lib/http/routes/wiki.js +211 -170
- package/lib/http/utils/routeHelpers.js +3 -1
- package/lib/http/utils/sse-sessions.js +16 -6
- package/lib/http/utils/sse.js +15 -5
- package/lib/infrastructure/audit/AuditLogger.js +5 -2
- package/lib/infrastructure/audit/AuditStore.js +10 -7
- package/lib/infrastructure/cache/CacheService.js +3 -1
- package/lib/infrastructure/cache/GraphCache.js +8 -4
- package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
- package/lib/infrastructure/config/ConfigLoader.js +9 -5
- package/lib/infrastructure/config/Defaults.js +30 -10
- package/lib/infrastructure/config/Paths.js +28 -8
- package/lib/infrastructure/config/TriggerSymbol.js +22 -10
- package/lib/infrastructure/database/DatabaseConnection.js +15 -10
- package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
- package/lib/infrastructure/external/ClipboardManager.js +6 -2
- package/lib/infrastructure/external/NativeUi.js +50 -43
- package/lib/infrastructure/external/OpenBrowser.js +14 -17
- package/lib/infrastructure/external/XcodeAutomation.js +14 -258
- package/lib/infrastructure/logging/Logger.js +46 -30
- package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
- package/lib/infrastructure/paths/HeaderResolver.js +25 -9
- package/lib/infrastructure/paths/PathFinder.js +34 -12
- package/lib/infrastructure/plugin/PluginManager.js +26 -8
- package/lib/infrastructure/realtime/RealtimeService.js +2 -2
- package/lib/infrastructure/vector/Chunker.js +22 -7
- package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
- package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
- package/lib/infrastructure/vector/VectorStore.js +28 -10
- package/lib/injection/ServiceContainer.js +247 -93
- package/lib/platform/ios/index.js +63 -0
- package/lib/platform/ios/routes/spm.js +437 -0
- package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
- package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
- package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
- package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
- package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
- package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
- package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
- package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
- package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
- package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
- package/lib/repository/base/BaseRepository.js +7 -9
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
- package/lib/repository/token/TokenUsageStore.js +4 -2
- package/lib/service/automation/ActionPipeline.js +1 -1
- package/lib/service/automation/AutomationOrchestrator.js +8 -4
- package/lib/service/automation/ContextCollector.js +7 -5
- package/lib/service/automation/DirectiveDetector.js +23 -16
- package/lib/service/automation/FileWatcher.js +112 -56
- package/lib/service/automation/TriggerResolver.js +6 -4
- package/lib/service/automation/handlers/AlinkHandler.js +24 -12
- package/lib/service/automation/handlers/CreateHandler.js +19 -20
- package/lib/service/automation/handlers/DraftHandler.js +14 -8
- package/lib/service/automation/handlers/GuardHandler.js +93 -63
- package/lib/service/automation/handlers/HeaderHandler.js +1 -6
- package/lib/service/automation/handlers/SearchHandler.js +155 -88
- package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
- package/lib/service/candidate/SimilarityService.js +25 -9
- package/lib/service/chat/AnalystAgent.js +50 -24
- package/lib/service/chat/CandidateGuardrail.js +143 -17
- package/lib/service/chat/ChatAgent.js +655 -260
- package/lib/service/chat/ContextWindow.js +116 -71
- package/lib/service/chat/ConversationStore.js +77 -36
- package/lib/service/chat/EpisodicConsolidator.js +47 -23
- package/lib/service/chat/HandoffProtocol.js +98 -22
- package/lib/service/chat/Memory.js +34 -14
- package/lib/service/chat/ProducerAgent.js +40 -20
- package/lib/service/chat/ProjectSemanticMemory.js +109 -78
- package/lib/service/chat/ReasoningLayer.js +148 -70
- package/lib/service/chat/ReasoningTrace.js +44 -32
- package/lib/service/chat/TaskPipeline.js +39 -19
- package/lib/service/chat/ToolRegistry.js +48 -29
- package/lib/service/chat/WorkingMemory.js +44 -18
- package/lib/service/chat/tools.js +1096 -494
- package/lib/service/context/RecipeExtractor.js +132 -51
- package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
- package/lib/service/cursor/KnowledgeCompressor.js +25 -22
- package/lib/service/cursor/RulesGenerator.js +13 -7
- package/lib/service/cursor/SkillsSyncer.js +77 -27
- package/lib/service/cursor/TokenBudget.js +2 -2
- package/lib/service/cursor/TopicClassifier.js +54 -20
- package/lib/service/guard/ComplianceReporter.js +55 -43
- package/lib/service/guard/ExclusionManager.js +67 -29
- package/lib/service/guard/GuardCheckEngine.js +381 -86
- package/lib/service/guard/GuardFeedbackLoop.js +22 -10
- package/lib/service/guard/GuardService.js +29 -19
- package/lib/service/guard/RuleLearner.js +55 -23
- package/lib/service/guard/SourceFileCollector.js +27 -20
- package/lib/service/guard/ViolationsStore.js +43 -38
- package/lib/service/knowledge/CodeEntityGraph.js +147 -82
- package/lib/service/knowledge/ConfidenceRouter.js +12 -10
- package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
- package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
- package/lib/service/knowledge/KnowledgeService.js +222 -112
- package/lib/service/module/ModuleService.js +969 -0
- package/lib/service/quality/FeedbackCollector.js +27 -15
- package/lib/service/quality/QualityScorer.js +78 -24
- package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
- package/lib/service/recipe/RecipeParser.js +78 -45
- package/lib/service/search/CoarseRanker.js +43 -28
- package/lib/service/search/CrossEncoderReranker.js +32 -21
- package/lib/service/search/InvertedIndex.js +21 -7
- package/lib/service/search/MultiSignalRanker.js +90 -28
- package/lib/service/search/RetrievalFunnel.js +45 -24
- package/lib/service/search/SearchEngine.js +255 -103
- package/lib/service/skills/EventAggregator.js +32 -15
- package/lib/service/skills/SignalCollector.js +140 -64
- package/lib/service/skills/SkillAdvisor.js +79 -42
- package/lib/service/skills/SkillHooks.js +16 -14
- package/lib/service/snippet/PlaceholderConverter.js +5 -0
- package/lib/service/snippet/SnippetFactory.js +116 -99
- package/lib/service/snippet/SnippetInstaller.js +234 -62
- package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
- package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
- package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
- package/lib/service/wiki/WikiGenerator.js +637 -263
- package/lib/shared/DimensionCopyRegistry.js +472 -0
- package/lib/shared/LanguageService.js +399 -0
- package/lib/shared/PathGuard.js +45 -28
- package/lib/shared/RecipeReadinessChecker.js +72 -12
- package/lib/shared/constants.js +41 -41
- package/lib/shared/errors/BaseError.js +2 -2
- package/lib/shared/errors/index.js +4 -4
- package/lib/shared/similarity.js +25 -8
- package/lib/shared/token-utils.js +6 -2
- package/lib/shared/utils/common.js +12 -4
- package/package.json +49 -13
- package/scripts/bench-real-projects.mjs +256 -0
- package/scripts/build-native-ui.js +30 -30
- package/scripts/clear-old-vector-index.js +5 -35
- package/scripts/clear-vector-cache.js +7 -37
- package/scripts/collect-test-project-stats.mjs +160 -0
- package/scripts/diagnose-mcp.js +41 -32
- package/scripts/ensure-parse-package.js +6 -9
- package/scripts/generate-recipe-drafts.js +116 -77
- package/scripts/init-db.js +3 -20
- package/scripts/init-snippets.js +305 -0
- package/scripts/init-vector-db.js +173 -170
- package/scripts/install-cursor-skill.js +148 -104
- package/scripts/install-full.js +8 -21
- package/scripts/install-vscode-copilot.js +146 -145
- package/scripts/migrate-md-to-knowledge.mjs +139 -151
- package/scripts/postinstall-safe.js +5 -17
- package/scripts/recipe-audit.js +106 -82
- package/scripts/release.js +283 -323
- package/scripts/setup-mcp-config.js +60 -52
- package/scripts/verify-context-api.js +20 -20
- package/skills/autosnippet-analysis/SKILL.md +10 -6
- package/skills/autosnippet-candidates/SKILL.md +27 -26
- package/skills/autosnippet-coldstart/SKILL.md +555 -38
- package/skills/autosnippet-concepts/SKILL.md +349 -337
- package/skills/autosnippet-create/SKILL.md +5 -5
- package/skills/autosnippet-reference-dart/SKILL.md +543 -0
- package/skills/autosnippet-reference-go/SKILL.md +539 -0
- package/skills/autosnippet-reference-java/SKILL.md +534 -0
- package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
- package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
- package/skills/autosnippet-reference-objc/SKILL.md +29 -6
- package/skills/autosnippet-reference-python/SKILL.md +800 -0
- package/skills/autosnippet-reference-swift/SKILL.md +70 -14
- package/skills/autosnippet-structure/SKILL.md +4 -4
- package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
- package/templates/recipes-setup/README.md +2 -2
- package/templates/recipes-setup/_template.md +1 -1
- package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
- package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
- package/resources/asd-entry/main.swift +0 -159
- package/scripts/build-asd-entry.js +0 -51
- package/scripts/init-xcode-snippets.js +0 -311
- package/template.json +0 -39
package/lib/http/routes/ai.js
CHANGED
|
@@ -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
|
|
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(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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(
|
|
63
|
-
|
|
102
|
+
router.post(
|
|
103
|
+
'/config',
|
|
104
|
+
asyncHandler(async (req, res) => {
|
|
105
|
+
const { provider, model } = req.body;
|
|
64
106
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
107
|
+
if (!provider || typeof provider !== 'string') {
|
|
108
|
+
throw new ValidationError('provider is required');
|
|
109
|
+
}
|
|
68
110
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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(
|
|
104
|
-
|
|
149
|
+
router.post(
|
|
150
|
+
'/summarize',
|
|
151
|
+
asyncHandler(async (req, res) => {
|
|
152
|
+
const { code, language } = req.body;
|
|
105
153
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
154
|
+
if (!code) {
|
|
155
|
+
throw new ValidationError('code is required');
|
|
156
|
+
}
|
|
109
157
|
|
|
110
|
-
|
|
111
|
-
|
|
158
|
+
const chatAgent = getChatAgent();
|
|
159
|
+
const result = await chatAgent.executeTool('summarize_code', { code, language });
|
|
112
160
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
161
|
+
if (result?.error) {
|
|
162
|
+
throw new ValidationError(result.error);
|
|
163
|
+
}
|
|
116
164
|
|
|
117
|
-
|
|
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(
|
|
125
|
-
|
|
173
|
+
router.post(
|
|
174
|
+
'/translate',
|
|
175
|
+
asyncHandler(async (req, res) => {
|
|
176
|
+
const { summary, usageGuide } = req.body;
|
|
126
177
|
|
|
127
|
-
|
|
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:
|
|
144
|
-
warning: result.error,
|
|
181
|
+
data: { summaryEn: '', usageGuideEn: '' },
|
|
145
182
|
});
|
|
146
183
|
}
|
|
147
184
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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(
|
|
164
|
-
|
|
215
|
+
router.post(
|
|
216
|
+
'/chat',
|
|
217
|
+
asyncHandler(async (req, res) => {
|
|
218
|
+
const { prompt, history = [], lang } = req.body;
|
|
165
219
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
220
|
+
if (!prompt) {
|
|
221
|
+
throw new ValidationError('prompt is required');
|
|
222
|
+
}
|
|
169
223
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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(
|
|
190
|
-
|
|
244
|
+
router.post(
|
|
245
|
+
'/agent/tool',
|
|
246
|
+
asyncHandler(async (req, res) => {
|
|
247
|
+
const { tool, params = {} } = req.body;
|
|
191
248
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
249
|
+
if (!tool) {
|
|
250
|
+
throw new ValidationError('tool name is required');
|
|
251
|
+
}
|
|
195
252
|
|
|
196
|
-
|
|
197
|
-
|
|
253
|
+
const chatAgent = getChatAgent();
|
|
254
|
+
const result = await chatAgent.executeTool(tool, params);
|
|
198
255
|
|
|
199
|
-
|
|
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(
|
|
208
|
-
|
|
265
|
+
router.post(
|
|
266
|
+
'/agent/task',
|
|
267
|
+
asyncHandler(async (req, res) => {
|
|
268
|
+
const { task, params = {} } = req.body;
|
|
209
269
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
270
|
+
if (!task) {
|
|
271
|
+
throw new ValidationError('task name is required');
|
|
272
|
+
}
|
|
213
273
|
|
|
214
|
-
|
|
215
|
-
|
|
274
|
+
const chatAgent = getChatAgent();
|
|
275
|
+
const result = await chatAgent.runTask(task, params);
|
|
216
276
|
|
|
217
|
-
|
|
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(
|
|
225
|
-
|
|
226
|
-
|
|
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(
|
|
235
|
-
|
|
298
|
+
router.post(
|
|
299
|
+
'/format-usage-guide',
|
|
300
|
+
asyncHandler(async (req, res) => {
|
|
301
|
+
const { text } = req.body;
|
|
236
302
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
303
|
+
if (!text) {
|
|
304
|
+
return res.json({ success: true, data: { formatted: '' } });
|
|
305
|
+
}
|
|
240
306
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
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 =
|
|
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('#'))
|
|
357
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
290
360
|
const eqIdx = trimmed.indexOf('=');
|
|
291
|
-
if (eqIdx === -1)
|
|
361
|
+
if (eqIdx === -1) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
292
364
|
const key = trimmed.slice(0, eqIdx).trim();
|
|
293
|
-
const val = trimmed
|
|
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: '',
|
|
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(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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(
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
338
|
-
|
|
417
|
+
const envPath = _getProjectEnvPath();
|
|
418
|
+
let content = existsSync(envPath) ? readFileSync(envPath, 'utf8') : '';
|
|
339
419
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
379
|
-
|
|
464
|
+
writeFileSync(envPath, content);
|
|
465
|
+
logger.info('LLM env config updated', { provider, model });
|
|
380
466
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
467
|
+
// 同步到当前进程环境变量(热生效)
|
|
468
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
469
|
+
process.env[k] = v;
|
|
470
|
+
}
|
|
385
471
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
400
|
-
|
|
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(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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)
|
|
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(
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
tokenStore
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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;
|