@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.
Files changed (326) hide show
  1. package/README.md +323 -0
  2. package/dist/api/handlers/index.d.ts +3 -0
  3. package/dist/api/handlers/index.js +4 -0
  4. package/dist/api/handlers/invoke.handler.d.ts +77 -0
  5. package/dist/api/handlers/invoke.handler.js +411 -0
  6. package/dist/api/handlers/messages.handler.d.ts +53 -0
  7. package/dist/api/handlers/messages.handler.js +80 -0
  8. package/dist/api/handlers/stream.handler.d.ts +92 -0
  9. package/dist/api/handlers/stream.handler.js +432 -0
  10. package/dist/api/index.d.ts +3 -0
  11. package/dist/api/index.js +4 -0
  12. package/dist/api/middleware/auth.middleware.d.ts +96 -0
  13. package/dist/api/middleware/auth.middleware.js +158 -0
  14. package/dist/api/middleware/index.d.ts +1 -0
  15. package/dist/api/middleware/index.js +2 -0
  16. package/dist/api/routes/agent.routes.d.ts +44 -0
  17. package/dist/api/routes/agent.routes.js +204 -0
  18. package/dist/api/routes/index.d.ts +3 -0
  19. package/dist/api/routes/index.js +4 -0
  20. package/dist/api/routes/session.routes.d.ts +82 -0
  21. package/dist/api/routes/session.routes.js +169 -0
  22. package/dist/api/routes/task.routes.d.ts +75 -0
  23. package/dist/api/routes/task.routes.js +108 -0
  24. package/dist/checkpoint/checkpoint-serializer.d.ts +58 -0
  25. package/dist/checkpoint/checkpoint-serializer.js +81 -0
  26. package/dist/checkpoint/composite-checkpoint-saver.d.ts +42 -0
  27. package/dist/checkpoint/composite-checkpoint-saver.js +118 -0
  28. package/dist/checkpoint/contracts/checkpoint-saver.contract.d.ts +65 -0
  29. package/dist/checkpoint/contracts/checkpoint-saver.contract.js +12 -0
  30. package/dist/checkpoint/contracts/index.d.ts +1 -0
  31. package/dist/checkpoint/contracts/index.js +1 -0
  32. package/dist/checkpoint/file-checkpoint-saver.d.ts +19 -0
  33. package/dist/checkpoint/file-checkpoint-saver.js +98 -0
  34. package/dist/checkpoint/hybrid-checkpoint-saver.d.ts +130 -0
  35. package/dist/checkpoint/hybrid-checkpoint-saver.js +211 -0
  36. package/dist/checkpoint/in-memory-checkpoint-saver.d.ts +14 -0
  37. package/dist/checkpoint/in-memory-checkpoint-saver.js +46 -0
  38. package/dist/checkpoint/index.d.ts +16 -0
  39. package/dist/checkpoint/index.js +13 -0
  40. package/dist/checkpoint/mongo-checkpoint-saver.d.ts +38 -0
  41. package/dist/checkpoint/mongo-checkpoint-saver.js +56 -0
  42. package/dist/checkpoint/pg-checkpoint-saver.d.ts +59 -0
  43. package/dist/checkpoint/pg-checkpoint-saver.js +140 -0
  44. package/dist/checkpoint/redis-checkpoint-saver.d.ts +68 -0
  45. package/dist/checkpoint/redis-checkpoint-saver.js +173 -0
  46. package/dist/checkpoint/restore-checkpoint.d.ts +33 -0
  47. package/dist/checkpoint/restore-checkpoint.js +33 -0
  48. package/dist/common/agent-code.d.ts +10 -0
  49. package/dist/common/agent-code.js +12 -0
  50. package/dist/common/concurrency.d.ts +79 -0
  51. package/dist/common/concurrency.js +121 -0
  52. package/dist/common/health.d.ts +40 -0
  53. package/dist/common/health.js +37 -0
  54. package/dist/common/index.d.ts +5 -0
  55. package/dist/common/index.js +5 -0
  56. package/dist/common/logger.d.ts +34 -0
  57. package/dist/common/logger.js +38 -0
  58. package/dist/common/queue-consumer.d.ts +125 -0
  59. package/dist/common/queue-consumer.js +159 -0
  60. package/dist/common/trace-id.d.ts +2 -0
  61. package/dist/common/trace-id.js +8 -0
  62. package/dist/common/types.d.ts +104 -0
  63. package/dist/common/types.js +14 -0
  64. package/dist/contracts/index.d.ts +11 -0
  65. package/dist/contracts/index.js +10 -0
  66. package/dist/contracts/permission-checker.d.ts +10 -0
  67. package/dist/contracts/permission-checker.js +4 -0
  68. package/dist/contracts/trace-collector.d.ts +211 -0
  69. package/dist/contracts/trace-collector.js +13 -0
  70. package/dist/governance/governance-client.d.ts +38 -0
  71. package/dist/governance/governance-client.js +95 -0
  72. package/dist/governance/index.d.ts +7 -0
  73. package/dist/governance/index.js +5 -0
  74. package/dist/governance/permission-guard.d.ts +19 -0
  75. package/dist/governance/permission-guard.js +59 -0
  76. package/dist/graph/adapters/index.d.ts +3 -0
  77. package/dist/graph/adapters/index.js +3 -0
  78. package/dist/graph/adapters/langgraph-checkpointer.d.ts +40 -0
  79. package/dist/graph/adapters/langgraph-checkpointer.js +211 -0
  80. package/dist/graph/adapters/langgraph.adapter.d.ts +190 -0
  81. package/dist/graph/adapters/langgraph.adapter.js +1303 -0
  82. package/dist/graph/adapters/subgraph-compiler.d.ts +39 -0
  83. package/dist/graph/adapters/subgraph-compiler.js +248 -0
  84. package/dist/graph/condition-evaluator.d.ts +57 -0
  85. package/dist/graph/condition-evaluator.js +162 -0
  86. package/dist/graph/contracts/executable-graph.d.ts +340 -0
  87. package/dist/graph/contracts/executable-graph.js +10 -0
  88. package/dist/graph/contracts/index.d.ts +2 -0
  89. package/dist/graph/contracts/index.js +2 -0
  90. package/dist/graph/contracts/state-graph.contract.d.ts +76 -0
  91. package/dist/graph/contracts/state-graph.contract.js +11 -0
  92. package/dist/graph/hitl/hitl-handler.d.ts +199 -0
  93. package/dist/graph/hitl/hitl-handler.js +480 -0
  94. package/dist/graph/hitl/index.d.ts +1 -0
  95. package/dist/graph/hitl/index.js +1 -0
  96. package/dist/graph/index.d.ts +7 -0
  97. package/dist/graph/index.js +7 -0
  98. package/dist/graph/nodes/index.d.ts +2 -0
  99. package/dist/graph/nodes/index.js +2 -0
  100. package/dist/graph/nodes/llm-node.d.ts +182 -0
  101. package/dist/graph/nodes/llm-node.js +281 -0
  102. package/dist/graph/nodes/skill-bind-node.d.ts +32 -0
  103. package/dist/graph/nodes/skill-bind-node.js +116 -0
  104. package/dist/graph/nodes/tool-node.d.ts +75 -0
  105. package/dist/graph/nodes/tool-node.js +324 -0
  106. package/dist/graph/resolve-graph-prompt.d.ts +5 -0
  107. package/dist/graph/resolve-graph-prompt.js +75 -0
  108. package/dist/graph/retry/budget-aware-retry.d.ts +91 -0
  109. package/dist/graph/retry/budget-aware-retry.js +126 -0
  110. package/dist/graph/retry/index.d.ts +9 -0
  111. package/dist/graph/retry/index.js +8 -0
  112. package/dist/graph/retry/node-retry.d.ts +123 -0
  113. package/dist/graph/retry/node-retry.js +235 -0
  114. package/dist/graph/skill-audit-recorder.d.ts +126 -0
  115. package/dist/graph/skill-audit-recorder.js +114 -0
  116. package/dist/index.d.ts +21 -0
  117. package/dist/index.js +32 -0
  118. package/dist/memory/compression/compression-stats.d.ts +109 -0
  119. package/dist/memory/compression/compression-stats.js +144 -0
  120. package/dist/memory/compression/compression.repository.d.ts +62 -0
  121. package/dist/memory/compression/compression.repository.js +71 -0
  122. package/dist/memory/compression/index.d.ts +10 -0
  123. package/dist/memory/compression/index.js +15 -0
  124. package/dist/memory/compression/llm-summarize.d.ts +58 -0
  125. package/dist/memory/compression/llm-summarize.js +192 -0
  126. package/dist/memory/compression/sliding-window.compression.d.ts +60 -0
  127. package/dist/memory/compression/sliding-window.compression.js +175 -0
  128. package/dist/memory/index.d.ts +4 -0
  129. package/dist/memory/index.js +7 -0
  130. package/dist/memory/long-term-memory.service.d.ts +94 -0
  131. package/dist/memory/long-term-memory.service.js +120 -0
  132. package/dist/memory/message-compression.service.d.ts +128 -0
  133. package/dist/memory/message-compression.service.js +69 -0
  134. package/dist/memory/pg-memory.repository.d.ts +44 -0
  135. package/dist/memory/pg-memory.repository.js +108 -0
  136. package/dist/memory/semantic-retrieval.service.d.ts +148 -0
  137. package/dist/memory/semantic-retrieval.service.js +238 -0
  138. package/dist/providers/echo-llm-provider.d.ts +10 -0
  139. package/dist/providers/echo-llm-provider.js +87 -0
  140. package/dist/providers/http-llm-provider.d.ts +35 -0
  141. package/dist/providers/http-llm-provider.js +404 -0
  142. package/dist/providers/index.d.ts +7 -0
  143. package/dist/providers/index.js +5 -0
  144. package/dist/providers/local-llm-provider.d.ts +27 -0
  145. package/dist/providers/local-llm-provider.js +105 -0
  146. package/dist/providers/resolve-llm-model.d.ts +13 -0
  147. package/dist/providers/resolve-llm-model.js +30 -0
  148. package/dist/providers/routing-llm-provider.d.ts +28 -0
  149. package/dist/providers/routing-llm-provider.js +79 -0
  150. package/dist/registry/agent-models.d.ts +1 -0
  151. package/dist/registry/agent-models.js +1 -0
  152. package/dist/registry/agent-registry-client.d.ts +56 -0
  153. package/dist/registry/agent-registry-client.js +36 -0
  154. package/dist/registry/definition-cache.d.ts +37 -0
  155. package/dist/registry/definition-cache.js +82 -0
  156. package/dist/registry/deployment-snapshot.d.ts +99 -0
  157. package/dist/registry/deployment-snapshot.js +542 -0
  158. package/dist/registry/graph-loader.d.ts +134 -0
  159. package/dist/registry/graph-loader.js +985 -0
  160. package/dist/registry/index.d.ts +9 -0
  161. package/dist/registry/index.js +9 -0
  162. package/dist/registry/preload-errors.d.ts +13 -0
  163. package/dist/registry/preload-errors.js +35 -0
  164. package/dist/registry/preload-flags.d.ts +22 -0
  165. package/dist/registry/preload-flags.js +44 -0
  166. package/dist/registry/skill-io-guard.d.ts +25 -0
  167. package/dist/registry/skill-io-guard.js +98 -0
  168. package/dist/registry/skill-tree.d.ts +27 -0
  169. package/dist/registry/skill-tree.js +183 -0
  170. package/dist/runtime/agent-runtime.d.ts +106 -0
  171. package/dist/runtime/agent-runtime.js +777 -0
  172. package/dist/runtime/dev-runtime.d.ts +69 -0
  173. package/dist/runtime/dev-runtime.js +199 -0
  174. package/dist/runtime/index.d.ts +4 -0
  175. package/dist/runtime/index.js +3 -0
  176. package/dist/runtime/storage-profile.d.ts +50 -0
  177. package/dist/runtime/storage-profile.js +138 -0
  178. package/dist/runtime/types.d.ts +120 -0
  179. package/dist/runtime/types.js +9 -0
  180. package/dist/server.d.ts +12 -0
  181. package/dist/server.js +161 -0
  182. package/dist/session/compression-middleware.d.ts +126 -0
  183. package/dist/session/compression-middleware.js +230 -0
  184. package/dist/session/file-message.repository.d.ts +25 -0
  185. package/dist/session/file-message.repository.js +78 -0
  186. package/dist/session/file-session.repository.d.ts +72 -0
  187. package/dist/session/file-session.repository.js +212 -0
  188. package/dist/session/index.d.ts +13 -0
  189. package/dist/session/index.js +12 -0
  190. package/dist/session/message.service.d.ts +80 -0
  191. package/dist/session/message.service.js +128 -0
  192. package/dist/session/persist-turn-messages.d.ts +145 -0
  193. package/dist/session/persist-turn-messages.js +325 -0
  194. package/dist/session/pg-message.repository.d.ts +49 -0
  195. package/dist/session/pg-message.repository.js +149 -0
  196. package/dist/session/pg-session.repository.d.ts +46 -0
  197. package/dist/session/pg-session.repository.js +117 -0
  198. package/dist/session/session-context.service.d.ts +49 -0
  199. package/dist/session/session-context.service.js +66 -0
  200. package/dist/session/session.service.d.ts +194 -0
  201. package/dist/session/session.service.js +166 -0
  202. package/dist/sse/connection-event-logger.d.ts +21 -0
  203. package/dist/sse/connection-event-logger.js +33 -0
  204. package/dist/sse/contracts/index.d.ts +1 -0
  205. package/dist/sse/contracts/index.js +1 -0
  206. package/dist/sse/contracts/sse-emitter.contract.d.ts +74 -0
  207. package/dist/sse/contracts/sse-emitter.contract.js +11 -0
  208. package/dist/sse/event-buffer.d.ts +20 -0
  209. package/dist/sse/event-buffer.js +45 -0
  210. package/dist/sse/events/code-events.d.ts +19 -0
  211. package/dist/sse/events/code-events.js +9 -0
  212. package/dist/sse/events/collaboration-events.d.ts +37 -0
  213. package/dist/sse/events/collaboration-events.js +11 -0
  214. package/dist/sse/events/connection-events.d.ts +13 -0
  215. package/dist/sse/events/connection-events.js +9 -0
  216. package/dist/sse/events/file-events.d.ts +29 -0
  217. package/dist/sse/events/file-events.js +10 -0
  218. package/dist/sse/events/hitl-events.d.ts +33 -0
  219. package/dist/sse/events/hitl-events.js +10 -0
  220. package/dist/sse/events/index.d.ts +107 -0
  221. package/dist/sse/events/index.js +96 -0
  222. package/dist/sse/events/plan-events.d.ts +38 -0
  223. package/dist/sse/events/plan-events.js +11 -0
  224. package/dist/sse/events/reasoning-events.d.ts +35 -0
  225. package/dist/sse/events/reasoning-events.js +13 -0
  226. package/dist/sse/events/response-events.d.ts +25 -0
  227. package/dist/sse/events/response-events.js +12 -0
  228. package/dist/sse/events/session-events.d.ts +43 -0
  229. package/dist/sse/events/session-events.js +12 -0
  230. package/dist/sse/events/skill-events.d.ts +48 -0
  231. package/dist/sse/events/skill-events.js +11 -0
  232. package/dist/sse/events/stats-events.d.ts +44 -0
  233. package/dist/sse/events/stats-events.js +12 -0
  234. package/dist/sse/events/tool-events.d.ts +68 -0
  235. package/dist/sse/events/tool-events.js +14 -0
  236. package/dist/sse/index.d.ts +8 -0
  237. package/dist/sse/index.js +8 -0
  238. package/dist/sse/sse-connection-manager.d.ts +116 -0
  239. package/dist/sse/sse-connection-manager.js +185 -0
  240. package/dist/sse/sse-emitter.d.ts +108 -0
  241. package/dist/sse/sse-emitter.js +210 -0
  242. package/dist/sse/sse-heartbeat.d.ts +58 -0
  243. package/dist/sse/sse-heartbeat.js +76 -0
  244. package/dist/sse/trace-collector.d.ts +60 -0
  245. package/dist/sse/trace-collector.js +107 -0
  246. package/dist/task/contracts/index.d.ts +2 -0
  247. package/dist/task/contracts/index.js +1 -0
  248. package/dist/task/contracts/task-manager.contract.d.ts +160 -0
  249. package/dist/task/contracts/task-manager.contract.js +10 -0
  250. package/dist/task/index.d.ts +7 -0
  251. package/dist/task/index.js +5 -0
  252. package/dist/task/pg-task.repository.d.ts +47 -0
  253. package/dist/task/pg-task.repository.js +131 -0
  254. package/dist/task/task-progress-tracker.d.ts +67 -0
  255. package/dist/task/task-progress-tracker.js +80 -0
  256. package/dist/task/task-timeout.d.ts +70 -0
  257. package/dist/task/task-timeout.js +116 -0
  258. package/dist/task/task.service.d.ts +84 -0
  259. package/dist/task/task.service.js +233 -0
  260. package/dist/token/compression-budget-integration.d.ts +145 -0
  261. package/dist/token/compression-budget-integration.js +185 -0
  262. package/dist/token/context-window-budget.d.ts +113 -0
  263. package/dist/token/context-window-budget.js +134 -0
  264. package/dist/token/contracts/index.d.ts +1 -0
  265. package/dist/token/contracts/index.js +1 -0
  266. package/dist/token/contracts/token-budget.contract.d.ts +77 -0
  267. package/dist/token/contracts/token-budget.contract.js +11 -0
  268. package/dist/token/index.d.ts +12 -0
  269. package/dist/token/index.js +10 -0
  270. package/dist/token/pg-token-budget.repository.d.ts +39 -0
  271. package/dist/token/pg-token-budget.repository.js +116 -0
  272. package/dist/token/session-budget-checker.d.ts +54 -0
  273. package/dist/token/session-budget-checker.js +72 -0
  274. package/dist/token/token-budget-guard.d.ts +66 -0
  275. package/dist/token/token-budget-guard.js +97 -0
  276. package/dist/token/token-budget.service.d.ts +86 -0
  277. package/dist/token/token-budget.service.js +130 -0
  278. package/dist/token/token-detail-mode.d.ts +73 -0
  279. package/dist/token/token-detail-mode.js +107 -0
  280. package/dist/token/token-estimator.d.ts +80 -0
  281. package/dist/token/token-estimator.js +134 -0
  282. package/dist/token/token-stats.service.d.ts +88 -0
  283. package/dist/token/token-stats.service.js +94 -0
  284. package/dist/tool/create-tool-executor.d.ts +33 -0
  285. package/dist/tool/create-tool-executor.js +60 -0
  286. package/dist/tool/dev-http-executor.d.ts +18 -0
  287. package/dist/tool/dev-http-executor.js +96 -0
  288. package/dist/tool/dev-mcp-executor.d.ts +29 -0
  289. package/dist/tool/dev-mcp-executor.js +84 -0
  290. package/dist/tool/dev-tool-executor.d.ts +23 -0
  291. package/dist/tool/dev-tool-executor.js +55 -0
  292. package/dist/tool/file-artifact.d.ts +11 -0
  293. package/dist/tool/file-artifact.js +90 -0
  294. package/dist/tool/http-tool-executor.d.ts +17 -0
  295. package/dist/tool/http-tool-executor.js +158 -0
  296. package/dist/tool/index.d.ts +18 -0
  297. package/dist/tool/index.js +17 -0
  298. package/dist/tool/loaded-tool-definition.d.ts +35 -0
  299. package/dist/tool/loaded-tool-definition.js +9 -0
  300. package/dist/tool/local-mcp-client.d.ts +23 -0
  301. package/dist/tool/local-mcp-client.js +225 -0
  302. package/dist/tool/local-tool-executor.d.ts +37 -0
  303. package/dist/tool/local-tool-executor.js +249 -0
  304. package/dist/tool/mcp-tool-executor.d.ts +23 -0
  305. package/dist/tool/mcp-tool-executor.js +160 -0
  306. package/dist/tool/mock-tool-executor.d.ts +28 -0
  307. package/dist/tool/mock-tool-executor.js +65 -0
  308. package/dist/tool/system-prompt-builder.d.ts +114 -0
  309. package/dist/tool/system-prompt-builder.js +213 -0
  310. package/dist/tool/tool-confirmation.d.ts +29 -0
  311. package/dist/tool/tool-confirmation.js +81 -0
  312. package/dist/tool/tool-event-emitter.d.ts +58 -0
  313. package/dist/tool/tool-event-emitter.js +154 -0
  314. package/dist/tool/tool-execution-router.d.ts +25 -0
  315. package/dist/tool/tool-execution-router.js +50 -0
  316. package/dist/tool/tool-executor.interface.d.ts +85 -0
  317. package/dist/tool/tool-executor.interface.js +7 -0
  318. package/dist/tool/tool-orchestrator.d.ts +22 -0
  319. package/dist/tool/tool-orchestrator.js +104 -0
  320. package/dist/tool/tool-result-injector.d.ts +64 -0
  321. package/dist/tool/tool-result-injector.js +65 -0
  322. package/dist/utils/format-error.d.ts +4 -0
  323. package/dist/utils/format-error.js +4 -0
  324. package/dist/utils/http-error.d.ts +11 -0
  325. package/dist/utils/http-error.js +24 -0
  326. 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 {};