@gt-fe/eap-runtime 0.0.8
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 +323 -0
- package/dist/api/handlers/index.d.ts +3 -0
- package/dist/api/handlers/index.js +4 -0
- package/dist/api/handlers/invoke.handler.d.ts +77 -0
- package/dist/api/handlers/invoke.handler.js +411 -0
- package/dist/api/handlers/messages.handler.d.ts +53 -0
- package/dist/api/handlers/messages.handler.js +80 -0
- package/dist/api/handlers/stream.handler.d.ts +92 -0
- package/dist/api/handlers/stream.handler.js +432 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.js +4 -0
- package/dist/api/middleware/auth.middleware.d.ts +96 -0
- package/dist/api/middleware/auth.middleware.js +158 -0
- package/dist/api/middleware/index.d.ts +1 -0
- package/dist/api/middleware/index.js +2 -0
- package/dist/api/routes/agent.routes.d.ts +44 -0
- package/dist/api/routes/agent.routes.js +204 -0
- package/dist/api/routes/index.d.ts +3 -0
- package/dist/api/routes/index.js +4 -0
- package/dist/api/routes/session.routes.d.ts +82 -0
- package/dist/api/routes/session.routes.js +169 -0
- package/dist/api/routes/task.routes.d.ts +75 -0
- package/dist/api/routes/task.routes.js +108 -0
- package/dist/checkpoint/checkpoint-serializer.d.ts +58 -0
- package/dist/checkpoint/checkpoint-serializer.js +81 -0
- package/dist/checkpoint/composite-checkpoint-saver.d.ts +42 -0
- package/dist/checkpoint/composite-checkpoint-saver.js +118 -0
- package/dist/checkpoint/contracts/checkpoint-saver.contract.d.ts +65 -0
- package/dist/checkpoint/contracts/checkpoint-saver.contract.js +12 -0
- package/dist/checkpoint/contracts/index.d.ts +1 -0
- package/dist/checkpoint/contracts/index.js +1 -0
- package/dist/checkpoint/file-checkpoint-saver.d.ts +19 -0
- package/dist/checkpoint/file-checkpoint-saver.js +98 -0
- package/dist/checkpoint/hybrid-checkpoint-saver.d.ts +130 -0
- package/dist/checkpoint/hybrid-checkpoint-saver.js +211 -0
- package/dist/checkpoint/in-memory-checkpoint-saver.d.ts +14 -0
- package/dist/checkpoint/in-memory-checkpoint-saver.js +46 -0
- package/dist/checkpoint/index.d.ts +16 -0
- package/dist/checkpoint/index.js +13 -0
- package/dist/checkpoint/mongo-checkpoint-saver.d.ts +38 -0
- package/dist/checkpoint/mongo-checkpoint-saver.js +56 -0
- package/dist/checkpoint/pg-checkpoint-saver.d.ts +59 -0
- package/dist/checkpoint/pg-checkpoint-saver.js +140 -0
- package/dist/checkpoint/redis-checkpoint-saver.d.ts +68 -0
- package/dist/checkpoint/redis-checkpoint-saver.js +173 -0
- package/dist/checkpoint/restore-checkpoint.d.ts +33 -0
- package/dist/checkpoint/restore-checkpoint.js +33 -0
- package/dist/common/agent-code.d.ts +10 -0
- package/dist/common/agent-code.js +12 -0
- package/dist/common/concurrency.d.ts +79 -0
- package/dist/common/concurrency.js +121 -0
- package/dist/common/health.d.ts +40 -0
- package/dist/common/health.js +37 -0
- package/dist/common/index.d.ts +5 -0
- package/dist/common/index.js +5 -0
- package/dist/common/logger.d.ts +34 -0
- package/dist/common/logger.js +38 -0
- package/dist/common/queue-consumer.d.ts +125 -0
- package/dist/common/queue-consumer.js +159 -0
- package/dist/common/trace-id.d.ts +2 -0
- package/dist/common/trace-id.js +8 -0
- package/dist/common/types.d.ts +104 -0
- package/dist/common/types.js +14 -0
- package/dist/contracts/index.d.ts +11 -0
- package/dist/contracts/index.js +10 -0
- package/dist/contracts/permission-checker.d.ts +10 -0
- package/dist/contracts/permission-checker.js +4 -0
- package/dist/contracts/trace-collector.d.ts +211 -0
- package/dist/contracts/trace-collector.js +13 -0
- package/dist/governance/governance-client.d.ts +38 -0
- package/dist/governance/governance-client.js +95 -0
- package/dist/governance/index.d.ts +7 -0
- package/dist/governance/index.js +5 -0
- package/dist/governance/permission-guard.d.ts +19 -0
- package/dist/governance/permission-guard.js +59 -0
- package/dist/graph/adapters/index.d.ts +3 -0
- package/dist/graph/adapters/index.js +3 -0
- package/dist/graph/adapters/langgraph-checkpointer.d.ts +40 -0
- package/dist/graph/adapters/langgraph-checkpointer.js +211 -0
- package/dist/graph/adapters/langgraph.adapter.d.ts +190 -0
- package/dist/graph/adapters/langgraph.adapter.js +1303 -0
- package/dist/graph/adapters/subgraph-compiler.d.ts +39 -0
- package/dist/graph/adapters/subgraph-compiler.js +248 -0
- package/dist/graph/condition-evaluator.d.ts +57 -0
- package/dist/graph/condition-evaluator.js +162 -0
- package/dist/graph/contracts/executable-graph.d.ts +340 -0
- package/dist/graph/contracts/executable-graph.js +10 -0
- package/dist/graph/contracts/index.d.ts +2 -0
- package/dist/graph/contracts/index.js +2 -0
- package/dist/graph/contracts/state-graph.contract.d.ts +76 -0
- package/dist/graph/contracts/state-graph.contract.js +11 -0
- package/dist/graph/hitl/hitl-handler.d.ts +199 -0
- package/dist/graph/hitl/hitl-handler.js +480 -0
- package/dist/graph/hitl/index.d.ts +1 -0
- package/dist/graph/hitl/index.js +1 -0
- package/dist/graph/index.d.ts +7 -0
- package/dist/graph/index.js +7 -0
- package/dist/graph/nodes/index.d.ts +2 -0
- package/dist/graph/nodes/index.js +2 -0
- package/dist/graph/nodes/llm-node.d.ts +182 -0
- package/dist/graph/nodes/llm-node.js +281 -0
- package/dist/graph/nodes/skill-bind-node.d.ts +32 -0
- package/dist/graph/nodes/skill-bind-node.js +116 -0
- package/dist/graph/nodes/tool-node.d.ts +75 -0
- package/dist/graph/nodes/tool-node.js +324 -0
- package/dist/graph/resolve-graph-prompt.d.ts +5 -0
- package/dist/graph/resolve-graph-prompt.js +75 -0
- package/dist/graph/retry/budget-aware-retry.d.ts +91 -0
- package/dist/graph/retry/budget-aware-retry.js +126 -0
- package/dist/graph/retry/index.d.ts +9 -0
- package/dist/graph/retry/index.js +8 -0
- package/dist/graph/retry/node-retry.d.ts +123 -0
- package/dist/graph/retry/node-retry.js +235 -0
- package/dist/graph/skill-audit-recorder.d.ts +126 -0
- package/dist/graph/skill-audit-recorder.js +114 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +32 -0
- package/dist/memory/compression/compression-stats.d.ts +109 -0
- package/dist/memory/compression/compression-stats.js +144 -0
- package/dist/memory/compression/compression.repository.d.ts +62 -0
- package/dist/memory/compression/compression.repository.js +71 -0
- package/dist/memory/compression/index.d.ts +10 -0
- package/dist/memory/compression/index.js +15 -0
- package/dist/memory/compression/llm-summarize.d.ts +58 -0
- package/dist/memory/compression/llm-summarize.js +192 -0
- package/dist/memory/compression/sliding-window.compression.d.ts +60 -0
- package/dist/memory/compression/sliding-window.compression.js +175 -0
- package/dist/memory/index.d.ts +4 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/long-term-memory.service.d.ts +94 -0
- package/dist/memory/long-term-memory.service.js +120 -0
- package/dist/memory/message-compression.service.d.ts +128 -0
- package/dist/memory/message-compression.service.js +69 -0
- package/dist/memory/pg-memory.repository.d.ts +44 -0
- package/dist/memory/pg-memory.repository.js +108 -0
- package/dist/memory/semantic-retrieval.service.d.ts +148 -0
- package/dist/memory/semantic-retrieval.service.js +238 -0
- package/dist/providers/echo-llm-provider.d.ts +10 -0
- package/dist/providers/echo-llm-provider.js +87 -0
- package/dist/providers/http-llm-provider.d.ts +35 -0
- package/dist/providers/http-llm-provider.js +404 -0
- package/dist/providers/index.d.ts +7 -0
- package/dist/providers/index.js +5 -0
- package/dist/providers/local-llm-provider.d.ts +27 -0
- package/dist/providers/local-llm-provider.js +105 -0
- package/dist/providers/resolve-llm-model.d.ts +13 -0
- package/dist/providers/resolve-llm-model.js +30 -0
- package/dist/providers/routing-llm-provider.d.ts +28 -0
- package/dist/providers/routing-llm-provider.js +79 -0
- package/dist/registry/agent-models.d.ts +1 -0
- package/dist/registry/agent-models.js +1 -0
- package/dist/registry/agent-registry-client.d.ts +56 -0
- package/dist/registry/agent-registry-client.js +36 -0
- package/dist/registry/definition-cache.d.ts +37 -0
- package/dist/registry/definition-cache.js +82 -0
- package/dist/registry/deployment-snapshot.d.ts +99 -0
- package/dist/registry/deployment-snapshot.js +542 -0
- package/dist/registry/graph-loader.d.ts +134 -0
- package/dist/registry/graph-loader.js +985 -0
- package/dist/registry/index.d.ts +9 -0
- package/dist/registry/index.js +9 -0
- package/dist/registry/preload-errors.d.ts +13 -0
- package/dist/registry/preload-errors.js +35 -0
- package/dist/registry/preload-flags.d.ts +22 -0
- package/dist/registry/preload-flags.js +44 -0
- package/dist/registry/skill-io-guard.d.ts +25 -0
- package/dist/registry/skill-io-guard.js +98 -0
- package/dist/registry/skill-tree.d.ts +27 -0
- package/dist/registry/skill-tree.js +183 -0
- package/dist/runtime/agent-runtime.d.ts +106 -0
- package/dist/runtime/agent-runtime.js +777 -0
- package/dist/runtime/dev-runtime.d.ts +69 -0
- package/dist/runtime/dev-runtime.js +199 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/storage-profile.d.ts +50 -0
- package/dist/runtime/storage-profile.js +138 -0
- package/dist/runtime/types.d.ts +120 -0
- package/dist/runtime/types.js +9 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +161 -0
- package/dist/session/compression-middleware.d.ts +126 -0
- package/dist/session/compression-middleware.js +230 -0
- package/dist/session/file-message.repository.d.ts +25 -0
- package/dist/session/file-message.repository.js +78 -0
- package/dist/session/file-session.repository.d.ts +72 -0
- package/dist/session/file-session.repository.js +212 -0
- package/dist/session/index.d.ts +13 -0
- package/dist/session/index.js +12 -0
- package/dist/session/message.service.d.ts +80 -0
- package/dist/session/message.service.js +128 -0
- package/dist/session/persist-turn-messages.d.ts +145 -0
- package/dist/session/persist-turn-messages.js +325 -0
- package/dist/session/pg-message.repository.d.ts +49 -0
- package/dist/session/pg-message.repository.js +149 -0
- package/dist/session/pg-session.repository.d.ts +46 -0
- package/dist/session/pg-session.repository.js +117 -0
- package/dist/session/session-context.service.d.ts +49 -0
- package/dist/session/session-context.service.js +66 -0
- package/dist/session/session.service.d.ts +194 -0
- package/dist/session/session.service.js +166 -0
- package/dist/sse/connection-event-logger.d.ts +21 -0
- package/dist/sse/connection-event-logger.js +33 -0
- package/dist/sse/contracts/index.d.ts +1 -0
- package/dist/sse/contracts/index.js +1 -0
- package/dist/sse/contracts/sse-emitter.contract.d.ts +74 -0
- package/dist/sse/contracts/sse-emitter.contract.js +11 -0
- package/dist/sse/event-buffer.d.ts +20 -0
- package/dist/sse/event-buffer.js +45 -0
- package/dist/sse/events/code-events.d.ts +19 -0
- package/dist/sse/events/code-events.js +9 -0
- package/dist/sse/events/collaboration-events.d.ts +37 -0
- package/dist/sse/events/collaboration-events.js +11 -0
- package/dist/sse/events/connection-events.d.ts +13 -0
- package/dist/sse/events/connection-events.js +9 -0
- package/dist/sse/events/file-events.d.ts +29 -0
- package/dist/sse/events/file-events.js +10 -0
- package/dist/sse/events/hitl-events.d.ts +33 -0
- package/dist/sse/events/hitl-events.js +10 -0
- package/dist/sse/events/index.d.ts +107 -0
- package/dist/sse/events/index.js +96 -0
- package/dist/sse/events/plan-events.d.ts +38 -0
- package/dist/sse/events/plan-events.js +11 -0
- package/dist/sse/events/reasoning-events.d.ts +35 -0
- package/dist/sse/events/reasoning-events.js +13 -0
- package/dist/sse/events/response-events.d.ts +25 -0
- package/dist/sse/events/response-events.js +12 -0
- package/dist/sse/events/session-events.d.ts +43 -0
- package/dist/sse/events/session-events.js +12 -0
- package/dist/sse/events/skill-events.d.ts +48 -0
- package/dist/sse/events/skill-events.js +11 -0
- package/dist/sse/events/stats-events.d.ts +44 -0
- package/dist/sse/events/stats-events.js +12 -0
- package/dist/sse/events/tool-events.d.ts +68 -0
- package/dist/sse/events/tool-events.js +14 -0
- package/dist/sse/index.d.ts +8 -0
- package/dist/sse/index.js +8 -0
- package/dist/sse/sse-connection-manager.d.ts +116 -0
- package/dist/sse/sse-connection-manager.js +185 -0
- package/dist/sse/sse-emitter.d.ts +108 -0
- package/dist/sse/sse-emitter.js +210 -0
- package/dist/sse/sse-heartbeat.d.ts +58 -0
- package/dist/sse/sse-heartbeat.js +76 -0
- package/dist/sse/trace-collector.d.ts +60 -0
- package/dist/sse/trace-collector.js +107 -0
- package/dist/task/contracts/index.d.ts +2 -0
- package/dist/task/contracts/index.js +1 -0
- package/dist/task/contracts/task-manager.contract.d.ts +160 -0
- package/dist/task/contracts/task-manager.contract.js +10 -0
- package/dist/task/index.d.ts +7 -0
- package/dist/task/index.js +5 -0
- package/dist/task/pg-task.repository.d.ts +47 -0
- package/dist/task/pg-task.repository.js +131 -0
- package/dist/task/task-progress-tracker.d.ts +67 -0
- package/dist/task/task-progress-tracker.js +80 -0
- package/dist/task/task-timeout.d.ts +70 -0
- package/dist/task/task-timeout.js +116 -0
- package/dist/task/task.service.d.ts +84 -0
- package/dist/task/task.service.js +233 -0
- package/dist/token/compression-budget-integration.d.ts +145 -0
- package/dist/token/compression-budget-integration.js +185 -0
- package/dist/token/context-window-budget.d.ts +113 -0
- package/dist/token/context-window-budget.js +134 -0
- package/dist/token/contracts/index.d.ts +1 -0
- package/dist/token/contracts/index.js +1 -0
- package/dist/token/contracts/token-budget.contract.d.ts +77 -0
- package/dist/token/contracts/token-budget.contract.js +11 -0
- package/dist/token/index.d.ts +12 -0
- package/dist/token/index.js +10 -0
- package/dist/token/pg-token-budget.repository.d.ts +39 -0
- package/dist/token/pg-token-budget.repository.js +116 -0
- package/dist/token/session-budget-checker.d.ts +54 -0
- package/dist/token/session-budget-checker.js +72 -0
- package/dist/token/token-budget-guard.d.ts +66 -0
- package/dist/token/token-budget-guard.js +97 -0
- package/dist/token/token-budget.service.d.ts +86 -0
- package/dist/token/token-budget.service.js +130 -0
- package/dist/token/token-detail-mode.d.ts +73 -0
- package/dist/token/token-detail-mode.js +107 -0
- package/dist/token/token-estimator.d.ts +80 -0
- package/dist/token/token-estimator.js +134 -0
- package/dist/token/token-stats.service.d.ts +88 -0
- package/dist/token/token-stats.service.js +94 -0
- package/dist/tool/create-tool-executor.d.ts +33 -0
- package/dist/tool/create-tool-executor.js +60 -0
- package/dist/tool/dev-http-executor.d.ts +18 -0
- package/dist/tool/dev-http-executor.js +96 -0
- package/dist/tool/dev-mcp-executor.d.ts +29 -0
- package/dist/tool/dev-mcp-executor.js +84 -0
- package/dist/tool/dev-tool-executor.d.ts +23 -0
- package/dist/tool/dev-tool-executor.js +55 -0
- package/dist/tool/file-artifact.d.ts +11 -0
- package/dist/tool/file-artifact.js +90 -0
- package/dist/tool/http-tool-executor.d.ts +17 -0
- package/dist/tool/http-tool-executor.js +158 -0
- package/dist/tool/index.d.ts +18 -0
- package/dist/tool/index.js +17 -0
- package/dist/tool/loaded-tool-definition.d.ts +35 -0
- package/dist/tool/loaded-tool-definition.js +9 -0
- package/dist/tool/local-mcp-client.d.ts +23 -0
- package/dist/tool/local-mcp-client.js +225 -0
- package/dist/tool/local-tool-executor.d.ts +37 -0
- package/dist/tool/local-tool-executor.js +249 -0
- package/dist/tool/mcp-tool-executor.d.ts +23 -0
- package/dist/tool/mcp-tool-executor.js +160 -0
- package/dist/tool/mock-tool-executor.d.ts +28 -0
- package/dist/tool/mock-tool-executor.js +65 -0
- package/dist/tool/system-prompt-builder.d.ts +114 -0
- package/dist/tool/system-prompt-builder.js +213 -0
- package/dist/tool/tool-confirmation.d.ts +29 -0
- package/dist/tool/tool-confirmation.js +81 -0
- package/dist/tool/tool-event-emitter.d.ts +58 -0
- package/dist/tool/tool-event-emitter.js +154 -0
- package/dist/tool/tool-execution-router.d.ts +25 -0
- package/dist/tool/tool-execution-router.js +50 -0
- package/dist/tool/tool-executor.interface.d.ts +85 -0
- package/dist/tool/tool-executor.interface.js +7 -0
- package/dist/tool/tool-orchestrator.d.ts +22 -0
- package/dist/tool/tool-orchestrator.js +104 -0
- package/dist/tool/tool-result-injector.d.ts +64 -0
- package/dist/tool/tool-result-injector.js +65 -0
- package/dist/utils/format-error.d.ts +4 -0
- package/dist/utils/format-error.js +4 -0
- package/dist/utils/http-error.d.ts +11 -0
- package/dist/utils/http-error.js +24 -0
- package/package.json +56 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Node — LLM 执行节点
|
|
3
|
+
*
|
|
4
|
+
* 代表 Graph 中的 LLM 推理节点,支持:
|
|
5
|
+
* - 流式输出(逐 token 推送)
|
|
6
|
+
* - Token 计数(inputTokens/outputTokens)
|
|
7
|
+
* - 重试 + 指数退避(默认 3 次)
|
|
8
|
+
*
|
|
9
|
+
* @see design.md §1.2 LLM 节点执行流程
|
|
10
|
+
* @see requirements.md Requirement 1 AC6, Requirement 9
|
|
11
|
+
*/
|
|
12
|
+
import { formatUnknownError } from '../../utils/format-error.js';
|
|
13
|
+
// ─────────────────────────────────────────────
|
|
14
|
+
// Default retry config
|
|
15
|
+
// ─────────────────────────────────────────────
|
|
16
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
17
|
+
maxRetries: 3,
|
|
18
|
+
backoff: {
|
|
19
|
+
type: 'exponential',
|
|
20
|
+
baseDelay: 1000,
|
|
21
|
+
maxDelay: 30000,
|
|
22
|
+
jitter: 0.1,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
// ─────────────────────────────────────────────
|
|
26
|
+
// LLMNode
|
|
27
|
+
// ─────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* LLMNode — LLM 执行节点
|
|
30
|
+
*
|
|
31
|
+
* 在 StateGraph 中作为 node function 注册,负责:
|
|
32
|
+
* 1. 从 state 中提取 messages
|
|
33
|
+
* 2. 调用 ILLMProvider 执行推理(支持 invoke / stream 模式)
|
|
34
|
+
* 3. 统计 Token 消耗
|
|
35
|
+
* 4. 失败时按 NodeRetryConfig 重试
|
|
36
|
+
* 5. 将 LLM 响应追加到 state.messages 并返回
|
|
37
|
+
*/
|
|
38
|
+
export class LLMNode {
|
|
39
|
+
config;
|
|
40
|
+
provider;
|
|
41
|
+
retryConfig;
|
|
42
|
+
nodeId;
|
|
43
|
+
streaming;
|
|
44
|
+
onChunk;
|
|
45
|
+
/**
|
|
46
|
+
* @param nodeId - 节点唯一标识
|
|
47
|
+
* @param config - LLM 节点配置
|
|
48
|
+
* @param provider - LLM 调用提供者
|
|
49
|
+
* @param streaming - 是否启用流式输出(默认 false)
|
|
50
|
+
* @param onChunk - 流式模式下每个文本片段回调(用于 SSE response.chunk)
|
|
51
|
+
*/
|
|
52
|
+
constructor(nodeId, config, provider, streaming = false, onChunk) {
|
|
53
|
+
this.nodeId = nodeId;
|
|
54
|
+
this.config = config;
|
|
55
|
+
this.provider = provider;
|
|
56
|
+
this.streaming = streaming;
|
|
57
|
+
this.onChunk = onChunk;
|
|
58
|
+
this.retryConfig = {
|
|
59
|
+
maxRetries: config.retry?.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries,
|
|
60
|
+
backoff: config.retry?.backoff ?? DEFAULT_RETRY_CONFIG.backoff,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* execute — StateGraph 节点函数
|
|
65
|
+
*
|
|
66
|
+
* 从 state 提取 messages → 调用 LLM → 处理重试 → 返回更新后的 state
|
|
67
|
+
*/
|
|
68
|
+
async execute(state) {
|
|
69
|
+
const messages = this.extractMessages(state);
|
|
70
|
+
// 注入 system prompt
|
|
71
|
+
const messagesWithSystem = this.prependSystemPrompt(messages);
|
|
72
|
+
const startedAt = Date.now();
|
|
73
|
+
// 带重试的 LLM 调用
|
|
74
|
+
const response = await this.invokeWithRetry(messagesWithSystem);
|
|
75
|
+
const durationMs = Date.now() - startedAt;
|
|
76
|
+
// 构建 assistant message(保留 reasoningContent 供后续 HTTP 回传)
|
|
77
|
+
const assistantMessage = {
|
|
78
|
+
role: 'assistant',
|
|
79
|
+
content: response.content,
|
|
80
|
+
toolCalls: response.toolCalls?.map((tc) => ({
|
|
81
|
+
id: tc.id,
|
|
82
|
+
name: tc.name,
|
|
83
|
+
arguments: tc.arguments,
|
|
84
|
+
})),
|
|
85
|
+
...(response.reasoningContent
|
|
86
|
+
? { reasoningContent: response.reasoningContent }
|
|
87
|
+
: {}),
|
|
88
|
+
};
|
|
89
|
+
// 返回更新后的 state
|
|
90
|
+
//
|
|
91
|
+
// 重要:messages channel 的 reducer 是 **append 语义**([...current, ...update]),
|
|
92
|
+
// 节点只需返回 **新增消息数组**,reducer 会自动追加到 current。
|
|
93
|
+
// 如果这里返回 [...currentMessages, newMsg],会导致 messages 被追加两次
|
|
94
|
+
// (即 user 消息重复的根因)。
|
|
95
|
+
//
|
|
96
|
+
// __llmTokenUsage channel 同样是 **数组累加器 reducer**,每次追加一条使用明细。
|
|
97
|
+
return {
|
|
98
|
+
messages: [assistantMessage],
|
|
99
|
+
currentNode: this.nodeId,
|
|
100
|
+
__llmTokenUsage: [
|
|
101
|
+
{
|
|
102
|
+
nodeId: this.nodeId,
|
|
103
|
+
model: response.model,
|
|
104
|
+
inputTokens: response.inputTokens,
|
|
105
|
+
outputTokens: response.outputTokens,
|
|
106
|
+
durationMs,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 获取节点 ID
|
|
113
|
+
*/
|
|
114
|
+
getNodeId() {
|
|
115
|
+
return this.nodeId;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 获取节点配置
|
|
119
|
+
*/
|
|
120
|
+
getConfig() {
|
|
121
|
+
return { ...this.config };
|
|
122
|
+
}
|
|
123
|
+
// ─────────────────────────────────────────────
|
|
124
|
+
// Private methods
|
|
125
|
+
// ─────────────────────────────────────────────
|
|
126
|
+
/**
|
|
127
|
+
* 从 state 中提取 messages 并转换为 GraphMessage[]
|
|
128
|
+
*/
|
|
129
|
+
extractMessages(state) {
|
|
130
|
+
const raw = state['messages'];
|
|
131
|
+
if (!Array.isArray(raw)) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
return raw.map((msg) => {
|
|
135
|
+
if (typeof msg === 'object' && msg !== null) {
|
|
136
|
+
const m = msg;
|
|
137
|
+
const toolCalls = (m['toolCalls'] ?? m['tool_calls']);
|
|
138
|
+
return {
|
|
139
|
+
role: m['role'] ?? 'user',
|
|
140
|
+
content: m['content'] ?? '',
|
|
141
|
+
toolCalls,
|
|
142
|
+
toolCallId: typeof m['toolCallId'] === 'string'
|
|
143
|
+
? m['toolCallId']
|
|
144
|
+
: typeof m['tool_call_id'] === 'string'
|
|
145
|
+
? m['tool_call_id']
|
|
146
|
+
: undefined,
|
|
147
|
+
toolName: typeof m['toolName'] === 'string' ? m['toolName'] : undefined,
|
|
148
|
+
success: typeof m['success'] === 'boolean' ? m['success'] : undefined,
|
|
149
|
+
reasoningContent: typeof m['reasoningContent'] === 'string'
|
|
150
|
+
? m['reasoningContent']
|
|
151
|
+
: typeof m['reasoning_content'] === 'string'
|
|
152
|
+
? m['reasoning_content']
|
|
153
|
+
: undefined,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return { role: 'user', content: String(msg) };
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* 在 messages 前追加 system prompt(兑底注入)
|
|
161
|
+
*
|
|
162
|
+
* 设计说明:
|
|
163
|
+
* - 当 config.systemPrompt 已设置时,HttpLLMProvider.buildRequestBody 已基于
|
|
164
|
+
* config.systemPrompt 注入过 system message,此处**不再重复添加**,避免 messages
|
|
165
|
+
* 里出现两条相同的 system。
|
|
166
|
+
* - 仅当 config.systemPrompt 未设置、且 messages 首条不是 system 时,才兑底加一条
|
|
167
|
+
* 空 system 占位(某些上游调用可能不通过 HttpLLMProvider,保留此兑底安全)。
|
|
168
|
+
*/
|
|
169
|
+
prependSystemPrompt(messages) {
|
|
170
|
+
// config.systemPrompt 已设:由 provider 统一注入,此处跳过(消除重复)
|
|
171
|
+
if (this.config.systemPrompt) {
|
|
172
|
+
return messages;
|
|
173
|
+
}
|
|
174
|
+
// config 未设 + 首条已是 system:保持原样
|
|
175
|
+
if (messages.length > 0 && messages[0].role === 'system') {
|
|
176
|
+
return messages;
|
|
177
|
+
}
|
|
178
|
+
// config 未设 + 首条非 system:无 system 可加,保持原样
|
|
179
|
+
return messages;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 带重试逻辑的 LLM 调用
|
|
183
|
+
*
|
|
184
|
+
* 根据 streaming 配置选择 invoke 或 stream 模式。
|
|
185
|
+
* 失败时按 backoff 策略重试,最多重试 maxRetries 次。
|
|
186
|
+
*/
|
|
187
|
+
async invokeWithRetry(messages) {
|
|
188
|
+
let lastError;
|
|
189
|
+
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
|
|
190
|
+
try {
|
|
191
|
+
if (this.streaming) {
|
|
192
|
+
return await this.invokeStreaming(messages);
|
|
193
|
+
}
|
|
194
|
+
return await this.provider.invoke(messages, this.config);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
lastError = error instanceof Error ? error : new Error(formatUnknownError(error));
|
|
198
|
+
// 最后一次尝试不需要等待
|
|
199
|
+
if (attempt < this.retryConfig.maxRetries) {
|
|
200
|
+
const delay = this.calculateBackoffDelay(attempt);
|
|
201
|
+
await this.sleep(delay);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
throw new Error(`LLMNode "${this.nodeId}" failed after ${this.retryConfig.maxRetries + 1} attempts: ${lastError ? formatUnknownError(lastError) : 'unknown error'}`);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 流式调用 LLM 并聚合为完整 LLMResponse
|
|
209
|
+
*/
|
|
210
|
+
async invokeStreaming(messages) {
|
|
211
|
+
const chunks = [];
|
|
212
|
+
const reasoningParts = [];
|
|
213
|
+
let inputTokens = 0;
|
|
214
|
+
let outputTokens = 0;
|
|
215
|
+
let toolCalls;
|
|
216
|
+
const iterator = this.provider.stream(messages, this.config);
|
|
217
|
+
for await (const chunk of iterator) {
|
|
218
|
+
if (chunk.content && this.onChunk) {
|
|
219
|
+
this.onChunk(chunk.content);
|
|
220
|
+
}
|
|
221
|
+
chunks.push(chunk.content);
|
|
222
|
+
if (chunk.reasoningContent) {
|
|
223
|
+
reasoningParts.push(chunk.reasoningContent);
|
|
224
|
+
}
|
|
225
|
+
// 勿用「!== undefined」接受 0:finish 包常先带 0,usage 包后到
|
|
226
|
+
if (typeof chunk.inputTokens === 'number' && chunk.inputTokens > 0) {
|
|
227
|
+
inputTokens = chunk.inputTokens;
|
|
228
|
+
}
|
|
229
|
+
if (typeof chunk.outputTokens === 'number' && chunk.outputTokens > 0) {
|
|
230
|
+
outputTokens = chunk.outputTokens;
|
|
231
|
+
}
|
|
232
|
+
if (chunk.toolCalls?.length) {
|
|
233
|
+
toolCalls = chunk.toolCalls;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const reasoningContent = reasoningParts.join('');
|
|
237
|
+
return {
|
|
238
|
+
content: chunks.join(''),
|
|
239
|
+
toolCalls,
|
|
240
|
+
...(reasoningContent ? { reasoningContent } : {}),
|
|
241
|
+
inputTokens,
|
|
242
|
+
outputTokens,
|
|
243
|
+
model: this.config.model,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* 计算退避延迟(支持 exponential / linear / fixed)
|
|
248
|
+
*/
|
|
249
|
+
calculateBackoffDelay(attempt) {
|
|
250
|
+
const backoff = this.retryConfig.backoff;
|
|
251
|
+
let delay;
|
|
252
|
+
switch (backoff.type) {
|
|
253
|
+
case 'exponential':
|
|
254
|
+
delay = backoff.baseDelay * Math.pow(2, attempt);
|
|
255
|
+
break;
|
|
256
|
+
case 'linear':
|
|
257
|
+
delay = backoff.baseDelay * (attempt + 1);
|
|
258
|
+
break;
|
|
259
|
+
case 'fixed':
|
|
260
|
+
delay = backoff.baseDelay;
|
|
261
|
+
break;
|
|
262
|
+
default:
|
|
263
|
+
delay = backoff.baseDelay;
|
|
264
|
+
}
|
|
265
|
+
// 应用最大延迟限制
|
|
266
|
+
delay = Math.min(delay, backoff.maxDelay);
|
|
267
|
+
// 应用 jitter(如果配置了抖动因子)
|
|
268
|
+
if (backoff.jitter && backoff.jitter > 0) {
|
|
269
|
+
const jitterRange = delay * backoff.jitter;
|
|
270
|
+
delay += Math.random() * jitterRange * 2 - jitterRange;
|
|
271
|
+
delay = Math.max(0, delay);
|
|
272
|
+
}
|
|
273
|
+
return Math.round(delay);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* 延迟等待
|
|
277
|
+
*/
|
|
278
|
+
sleep(ms) {
|
|
279
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 可选图胶水:把 silent LLM 的结构化输出写成 activeSkillPrompt / skillRoute。
|
|
3
|
+
* 不是 Graph 节点类型。任何「多本手册互斥」的域图都可选用;字段是约定不是契约。
|
|
4
|
+
*/
|
|
5
|
+
import type { LoadedSkillInfo, LoadedSkillMap } from '../../tool/system-prompt-builder.js';
|
|
6
|
+
export type SkillRoute = 'skill' | 'clarify' | 'none';
|
|
7
|
+
export interface SkillBindPatch {
|
|
8
|
+
skillRoute: SkillRoute;
|
|
9
|
+
activatedSkill: string | null;
|
|
10
|
+
/** 展示名;未选中时为空 */
|
|
11
|
+
skillName?: string;
|
|
12
|
+
activeSkillPrompt?: string;
|
|
13
|
+
skillInput?: Record<string, unknown>;
|
|
14
|
+
messages: Array<Record<string, unknown>>;
|
|
15
|
+
/** 用户写了 skill 名但清单里没有,才算 unmatched */
|
|
16
|
+
unmatched?: boolean;
|
|
17
|
+
}
|
|
18
|
+
interface SkillSelectPayload {
|
|
19
|
+
activatedSkill?: unknown;
|
|
20
|
+
needsClarification?: unknown;
|
|
21
|
+
clarificationQuestion?: unknown;
|
|
22
|
+
params?: unknown;
|
|
23
|
+
confidence?: unknown;
|
|
24
|
+
}
|
|
25
|
+
export declare function extractJsonObject(text: string): SkillSelectPayload | null;
|
|
26
|
+
export declare function resolveSkillDefinition(defs: LoadedSkillMap | undefined, rawName: string): LoadedSkillInfo | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* 消费选择节点刚写出的 assistant JSON,改写为路由 + Skill 提示词。
|
|
29
|
+
* 返回的 messages 是**整段替换**(调用方须覆盖 messages channel 时改用 replace 语义)。
|
|
30
|
+
*/
|
|
31
|
+
export declare function bindSelectedSkill(state: Record<string, unknown>): SkillBindPatch;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
function lastAssistantText(messages) {
|
|
2
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3
|
+
const msg = messages[i];
|
|
4
|
+
if (!msg || msg.role !== 'assistant')
|
|
5
|
+
continue;
|
|
6
|
+
return typeof msg.content === 'string' ? msg.content : '';
|
|
7
|
+
}
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
export function extractJsonObject(text) {
|
|
11
|
+
const trimmed = text.trim();
|
|
12
|
+
if (!trimmed)
|
|
13
|
+
return null;
|
|
14
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
15
|
+
const candidate = fenced?.[1]?.trim() ?? trimmed;
|
|
16
|
+
const start = candidate.indexOf('{');
|
|
17
|
+
const end = candidate.lastIndexOf('}');
|
|
18
|
+
if (start < 0 || end <= start)
|
|
19
|
+
return null;
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(candidate.slice(start, end + 1));
|
|
22
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
23
|
+
? parsed
|
|
24
|
+
: null;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function resolveSkillDefinition(defs, rawName) {
|
|
31
|
+
if (!defs || !rawName || rawName === 'none')
|
|
32
|
+
return undefined;
|
|
33
|
+
if (defs[rawName])
|
|
34
|
+
return defs[rawName];
|
|
35
|
+
const needle = rawName.trim().toLowerCase();
|
|
36
|
+
return Object.values(defs).find((skill) => {
|
|
37
|
+
return (skill.skillId.toLowerCase() === needle ||
|
|
38
|
+
(skill.name && skill.name.toLowerCase() === needle));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function formatParams(params) {
|
|
42
|
+
return Object.entries(params)
|
|
43
|
+
.map(([key, value]) => `- ${key}: ${JSON.stringify(value)}`)
|
|
44
|
+
.join('\n');
|
|
45
|
+
}
|
|
46
|
+
function buildSkillPrompt(skill, params) {
|
|
47
|
+
const parts = [`# 技能:${skill.name || skill.skillId}`];
|
|
48
|
+
if (skill.description) {
|
|
49
|
+
parts.push(skill.description);
|
|
50
|
+
}
|
|
51
|
+
if (params && Object.keys(params).length > 0) {
|
|
52
|
+
parts.push(`# 提取的参数\n${formatParams(params)}`);
|
|
53
|
+
}
|
|
54
|
+
if (skill.prompt?.trim()) {
|
|
55
|
+
parts.push(skill.prompt.trim());
|
|
56
|
+
}
|
|
57
|
+
return parts.join('\n\n');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 消费选择节点刚写出的 assistant JSON,改写为路由 + Skill 提示词。
|
|
61
|
+
* 返回的 messages 是**整段替换**(调用方须覆盖 messages channel 时改用 replace 语义)。
|
|
62
|
+
*/
|
|
63
|
+
export function bindSelectedSkill(state) {
|
|
64
|
+
const incoming = Array.isArray(state.messages) ? [...state.messages] : [];
|
|
65
|
+
const fromState = (typeof state.silentOutput === 'string' && state.silentOutput)
|
|
66
|
+
|| (typeof state.skillSelectRaw === 'string' ? state.skillSelectRaw : '');
|
|
67
|
+
const lastText = lastAssistantText(incoming);
|
|
68
|
+
const lastIsSelector = extractJsonObject(lastText) !== null;
|
|
69
|
+
const parsed = extractJsonObject(fromState) ?? extractJsonObject(lastText);
|
|
70
|
+
const defs = state.skillDefinitions;
|
|
71
|
+
const nextMessages = () => {
|
|
72
|
+
const next = incoming.filter((item) => Boolean(item) && typeof item === 'object');
|
|
73
|
+
if (!lastIsSelector)
|
|
74
|
+
return next;
|
|
75
|
+
for (let i = next.length - 1; i >= 0; i--) {
|
|
76
|
+
if (next[i]?.role === 'assistant') {
|
|
77
|
+
next.splice(i, 1);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return next;
|
|
82
|
+
};
|
|
83
|
+
if (!parsed) {
|
|
84
|
+
return { skillRoute: 'none', activatedSkill: null, messages: nextMessages() };
|
|
85
|
+
}
|
|
86
|
+
const needsClarification = parsed.needsClarification === true;
|
|
87
|
+
const question = typeof parsed.clarificationQuestion === 'string' ? parsed.clarificationQuestion.trim() : '';
|
|
88
|
+
if (needsClarification && question) {
|
|
89
|
+
const messages = nextMessages();
|
|
90
|
+
messages.push({ role: 'assistant', content: question });
|
|
91
|
+
return { skillRoute: 'clarify', activatedSkill: null, messages };
|
|
92
|
+
}
|
|
93
|
+
const rawName = typeof parsed.activatedSkill === 'string' ? parsed.activatedSkill.trim() : '';
|
|
94
|
+
const params = parsed.params && typeof parsed.params === 'object' && !Array.isArray(parsed.params)
|
|
95
|
+
? parsed.params
|
|
96
|
+
: undefined;
|
|
97
|
+
const skill = resolveSkillDefinition(defs, rawName);
|
|
98
|
+
if (!skill) {
|
|
99
|
+
const asked = Boolean(rawName && rawName !== 'none');
|
|
100
|
+
return {
|
|
101
|
+
skillRoute: 'none',
|
|
102
|
+
activatedSkill: null,
|
|
103
|
+
skillInput: params,
|
|
104
|
+
messages: nextMessages(),
|
|
105
|
+
unmatched: asked,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
skillRoute: 'skill',
|
|
110
|
+
activatedSkill: skill.skillId,
|
|
111
|
+
skillName: skill.name || skill.skillId,
|
|
112
|
+
activeSkillPrompt: buildSkillPrompt(skill, params),
|
|
113
|
+
skillInput: params,
|
|
114
|
+
messages: nextMessages(),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolNode — Tool 执行节点
|
|
3
|
+
*
|
|
4
|
+
* 在 StateGraph 中作为 node function 注册,负责:
|
|
5
|
+
* 1. 从 state 中提取最后一条 assistant 消息的 toolCalls
|
|
6
|
+
* 2. 通过 ToolOrchestrator.executeAll() 并行执行所有 Tool 调用
|
|
7
|
+
* 3. 通过 ToolResultInjector 将执行结果注入 state
|
|
8
|
+
*
|
|
9
|
+
* @see design.md §1.4 ToolNode: LangGraph的ToolNode封装
|
|
10
|
+
* @see requirements.md Requirement 8 AC1, AC3, AC5
|
|
11
|
+
*/
|
|
12
|
+
import type { AuthContext } from '../../tool/tool-executor.interface.js';
|
|
13
|
+
import type { ToolOrchestrator } from '../../tool/tool-orchestrator.js';
|
|
14
|
+
import type { ToolResultInjector } from '../../tool/tool-result-injector.js';
|
|
15
|
+
import type { HitlHandler } from '../hitl/hitl-handler.js';
|
|
16
|
+
/**
|
|
17
|
+
* LangGraphState — LangGraph 内部状态(与 LLMNode 对齐)
|
|
18
|
+
*/
|
|
19
|
+
type LangGraphState = Record<string, unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* ToolNode — Tool 执行节点(封装 ToolOrchestrator)
|
|
22
|
+
*
|
|
23
|
+
* 职责:
|
|
24
|
+
* 1. 从 state.messages 中找到最后一条 assistant 消息的 toolCalls
|
|
25
|
+
* 2. 将 toolCalls 转换为 ToolCallRequest[](附带 authContext)
|
|
26
|
+
* 3. 调用 orchestrator.executeAll() 并行执行
|
|
27
|
+
* 4. 调用 injector.injectResults() 将结果写回 state
|
|
28
|
+
* 5. 返回更新后的 state
|
|
29
|
+
*
|
|
30
|
+
* 支持多 Tool 并行调用,LLM 一次返回多个 tool_call 时全部并行执行。
|
|
31
|
+
*/
|
|
32
|
+
export interface ToolNodeProgress {
|
|
33
|
+
toolCallId: string;
|
|
34
|
+
toolName: string;
|
|
35
|
+
status: 'started' | 'completed' | 'error';
|
|
36
|
+
arguments?: Record<string, unknown>;
|
|
37
|
+
content?: string;
|
|
38
|
+
error?: string;
|
|
39
|
+
executionTime?: number;
|
|
40
|
+
node?: string;
|
|
41
|
+
skillId?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare class ToolNode {
|
|
44
|
+
private readonly orchestrator;
|
|
45
|
+
private readonly injector;
|
|
46
|
+
private readonly defaultAuthContext;
|
|
47
|
+
private readonly onProgress?;
|
|
48
|
+
private readonly hitlHandler?;
|
|
49
|
+
private readonly nodeId?;
|
|
50
|
+
constructor(orchestrator: ToolOrchestrator, injector: ToolResultInjector, defaultAuthContext: AuthContext, onProgress?: ((data: ToolNodeProgress) => void) | undefined, hitlHandler?: HitlHandler | undefined, nodeId?: string | undefined);
|
|
51
|
+
/**
|
|
52
|
+
* execute — StateGraph 节点函数
|
|
53
|
+
*
|
|
54
|
+
* 提取 pending tool_calls → 并行执行 → 注入结果 → 返回更新 state
|
|
55
|
+
*
|
|
56
|
+
* @param state - 当前 LangGraph State
|
|
57
|
+
* @returns 包含 Tool 结果消息的更新 state
|
|
58
|
+
*/
|
|
59
|
+
execute(state: LangGraphState): Promise<LangGraphState>;
|
|
60
|
+
/**
|
|
61
|
+
* 从 state 中提取 messages 数组
|
|
62
|
+
*/
|
|
63
|
+
private extractMessages;
|
|
64
|
+
/**
|
|
65
|
+
* 从 messages 中找到最后一条 assistant 消息
|
|
66
|
+
*/
|
|
67
|
+
private findLastAssistantMessage;
|
|
68
|
+
private buildAuthContext;
|
|
69
|
+
private shouldForceServer;
|
|
70
|
+
/**
|
|
71
|
+
* 将 LangGraphState 转换为 GraphState(供 injector 使用)
|
|
72
|
+
*/
|
|
73
|
+
private toGraphState;
|
|
74
|
+
}
|
|
75
|
+
export {};
|