@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,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HITL (Human-In-The-Loop) Handler
|
|
3
|
+
*
|
|
4
|
+
* 实现人工介入的中断、恢复、超时逻辑。
|
|
5
|
+
* 当 Graph 到达 HITL 节点时暂停执行,等待人工确认后继续。
|
|
6
|
+
*
|
|
7
|
+
* 超时不得在 setTimeout 回调里 throw(会变成未捕获异常);
|
|
8
|
+
* 由 AgentRuntime 监听器关 Task / Span。deadline 写入 Checkpoint,冷恢复可校验。
|
|
9
|
+
*
|
|
10
|
+
* @see design.md §1.6 hitl.waiting / hitl.resumed 事件
|
|
11
|
+
* @see tasks.md Task 14
|
|
12
|
+
*/
|
|
13
|
+
// ─────────────────────────────────────────────
|
|
14
|
+
// Types
|
|
15
|
+
// ─────────────────────────────────────────────
|
|
16
|
+
/** HITL 默认等待时长(毫秒)= 300 秒。SSE `hitl.waited.timeout` 对外用秒。 */
|
|
17
|
+
export const DEFAULT_HITL_TIMEOUT_MS = 300_000;
|
|
18
|
+
/** HITL 超时错误(保留类型;timer 不再 throw,resume 逾期改走 AppError) */
|
|
19
|
+
export class HitlTimeoutError extends Error {
|
|
20
|
+
taskId;
|
|
21
|
+
timeoutMs;
|
|
22
|
+
constructor(taskId, timeoutMs) {
|
|
23
|
+
super(`HITL timeout: task ${taskId} exceeded ${timeoutMs}ms wait`);
|
|
24
|
+
this.taskId = taskId;
|
|
25
|
+
this.timeoutMs = timeoutMs;
|
|
26
|
+
this.name = 'HitlTimeoutError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Checkpoint 已标记超时,禁止再 resume */
|
|
30
|
+
export class HitlAlreadyTimedOutError extends Error {
|
|
31
|
+
taskId;
|
|
32
|
+
constructor(taskId) {
|
|
33
|
+
super(`HITL already timed out: task ${taskId}`);
|
|
34
|
+
this.taskId = taskId;
|
|
35
|
+
this.name = 'HitlAlreadyTimedOutError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** 内存与 Checkpoint 都没有等待态(已被 resume/timeout 抢走) */
|
|
39
|
+
export class HitlNotWaitingError extends Error {
|
|
40
|
+
taskId;
|
|
41
|
+
constructor(taskId) {
|
|
42
|
+
super(`No HITL waiting state for task: ${taskId}`);
|
|
43
|
+
this.taskId = taskId;
|
|
44
|
+
this.name = 'HitlNotWaitingError';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function isHitlDecision(choice) {
|
|
48
|
+
return choice === 'confirm' || choice === 'reject';
|
|
49
|
+
}
|
|
50
|
+
/** 最后一条 assistant 仍有未配对的 toolCalls(HITL 暂停后尚未 result/error) */
|
|
51
|
+
export function lastAssistantHasUnansweredToolCalls(messages) {
|
|
52
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
53
|
+
const msg = messages[i];
|
|
54
|
+
if (!msg || msg.role !== 'assistant')
|
|
55
|
+
continue;
|
|
56
|
+
const toolCalls = msg.toolCalls;
|
|
57
|
+
if (!Array.isArray(toolCalls) || toolCalls.length === 0)
|
|
58
|
+
return false;
|
|
59
|
+
const pending = new Set(toolCalls.map((tc) => tc.id).filter((id) => Boolean(id)));
|
|
60
|
+
for (let j = i + 1; j < messages.length; j++) {
|
|
61
|
+
const later = messages[j];
|
|
62
|
+
if (later?.role === 'tool' && later.toolCallId) {
|
|
63
|
+
pending.delete(later.toolCallId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return pending.size > 0;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* HITL 已中断、等待外部 resume(非阻塞)
|
|
72
|
+
*
|
|
73
|
+
* Tool 确认节点 interrupt 后抛出本错误,由 invoke/stream 捕获并返回 waiting_hitl,
|
|
74
|
+
* 客户端再通过 POST .../hitl 或 streamResumeHitl 继续。
|
|
75
|
+
*/
|
|
76
|
+
export class HitlPausedError extends Error {
|
|
77
|
+
taskId;
|
|
78
|
+
threadId;
|
|
79
|
+
question;
|
|
80
|
+
options;
|
|
81
|
+
toolCallId;
|
|
82
|
+
toolName;
|
|
83
|
+
timeoutMs;
|
|
84
|
+
toolSpanId;
|
|
85
|
+
skillSpanId;
|
|
86
|
+
constructor(taskId, threadId, question, options, toolCallId, toolName, timeoutMs = DEFAULT_HITL_TIMEOUT_MS) {
|
|
87
|
+
super(`HITL paused: task ${taskId} waiting for confirmation`);
|
|
88
|
+
this.taskId = taskId;
|
|
89
|
+
this.threadId = threadId;
|
|
90
|
+
this.question = question;
|
|
91
|
+
this.options = options;
|
|
92
|
+
this.toolCallId = toolCallId;
|
|
93
|
+
this.toolName = toolName;
|
|
94
|
+
this.timeoutMs = timeoutMs;
|
|
95
|
+
this.name = 'HitlPausedError';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 向上解包 HitlPausedError(LangGraph / Promise 可能挂在 `.cause`)
|
|
100
|
+
*/
|
|
101
|
+
export function unwrapHitlPaused(error) {
|
|
102
|
+
let current = error;
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
while (current && typeof current === 'object' && !seen.has(current)) {
|
|
105
|
+
seen.add(current);
|
|
106
|
+
if (current instanceof HitlPausedError)
|
|
107
|
+
return current;
|
|
108
|
+
current = current.cause;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
/** Checkpoint state 中持久化的 HITL 等待元数据(进程重启 / 无内存 Map 时冷恢复) */
|
|
113
|
+
export const HITL_WAITING_STATE_KEY = '__hitlWaiting';
|
|
114
|
+
export function toHitlTimeoutMs(timeoutMs) {
|
|
115
|
+
if (typeof timeoutMs !== 'number' || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
116
|
+
return DEFAULT_HITL_TIMEOUT_MS;
|
|
117
|
+
}
|
|
118
|
+
return timeoutMs;
|
|
119
|
+
}
|
|
120
|
+
/** SSE `hitl.waited.data.timeout`:秒 */
|
|
121
|
+
export function toHitlTimeoutSeconds(timeoutMs) {
|
|
122
|
+
return Math.max(1, Math.round(toHitlTimeoutMs(timeoutMs) / 1000));
|
|
123
|
+
}
|
|
124
|
+
export function isHitlDeadlinePassed(persisted, now = Date.now()) {
|
|
125
|
+
if (!persisted?.deadlineAt)
|
|
126
|
+
return false;
|
|
127
|
+
const t = Date.parse(persisted.deadlineAt);
|
|
128
|
+
return Number.isFinite(t) && now >= t;
|
|
129
|
+
}
|
|
130
|
+
export function readPersistedHitlWaiting(state) {
|
|
131
|
+
if (!state || typeof state !== 'object')
|
|
132
|
+
return undefined;
|
|
133
|
+
const raw = state[HITL_WAITING_STATE_KEY];
|
|
134
|
+
if (!raw || typeof raw !== 'object')
|
|
135
|
+
return undefined;
|
|
136
|
+
return raw;
|
|
137
|
+
}
|
|
138
|
+
/** 门面 stream / HTTP StreamOrchestrator / streamResumeHitl 共用 */
|
|
139
|
+
export function buildHitlWaitedPayload(paused) {
|
|
140
|
+
return {
|
|
141
|
+
question: paused.question,
|
|
142
|
+
options: paused.options,
|
|
143
|
+
threadId: paused.threadId,
|
|
144
|
+
timeout: toHitlTimeoutSeconds(paused.timeoutMs),
|
|
145
|
+
...(paused.toolCallId ? { toolCallId: paused.toolCallId } : {}),
|
|
146
|
+
...(paused.toolName ? { toolName: paused.toolName } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 确保 HITL checkpoint 持久化运行期上下文字段(loadedTools 等)
|
|
151
|
+
*/
|
|
152
|
+
function prepareHitlCheckpointState(state) {
|
|
153
|
+
const raw = state;
|
|
154
|
+
return {
|
|
155
|
+
...state,
|
|
156
|
+
userId: state.userId ?? raw.userId ?? '',
|
|
157
|
+
loadedTools: state.loadedTools ?? raw.loadedTools,
|
|
158
|
+
forceToolServer: state.forceToolServer ?? raw.forceToolServer,
|
|
159
|
+
executionContext: state.executionContext ?? raw.executionContext,
|
|
160
|
+
agentCode: state.agentCode ?? raw.agentCode ?? state.agentId ?? raw.agentId,
|
|
161
|
+
correlationId: state.correlationId ?? raw.correlationId,
|
|
162
|
+
delegationToken: state.delegationToken ?? raw.delegationToken,
|
|
163
|
+
skillSpanId: state.skillSpanId ?? raw.skillSpanId,
|
|
164
|
+
traceId: state.traceId ?? (typeof raw.traceId === 'string' ? raw.traceId : undefined),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function toTimeoutEvent(waiting) {
|
|
168
|
+
const timeoutMs = toHitlTimeoutMs(waiting.config.timeout);
|
|
169
|
+
const behavior = waiting.config.timeoutBehavior ?? 'error';
|
|
170
|
+
return {
|
|
171
|
+
taskId: waiting.taskId,
|
|
172
|
+
threadId: waiting.threadId,
|
|
173
|
+
behavior,
|
|
174
|
+
timeoutMs,
|
|
175
|
+
question: waiting.config.question,
|
|
176
|
+
options: waiting.config.options,
|
|
177
|
+
defaultChoice: waiting.config.defaultOption ?? waiting.config.options[0] ?? 'confirm',
|
|
178
|
+
toolCallId: waiting.toolCallId,
|
|
179
|
+
toolName: waiting.toolName,
|
|
180
|
+
toolSpanId: waiting.toolSpanId,
|
|
181
|
+
skillSpanId: waiting.skillSpanId,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function waitingFromPersisted(persisted, taskId) {
|
|
185
|
+
return {
|
|
186
|
+
taskId,
|
|
187
|
+
threadId: persisted.threadId,
|
|
188
|
+
config: persisted.config,
|
|
189
|
+
timer: null,
|
|
190
|
+
interruptedAt: persisted.interruptedAt,
|
|
191
|
+
deadlineAt: persisted.deadlineAt,
|
|
192
|
+
toolCallId: persisted.toolCallId,
|
|
193
|
+
toolName: persisted.toolName,
|
|
194
|
+
toolSpanId: persisted.toolSpanId,
|
|
195
|
+
skillSpanId: persisted.skillSpanId,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
// ─────────────────────────────────────────────
|
|
199
|
+
// HitlHandler
|
|
200
|
+
// ─────────────────────────────────────────────
|
|
201
|
+
/**
|
|
202
|
+
* HitlHandler — 管理 HITL 中断/恢复/超时
|
|
203
|
+
*
|
|
204
|
+
* 职责:
|
|
205
|
+
* - interrupt: 保存 checkpoint + 发射 hitl.waiting + 启动超时计时
|
|
206
|
+
* - resume: 加载 checkpoint + 注入用户选择 + 发射 hitl.resumed
|
|
207
|
+
* - handleTimeout: 不 throw;回调 listener(error/cancel 收口,auto-confirm 续跑)
|
|
208
|
+
*/
|
|
209
|
+
export class HitlHandler {
|
|
210
|
+
checkpointSaver;
|
|
211
|
+
sseEmitter;
|
|
212
|
+
/** taskId → waiting state */
|
|
213
|
+
waitingTasks = new Map();
|
|
214
|
+
/** resume 后暂存的用户选择(供 ToolConfirmationInterceptor 轮询读取) */
|
|
215
|
+
resolvedChoices = new Map();
|
|
216
|
+
timeoutListener;
|
|
217
|
+
/** 同一 taskId 上串行化 resume 与 timeout,避免双边占用 */
|
|
218
|
+
taskLocks = new Map();
|
|
219
|
+
constructor(checkpointSaver, sseEmitter, timeoutListener) {
|
|
220
|
+
this.checkpointSaver = checkpointSaver;
|
|
221
|
+
this.sseEmitter = sseEmitter;
|
|
222
|
+
this.timeoutListener = timeoutListener;
|
|
223
|
+
}
|
|
224
|
+
setTimeoutListener(listener) {
|
|
225
|
+
this.timeoutListener = listener;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 中断 Graph 执行,等待人工确认
|
|
229
|
+
*
|
|
230
|
+
* 写入 `__hitlWaiting`(含 deadlineAt)到同一 thread 的 Checkpoint。
|
|
231
|
+
* 不保留 LangGraph `engineSnapshot`:子图与父图共用 session thread,
|
|
232
|
+
* resume 时用 GraphState 从父图入口重入(skillSpanId / hitlChoice 防重复 span)。
|
|
233
|
+
*/
|
|
234
|
+
async interrupt(taskId, threadId, state, config, extras = {}) {
|
|
235
|
+
const now = new Date().toISOString();
|
|
236
|
+
const timeoutMs = toHitlTimeoutMs(config.timeout);
|
|
237
|
+
const deadlineAt = new Date(Date.now() + timeoutMs).toISOString();
|
|
238
|
+
const skillSpanId = extras.skillSpanId ??
|
|
239
|
+
(typeof state.skillSpanId === 'string' ? state.skillSpanId : undefined);
|
|
240
|
+
const persisted = {
|
|
241
|
+
taskId,
|
|
242
|
+
threadId,
|
|
243
|
+
config: { ...config, timeout: timeoutMs },
|
|
244
|
+
interruptedAt: now,
|
|
245
|
+
deadlineAt,
|
|
246
|
+
toolCallId: extras.toolCallId,
|
|
247
|
+
toolName: extras.toolName,
|
|
248
|
+
toolSpanId: extras.toolSpanId,
|
|
249
|
+
skillSpanId,
|
|
250
|
+
};
|
|
251
|
+
const existing = await this.checkpointSaver.load(threadId);
|
|
252
|
+
const persistedState = {
|
|
253
|
+
...(existing?.state ?? {}),
|
|
254
|
+
...prepareHitlCheckpointState(state),
|
|
255
|
+
[HITL_WAITING_STATE_KEY]: persisted,
|
|
256
|
+
};
|
|
257
|
+
const checkpointData = {
|
|
258
|
+
checkpointId: existing?.checkpointId ?? `hitl-${taskId}-${Date.now()}`,
|
|
259
|
+
threadId,
|
|
260
|
+
currentNodeId: existing?.currentNodeId ?? '__hitl_waiting__',
|
|
261
|
+
historyPath: existing?.historyPath ?? [],
|
|
262
|
+
state: persistedState,
|
|
263
|
+
createdBy: existing?.createdBy ?? 'system',
|
|
264
|
+
createdAt: existing?.createdAt ?? now,
|
|
265
|
+
updatedBy: 'system',
|
|
266
|
+
updatedAt: now,
|
|
267
|
+
isDeleted: false,
|
|
268
|
+
};
|
|
269
|
+
await this.checkpointSaver.save(threadId, checkpointData);
|
|
270
|
+
const event = {
|
|
271
|
+
type: 'hitl.waiting',
|
|
272
|
+
data: {
|
|
273
|
+
question: config.question,
|
|
274
|
+
options: config.options,
|
|
275
|
+
timeout: toHitlTimeoutSeconds(timeoutMs),
|
|
276
|
+
},
|
|
277
|
+
correlationId: threadId,
|
|
278
|
+
taskId,
|
|
279
|
+
timestamp: now,
|
|
280
|
+
};
|
|
281
|
+
this.sseEmitter.emit(event);
|
|
282
|
+
const previous = this.waitingTasks.get(taskId);
|
|
283
|
+
if (previous?.timer) {
|
|
284
|
+
clearTimeout(previous.timer);
|
|
285
|
+
}
|
|
286
|
+
const timer = setTimeout(() => {
|
|
287
|
+
void this.handleTimeoutAsync(taskId).catch(() => undefined);
|
|
288
|
+
}, timeoutMs);
|
|
289
|
+
if (typeof timer.unref === 'function') {
|
|
290
|
+
timer.unref();
|
|
291
|
+
}
|
|
292
|
+
this.waitingTasks.set(taskId, {
|
|
293
|
+
taskId,
|
|
294
|
+
threadId,
|
|
295
|
+
config: persisted.config,
|
|
296
|
+
timer,
|
|
297
|
+
interruptedAt: now,
|
|
298
|
+
deadlineAt,
|
|
299
|
+
toolCallId: extras.toolCallId,
|
|
300
|
+
toolName: extras.toolName,
|
|
301
|
+
toolSpanId: extras.toolSpanId,
|
|
302
|
+
skillSpanId,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* 用户响应后恢复执行
|
|
307
|
+
*
|
|
308
|
+
* 优先用进程内 waitingTasks;若进程重启丢失,可用 threadId 从 Checkpoint 冷恢复
|
|
309
|
+
*(interrupt 时已写入 `__hitlWaiting`)。
|
|
310
|
+
*
|
|
311
|
+
* @returns 注入用户选择后的 GraphState
|
|
312
|
+
*/
|
|
313
|
+
async resume(taskId, choice, userId, threadIdHint) {
|
|
314
|
+
return this.withTaskLock(taskId, async () => {
|
|
315
|
+
let waiting = this.waitingTasks.get(taskId);
|
|
316
|
+
const threadId = waiting?.threadId ?? threadIdHint;
|
|
317
|
+
if (!threadId) {
|
|
318
|
+
throw new HitlNotWaitingError(taskId);
|
|
319
|
+
}
|
|
320
|
+
const checkpoint = await this.checkpointSaver.load(threadId);
|
|
321
|
+
if (!checkpoint) {
|
|
322
|
+
throw new Error(`Checkpoint not found for thread: ${threadId}`);
|
|
323
|
+
}
|
|
324
|
+
const persisted = readPersistedHitlWaiting(checkpoint.state);
|
|
325
|
+
if (persisted?.timedOut) {
|
|
326
|
+
this.clearWaiting(taskId);
|
|
327
|
+
throw new HitlAlreadyTimedOutError(taskId);
|
|
328
|
+
}
|
|
329
|
+
if (!waiting && !persisted) {
|
|
330
|
+
throw new HitlNotWaitingError(taskId);
|
|
331
|
+
}
|
|
332
|
+
if (!waiting && persisted) {
|
|
333
|
+
waiting = waitingFromPersisted(persisted, taskId);
|
|
334
|
+
}
|
|
335
|
+
if (!waiting) {
|
|
336
|
+
throw new HitlNotWaitingError(taskId);
|
|
337
|
+
}
|
|
338
|
+
this.clearWaiting(taskId);
|
|
339
|
+
this.resolvedChoices.set(taskId, choice);
|
|
340
|
+
const baseState = prepareHitlCheckpointState(checkpoint.state);
|
|
341
|
+
const cleaned = { ...baseState };
|
|
342
|
+
delete cleaned[HITL_WAITING_STATE_KEY];
|
|
343
|
+
const resumedState = {
|
|
344
|
+
...cleaned,
|
|
345
|
+
hitlChoice: choice,
|
|
346
|
+
hitlRespondedBy: userId,
|
|
347
|
+
};
|
|
348
|
+
const now = new Date().toISOString();
|
|
349
|
+
await this.checkpointSaver.save(waiting.threadId, {
|
|
350
|
+
...checkpoint,
|
|
351
|
+
engineSnapshot: undefined,
|
|
352
|
+
state: resumedState,
|
|
353
|
+
updatedBy: userId,
|
|
354
|
+
updatedAt: now,
|
|
355
|
+
});
|
|
356
|
+
const event = {
|
|
357
|
+
type: 'hitl.resumed',
|
|
358
|
+
data: { choice, userId },
|
|
359
|
+
correlationId: waiting.threadId,
|
|
360
|
+
taskId,
|
|
361
|
+
timestamp: now,
|
|
362
|
+
};
|
|
363
|
+
this.sseEmitter.emit(event);
|
|
364
|
+
return resumedState;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* 超时处理入口(同步包装)。timer 回调不得 throw。
|
|
369
|
+
*/
|
|
370
|
+
handleTimeout(taskId) {
|
|
371
|
+
void this.handleTimeoutAsync(taskId);
|
|
372
|
+
}
|
|
373
|
+
async handleTimeoutAsync(taskId) {
|
|
374
|
+
let event;
|
|
375
|
+
try {
|
|
376
|
+
event = await this.withTaskLock(taskId, async () => {
|
|
377
|
+
const waiting = this.waitingTasks.get(taskId);
|
|
378
|
+
if (!waiting)
|
|
379
|
+
return undefined;
|
|
380
|
+
const checkpoint = await this.checkpointSaver.load(waiting.threadId);
|
|
381
|
+
const persisted = readPersistedHitlWaiting(checkpoint?.state);
|
|
382
|
+
if (!persisted || persisted.timedOut) {
|
|
383
|
+
this.clearWaiting(taskId);
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
const timeoutEvent = toTimeoutEvent({
|
|
387
|
+
...waiting,
|
|
388
|
+
toolCallId: waiting.toolCallId ?? persisted.toolCallId,
|
|
389
|
+
toolName: waiting.toolName ?? persisted.toolName,
|
|
390
|
+
toolSpanId: waiting.toolSpanId ?? persisted.toolSpanId,
|
|
391
|
+
skillSpanId: waiting.skillSpanId ?? persisted.skillSpanId,
|
|
392
|
+
});
|
|
393
|
+
this.clearWaiting(taskId);
|
|
394
|
+
if (timeoutEvent.behavior !== 'auto-confirm' && checkpoint) {
|
|
395
|
+
const marked = {
|
|
396
|
+
...checkpoint.state,
|
|
397
|
+
[HITL_WAITING_STATE_KEY]: {
|
|
398
|
+
...persisted,
|
|
399
|
+
timedOut: true,
|
|
400
|
+
timeoutBehavior: timeoutEvent.behavior,
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
await this.checkpointSaver.save(waiting.threadId, {
|
|
404
|
+
...checkpoint,
|
|
405
|
+
engineSnapshot: undefined,
|
|
406
|
+
state: marked,
|
|
407
|
+
updatedAt: new Date().toISOString(),
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
return timeoutEvent;
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (!event)
|
|
417
|
+
return;
|
|
418
|
+
if (this.timeoutListener) {
|
|
419
|
+
try {
|
|
420
|
+
await this.timeoutListener(event);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
// listener 错误不得冒泡到 timer
|
|
424
|
+
}
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (event.behavior === 'auto-confirm') {
|
|
428
|
+
try {
|
|
429
|
+
await this.resume(taskId, event.defaultChoice ?? 'confirm', '__system_timeout__', event.threadId);
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
// ignore
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
clearWaiting(taskId) {
|
|
437
|
+
const waiting = this.waitingTasks.get(taskId);
|
|
438
|
+
if (waiting?.timer) {
|
|
439
|
+
clearTimeout(waiting.timer);
|
|
440
|
+
}
|
|
441
|
+
this.waitingTasks.delete(taskId);
|
|
442
|
+
}
|
|
443
|
+
async withTaskLock(taskId, fn) {
|
|
444
|
+
const prev = this.taskLocks.get(taskId) ?? Promise.resolve();
|
|
445
|
+
let release;
|
|
446
|
+
const held = new Promise((resolve) => {
|
|
447
|
+
release = resolve;
|
|
448
|
+
});
|
|
449
|
+
const next = prev.then(() => held);
|
|
450
|
+
this.taskLocks.set(taskId, next);
|
|
451
|
+
await prev;
|
|
452
|
+
try {
|
|
453
|
+
return await fn();
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
release();
|
|
457
|
+
if (this.taskLocks.get(taskId) === next) {
|
|
458
|
+
this.taskLocks.delete(taskId);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/** 查询某 task 是否在等待 HITL */
|
|
463
|
+
isWaiting(taskId) {
|
|
464
|
+
return this.waitingTasks.has(taskId);
|
|
465
|
+
}
|
|
466
|
+
/** 获取等待状态(测试/调试用) */
|
|
467
|
+
getWaitingState(taskId) {
|
|
468
|
+
return this.waitingTasks.get(taskId);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* 取出并清除 resume 写入的用户选择(确认拦截器轮询用)
|
|
472
|
+
*/
|
|
473
|
+
consumeResolvedChoice(taskId) {
|
|
474
|
+
const choice = this.resolvedChoices.get(taskId);
|
|
475
|
+
if (choice !== undefined) {
|
|
476
|
+
this.resolvedChoices.delete(taskId);
|
|
477
|
+
}
|
|
478
|
+
return choice;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './hitl-handler.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './hitl-handler.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './contracts/index.js';
|
|
2
|
+
export * from './adapters/index.js';
|
|
3
|
+
export * from './nodes/index.js';
|
|
4
|
+
export * from './retry/index.js';
|
|
5
|
+
export * from './hitl/index.js';
|
|
6
|
+
export { ConditionEvaluator, type ConditionEvaluatorFn } from './condition-evaluator.js';
|
|
7
|
+
export { SkillAuditRecorder, SkillInvocationContext, InMemorySkillAuditStore, type SkillInvocationRecord, type ISkillAuditStore, } from './skill-audit-recorder.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './contracts/index.js';
|
|
2
|
+
export * from './adapters/index.js';
|
|
3
|
+
export * from './nodes/index.js';
|
|
4
|
+
export * from './retry/index.js';
|
|
5
|
+
export * from './hitl/index.js';
|
|
6
|
+
export { ConditionEvaluator } from './condition-evaluator.js';
|
|
7
|
+
export { SkillAuditRecorder, SkillInvocationContext, InMemorySkillAuditStore, } from './skill-audit-recorder.js';
|
|
@@ -0,0 +1,182 @@
|
|
|
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 type { NodeRetryConfig, GraphMessage } from '../contracts/executable-graph.js';
|
|
13
|
+
/**
|
|
14
|
+
* LLMNodeConfig — LLM 节点配置
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* LLMToolDefinition — 传给 LLM 的 Tool 定义(OpenAI function-calling 格式)
|
|
18
|
+
*/
|
|
19
|
+
export interface LLMToolDefinition {
|
|
20
|
+
type: 'function';
|
|
21
|
+
function: {
|
|
22
|
+
name: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
parameters?: Record<string, unknown>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface LLMNodeConfig {
|
|
28
|
+
/** 模型名称(如 gpt-4o, claude-3-sonnet) */
|
|
29
|
+
model: string;
|
|
30
|
+
/** 系统提示词 */
|
|
31
|
+
systemPrompt?: string;
|
|
32
|
+
/** 温度参数 (0-2) */
|
|
33
|
+
temperature?: number;
|
|
34
|
+
/** 最大输出 Token 数 */
|
|
35
|
+
maxTokens?: number;
|
|
36
|
+
/** 重试配置(默认 3 次 + 指数退避) */
|
|
37
|
+
retry?: NodeRetryConfig;
|
|
38
|
+
/** 可用 Tool 定义列表(function-calling) */
|
|
39
|
+
tools?: LLMToolDefinition[];
|
|
40
|
+
/** 强制 LLM 调用指定 Tool(auto=默认, none=禁用, required=强制) */
|
|
41
|
+
toolChoice?: 'auto' | 'none' | 'required' | {
|
|
42
|
+
type: 'function';
|
|
43
|
+
function: {
|
|
44
|
+
name: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* LLMResponse — LLM 完整响应
|
|
50
|
+
*/
|
|
51
|
+
export interface LLMResponse {
|
|
52
|
+
/** 生成的文本内容 */
|
|
53
|
+
content: string;
|
|
54
|
+
/** Tool 调用列表(function-calling) */
|
|
55
|
+
toolCalls?: LLMNodeToolCall[];
|
|
56
|
+
/** thinking 模式 reasoning(需多轮回传) */
|
|
57
|
+
reasoningContent?: string;
|
|
58
|
+
/** 输入 Token 数 */
|
|
59
|
+
inputTokens: number;
|
|
60
|
+
/** 输出 Token 数 */
|
|
61
|
+
outputTokens: number;
|
|
62
|
+
/** 实际使用的模型标识 */
|
|
63
|
+
model: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* LLMNodeToolCall — LLM 节点中返回的 Tool 调用(扁平格式)
|
|
67
|
+
*
|
|
68
|
+
* 与 tool-orchestrator 中的 LLMToolCall (OpenAI 格式) 互补,
|
|
69
|
+
* 此类型用于 LLMNode 内部状态流转。
|
|
70
|
+
*/
|
|
71
|
+
export interface LLMNodeToolCall {
|
|
72
|
+
id: string;
|
|
73
|
+
name: string;
|
|
74
|
+
arguments: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* LLMChunk — 流式输出的单个 chunk
|
|
78
|
+
*/
|
|
79
|
+
export interface LLMChunk {
|
|
80
|
+
/** chunk 文本内容 */
|
|
81
|
+
content: string;
|
|
82
|
+
/** 是否为最后一个 chunk */
|
|
83
|
+
isEnd: boolean;
|
|
84
|
+
/** 最终 chunk 携带的输入 Token 数 */
|
|
85
|
+
inputTokens?: number;
|
|
86
|
+
/** 最终 chunk 携带的输出 Token 数 */
|
|
87
|
+
outputTokens?: number;
|
|
88
|
+
/** 流式 tool_calls 增量(最终 chunk 携带) */
|
|
89
|
+
toolCalls?: LLMNodeToolCall[];
|
|
90
|
+
/** thinking 增量;最终聚合后写入 assistant 消息 */
|
|
91
|
+
reasoningContent?: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* ILLMProvider — LLM 调用提供者接口
|
|
95
|
+
*
|
|
96
|
+
* 抽象 LLM 调用层,使 LLMNode 不依赖具体 LLM SDK。
|
|
97
|
+
*/
|
|
98
|
+
export interface ILLMProvider {
|
|
99
|
+
/** 同步调用 LLM,返回完整响应 */
|
|
100
|
+
invoke(messages: GraphMessage[], config: LLMNodeConfig): Promise<LLMResponse>;
|
|
101
|
+
/** 流式调用 LLM,逐 chunk 返回 */
|
|
102
|
+
stream(messages: GraphMessage[], config: LLMNodeConfig): AsyncIterableIterator<LLMChunk>;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* LangGraphState — LangGraph 内部状态(与 adapter 对齐)
|
|
106
|
+
*/
|
|
107
|
+
type LangGraphState = Record<string, unknown>;
|
|
108
|
+
/**
|
|
109
|
+
* LLMNode — LLM 执行节点
|
|
110
|
+
*
|
|
111
|
+
* 在 StateGraph 中作为 node function 注册,负责:
|
|
112
|
+
* 1. 从 state 中提取 messages
|
|
113
|
+
* 2. 调用 ILLMProvider 执行推理(支持 invoke / stream 模式)
|
|
114
|
+
* 3. 统计 Token 消耗
|
|
115
|
+
* 4. 失败时按 NodeRetryConfig 重试
|
|
116
|
+
* 5. 将 LLM 响应追加到 state.messages 并返回
|
|
117
|
+
*/
|
|
118
|
+
export declare class LLMNode {
|
|
119
|
+
private readonly config;
|
|
120
|
+
private readonly provider;
|
|
121
|
+
private readonly retryConfig;
|
|
122
|
+
private readonly nodeId;
|
|
123
|
+
private readonly streaming;
|
|
124
|
+
private readonly onChunk?;
|
|
125
|
+
/**
|
|
126
|
+
* @param nodeId - 节点唯一标识
|
|
127
|
+
* @param config - LLM 节点配置
|
|
128
|
+
* @param provider - LLM 调用提供者
|
|
129
|
+
* @param streaming - 是否启用流式输出(默认 false)
|
|
130
|
+
* @param onChunk - 流式模式下每个文本片段回调(用于 SSE response.chunk)
|
|
131
|
+
*/
|
|
132
|
+
constructor(nodeId: string, config: LLMNodeConfig, provider: ILLMProvider, streaming?: boolean, onChunk?: (chunk: string) => void);
|
|
133
|
+
/**
|
|
134
|
+
* execute — StateGraph 节点函数
|
|
135
|
+
*
|
|
136
|
+
* 从 state 提取 messages → 调用 LLM → 处理重试 → 返回更新后的 state
|
|
137
|
+
*/
|
|
138
|
+
execute(state: LangGraphState): Promise<LangGraphState>;
|
|
139
|
+
/**
|
|
140
|
+
* 获取节点 ID
|
|
141
|
+
*/
|
|
142
|
+
getNodeId(): string;
|
|
143
|
+
/**
|
|
144
|
+
* 获取节点配置
|
|
145
|
+
*/
|
|
146
|
+
getConfig(): LLMNodeConfig;
|
|
147
|
+
/**
|
|
148
|
+
* 从 state 中提取 messages 并转换为 GraphMessage[]
|
|
149
|
+
*/
|
|
150
|
+
private extractMessages;
|
|
151
|
+
/**
|
|
152
|
+
* 在 messages 前追加 system prompt(兑底注入)
|
|
153
|
+
*
|
|
154
|
+
* 设计说明:
|
|
155
|
+
* - 当 config.systemPrompt 已设置时,HttpLLMProvider.buildRequestBody 已基于
|
|
156
|
+
* config.systemPrompt 注入过 system message,此处**不再重复添加**,避免 messages
|
|
157
|
+
* 里出现两条相同的 system。
|
|
158
|
+
* - 仅当 config.systemPrompt 未设置、且 messages 首条不是 system 时,才兑底加一条
|
|
159
|
+
* 空 system 占位(某些上游调用可能不通过 HttpLLMProvider,保留此兑底安全)。
|
|
160
|
+
*/
|
|
161
|
+
private prependSystemPrompt;
|
|
162
|
+
/**
|
|
163
|
+
* 带重试逻辑的 LLM 调用
|
|
164
|
+
*
|
|
165
|
+
* 根据 streaming 配置选择 invoke 或 stream 模式。
|
|
166
|
+
* 失败时按 backoff 策略重试,最多重试 maxRetries 次。
|
|
167
|
+
*/
|
|
168
|
+
private invokeWithRetry;
|
|
169
|
+
/**
|
|
170
|
+
* 流式调用 LLM 并聚合为完整 LLMResponse
|
|
171
|
+
*/
|
|
172
|
+
private invokeStreaming;
|
|
173
|
+
/**
|
|
174
|
+
* 计算退避延迟(支持 exponential / linear / fixed)
|
|
175
|
+
*/
|
|
176
|
+
private calculateBackoffDelay;
|
|
177
|
+
/**
|
|
178
|
+
* 延迟等待
|
|
179
|
+
*/
|
|
180
|
+
private sleep;
|
|
181
|
+
}
|
|
182
|
+
export {};
|