@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,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 多轮对话 Message 写回辅助
|
|
3
|
+
*
|
|
4
|
+
* invoke / stream 主路径在执行前后调用,保证 Session 短期记忆可被下一轮加载。
|
|
5
|
+
*/
|
|
6
|
+
export async function persistUserTurnMessage(messageService, params) {
|
|
7
|
+
if (!messageService || !params.content)
|
|
8
|
+
return;
|
|
9
|
+
await messageService.addMessage({
|
|
10
|
+
sessionId: params.sessionId,
|
|
11
|
+
taskId: params.taskId,
|
|
12
|
+
correlationId: params.correlationId,
|
|
13
|
+
role: 'user',
|
|
14
|
+
content: params.content,
|
|
15
|
+
tokenCount: 0,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export async function persistAssistantTurnMessage(messageService, params) {
|
|
19
|
+
if (!messageService)
|
|
20
|
+
return;
|
|
21
|
+
const content = params.content?.trim() ?? '';
|
|
22
|
+
if (!content && !(params.toolCalls && params.toolCalls.length > 0))
|
|
23
|
+
return;
|
|
24
|
+
await messageService.addMessage({
|
|
25
|
+
sessionId: params.sessionId,
|
|
26
|
+
taskId: params.taskId,
|
|
27
|
+
correlationId: params.correlationId,
|
|
28
|
+
role: 'assistant',
|
|
29
|
+
content: content || '(tool_calls)',
|
|
30
|
+
toolCalls: params.toolCalls,
|
|
31
|
+
tokenCount: params.tokenCount ?? 0,
|
|
32
|
+
reasoning: params.reasoningContent
|
|
33
|
+
? { reasoningContent: params.reasoningContent }
|
|
34
|
+
: null,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* HITL 等待态占位消息:避免早退时本轮只有 user、历史偏薄
|
|
39
|
+
*
|
|
40
|
+
* 以 assistant 写入确认问题;metadata.hitlWaiting=true 供后续过滤/展示。
|
|
41
|
+
*/
|
|
42
|
+
export async function persistHitlWaitingPlaceholder(messageService, params) {
|
|
43
|
+
if (!messageService)
|
|
44
|
+
return;
|
|
45
|
+
const question = params.question?.trim() || '等待人工确认';
|
|
46
|
+
await messageService.addMessage({
|
|
47
|
+
sessionId: params.sessionId,
|
|
48
|
+
taskId: params.taskId,
|
|
49
|
+
correlationId: params.correlationId,
|
|
50
|
+
role: 'assistant',
|
|
51
|
+
content: question,
|
|
52
|
+
tokenCount: 0,
|
|
53
|
+
metadata: {
|
|
54
|
+
hitlWaiting: true,
|
|
55
|
+
hitlOptions: params.options,
|
|
56
|
+
hitlThreadId: params.threadId,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export async function persistToolTurnMessage(messageService, params) {
|
|
61
|
+
if (!messageService)
|
|
62
|
+
return;
|
|
63
|
+
const content = typeof params.content === 'string'
|
|
64
|
+
? params.content
|
|
65
|
+
: JSON.stringify(params.content ?? null);
|
|
66
|
+
await messageService.addMessage({
|
|
67
|
+
sessionId: params.sessionId,
|
|
68
|
+
taskId: params.taskId,
|
|
69
|
+
correlationId: params.correlationId,
|
|
70
|
+
role: 'tool',
|
|
71
|
+
content,
|
|
72
|
+
toolCalls: [
|
|
73
|
+
{
|
|
74
|
+
id: params.toolCallId,
|
|
75
|
+
name: params.toolName,
|
|
76
|
+
success: params.success,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
tokenCount: 0,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** 从流式 RuntimeEvent payload 抽取文本增量 */
|
|
83
|
+
export function extractStreamTextDelta(eventType, payload) {
|
|
84
|
+
if (eventType !== 'response.chunk' &&
|
|
85
|
+
eventType !== 'response.delta' &&
|
|
86
|
+
eventType !== 'response.final') {
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
if (!payload || typeof payload !== 'object')
|
|
90
|
+
return '';
|
|
91
|
+
const p = payload;
|
|
92
|
+
const text = p.chunk ?? p.delta ?? p.content;
|
|
93
|
+
return typeof text === 'string' ? text : '';
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 从流式事件抽取 assistant reasoning。
|
|
97
|
+
* 子图在 skill.completed 上回传(父图不再为 subgraph 节点发空 response.chunk)。
|
|
98
|
+
*/
|
|
99
|
+
export function extractStreamReasoning(eventType, payload) {
|
|
100
|
+
if (eventType !== 'response.chunk' &&
|
|
101
|
+
eventType !== 'response.delta' &&
|
|
102
|
+
eventType !== 'response.final' &&
|
|
103
|
+
eventType !== 'skill.completed') {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
if (!payload || typeof payload !== 'object')
|
|
107
|
+
return undefined;
|
|
108
|
+
const value = payload.reasoningContent;
|
|
109
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
110
|
+
}
|
|
111
|
+
/** 历史 MessageRecord → GraphMessage(保留 tool 元数据与 reasoning) */
|
|
112
|
+
export function messageRecordsToGraphMessages(records) {
|
|
113
|
+
return records.map((m) => {
|
|
114
|
+
const base = {
|
|
115
|
+
role: m.role,
|
|
116
|
+
content: m.content,
|
|
117
|
+
};
|
|
118
|
+
if (m.toolCalls && m.toolCalls.length > 0) {
|
|
119
|
+
base.toolCalls = m.toolCalls;
|
|
120
|
+
const first = m.toolCalls[0];
|
|
121
|
+
if (m.role === 'tool') {
|
|
122
|
+
if (first?.id)
|
|
123
|
+
base.toolCallId = first.id;
|
|
124
|
+
if (first?.name)
|
|
125
|
+
base.toolName = first.name;
|
|
126
|
+
if (typeof first?.success === 'boolean')
|
|
127
|
+
base.success = first.success;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const reasoningContent = extractReasoningContent(m.reasoning);
|
|
131
|
+
if (reasoningContent)
|
|
132
|
+
base.reasoningContent = reasoningContent;
|
|
133
|
+
return base;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function extractReasoningContent(reasoning) {
|
|
137
|
+
if (typeof reasoning === 'string' && reasoning) {
|
|
138
|
+
const trimmed = reasoning.trim();
|
|
139
|
+
if (trimmed.startsWith('{')) {
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(trimmed);
|
|
142
|
+
if (typeof parsed.reasoningContent === 'string' && parsed.reasoningContent) {
|
|
143
|
+
return parsed.reasoningContent;
|
|
144
|
+
}
|
|
145
|
+
if (typeof parsed.content === 'string' && parsed.content) {
|
|
146
|
+
return parsed.content;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// 非 JSON 字符串,按原文回传
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return reasoning;
|
|
154
|
+
}
|
|
155
|
+
if (!reasoning || typeof reasoning !== 'object')
|
|
156
|
+
return undefined;
|
|
157
|
+
const r = reasoning;
|
|
158
|
+
if (typeof r.reasoningContent === 'string' && r.reasoningContent)
|
|
159
|
+
return r.reasoningContent;
|
|
160
|
+
if (typeof r.content === 'string' && r.content)
|
|
161
|
+
return r.content;
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 组装本轮送入 Graph 的 messages。
|
|
166
|
+
*
|
|
167
|
+
* 契约:
|
|
168
|
+
* - Session 消息是产品侧短期记忆(展示/落库)
|
|
169
|
+
* - Session 级 Checkpoint 持有图内 messages(含 reasoning)
|
|
170
|
+
* - 当 Checkpoint 已有历史时,只追加本轮 user,避免双重 append
|
|
171
|
+
* - Checkpoint 缺失/无 messages 时,回退 Session 全量历史
|
|
172
|
+
*/
|
|
173
|
+
export async function buildTurnGraphMessages(params) {
|
|
174
|
+
const newUser = { role: 'user', content: params.input };
|
|
175
|
+
const history = params.history;
|
|
176
|
+
if (!params.checkpointSaver || !params.threadId || history.length === 0) {
|
|
177
|
+
return [...history, newUser];
|
|
178
|
+
}
|
|
179
|
+
const cp = await params.checkpointSaver.load(params.threadId);
|
|
180
|
+
if (checkpointHasMessages(cp)) {
|
|
181
|
+
return [newUser];
|
|
182
|
+
}
|
|
183
|
+
return [...history, newUser];
|
|
184
|
+
}
|
|
185
|
+
function checkpointHasMessages(cp) {
|
|
186
|
+
if (!cp)
|
|
187
|
+
return false;
|
|
188
|
+
if (Array.isArray(cp.state?.messages) && cp.state.messages.length > 0)
|
|
189
|
+
return true;
|
|
190
|
+
const channelValues = cp.engineSnapshot?.['channel_values'];
|
|
191
|
+
if (channelValues && typeof channelValues === 'object') {
|
|
192
|
+
const messages = channelValues['messages'];
|
|
193
|
+
if (Array.isArray(messages) && messages.length > 0)
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
export function buildHitlWaitingTaskOutput(params) {
|
|
199
|
+
return {
|
|
200
|
+
hitlWaiting: true,
|
|
201
|
+
hitlThreadId: params.threadId,
|
|
202
|
+
question: params.question?.trim() || '等待人工确认',
|
|
203
|
+
options: params.options,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/** 从 Task.output 解析 Checkpoint thread_id(含嵌套 hitl.threadId) */
|
|
207
|
+
export function extractHitlThreadId(output) {
|
|
208
|
+
if (!output || typeof output !== 'object')
|
|
209
|
+
return undefined;
|
|
210
|
+
const o = output;
|
|
211
|
+
if (typeof o.hitlThreadId === 'string' && o.hitlThreadId.trim()) {
|
|
212
|
+
return o.hitlThreadId.trim();
|
|
213
|
+
}
|
|
214
|
+
const hitl = o.hitl;
|
|
215
|
+
if (hitl && typeof hitl === 'object') {
|
|
216
|
+
const threadId = hitl.threadId;
|
|
217
|
+
if (typeof threadId === 'string' && threadId.trim())
|
|
218
|
+
return threadId.trim();
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Checkpoint thread_id 契约:HITL/多轮优先且应唯一等于 sessionId;
|
|
224
|
+
* 无 session 时退到 `task-${taskId}`。勿用 correlationId 作为 Checkpoint key。
|
|
225
|
+
*
|
|
226
|
+
* 注意:session 级 thread + 全量 Session 历史同时注入会导致 messages 双重 append。
|
|
227
|
+
* 组装本轮 Graph 输入请用 {@link buildTurnGraphMessages}(有 Checkpoint 历史则只追加 user)。
|
|
228
|
+
*/
|
|
229
|
+
export function resolveCheckpointThreadId(params) {
|
|
230
|
+
if (params.sessionId)
|
|
231
|
+
return params.sessionId;
|
|
232
|
+
if (params.taskId)
|
|
233
|
+
return `task-${params.taskId}`;
|
|
234
|
+
if (params.graphId)
|
|
235
|
+
return `thread-${params.graphId}`;
|
|
236
|
+
return `thread-${Date.now()}`;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 将 stream 侧累加的 tool 事件规范为与 invoke GraphState 增量同序的 messages:
|
|
240
|
+
* assistant(tool_calls) → tool* → assistant(text)
|
|
241
|
+
*/
|
|
242
|
+
export function buildTurnTranscriptFromStreamEvents(params) {
|
|
243
|
+
const out = [];
|
|
244
|
+
const pending = params.pendingToolCalls ?? [];
|
|
245
|
+
const results = params.toolResults ?? [];
|
|
246
|
+
const text = params.assistantText ?? '';
|
|
247
|
+
if (pending.length > 0) {
|
|
248
|
+
out.push({
|
|
249
|
+
role: 'assistant',
|
|
250
|
+
content: '',
|
|
251
|
+
toolCalls: pending.map((tc) => ({
|
|
252
|
+
id: tc.id,
|
|
253
|
+
name: tc.name,
|
|
254
|
+
arguments: tc.arguments,
|
|
255
|
+
})),
|
|
256
|
+
...(params.toolCallReasoningContent
|
|
257
|
+
? { reasoningContent: params.toolCallReasoningContent }
|
|
258
|
+
: {}),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
for (const tr of results) {
|
|
262
|
+
out.push({
|
|
263
|
+
role: 'tool',
|
|
264
|
+
content: typeof tr.content === 'string' ? tr.content : JSON.stringify(tr.content ?? null),
|
|
265
|
+
toolCallId: tr.toolCallId,
|
|
266
|
+
toolName: tr.toolName,
|
|
267
|
+
success: tr.success,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (text.trim() || (params.persistEmptyAssistant && out.length === 0)) {
|
|
271
|
+
out.push({
|
|
272
|
+
role: 'assistant',
|
|
273
|
+
content: text,
|
|
274
|
+
...(params.assistantReasoningContent
|
|
275
|
+
? { reasoningContent: params.assistantReasoningContent }
|
|
276
|
+
: {}),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* 统一落库本轮 transcript(invoke / stream / AgentRuntime.stream 共用)
|
|
283
|
+
*/
|
|
284
|
+
export async function persistTurnTranscript(messageService, params) {
|
|
285
|
+
if (!messageService)
|
|
286
|
+
return;
|
|
287
|
+
let hasAssistantText = false;
|
|
288
|
+
for (const m of params.messages) {
|
|
289
|
+
if (m.role === 'assistant') {
|
|
290
|
+
const isToolCallOnly = Boolean(m.toolCalls?.length) && !(m.content?.trim());
|
|
291
|
+
if (!isToolCallOnly && (m.content?.trim() ?? '')) {
|
|
292
|
+
hasAssistantText = true;
|
|
293
|
+
}
|
|
294
|
+
await persistAssistantTurnMessage(messageService, {
|
|
295
|
+
sessionId: params.sessionId,
|
|
296
|
+
taskId: params.taskId,
|
|
297
|
+
correlationId: params.correlationId,
|
|
298
|
+
content: m.content ?? '',
|
|
299
|
+
toolCalls: m.toolCalls,
|
|
300
|
+
tokenCount: isToolCallOnly ? 0 : params.assistantTokenCount,
|
|
301
|
+
reasoningContent: m.reasoningContent,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
else if (m.role === 'tool') {
|
|
305
|
+
await persistToolTurnMessage(messageService, {
|
|
306
|
+
sessionId: params.sessionId,
|
|
307
|
+
taskId: params.taskId,
|
|
308
|
+
correlationId: params.correlationId,
|
|
309
|
+
toolCallId: m.toolCallId ?? 'unknown',
|
|
310
|
+
toolName: m.toolName ?? 'unknown',
|
|
311
|
+
content: m.content,
|
|
312
|
+
success: m.success !== false,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (!hasAssistantText && params.fallbackAssistantContent != null) {
|
|
317
|
+
await persistAssistantTurnMessage(messageService, {
|
|
318
|
+
sessionId: params.sessionId,
|
|
319
|
+
taskId: params.taskId,
|
|
320
|
+
correlationId: params.correlationId,
|
|
321
|
+
content: params.fallbackAssistantContent,
|
|
322
|
+
tokenCount: params.assistantTokenCount,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgresMessageRepository — Message 持久化 PostgreSQL 实现
|
|
3
|
+
*
|
|
4
|
+
* 基于 pg 连接池实现 IFullMessageRepository 接口。
|
|
5
|
+
* JSON 字段(tool_calls / reasoning / metadata)使用 JSONB 类型存储。
|
|
6
|
+
*
|
|
7
|
+
* @see design.md §1.3 数据模型 — messages 表
|
|
8
|
+
* @see message.service.ts IFullMessageRepository 接口定义
|
|
9
|
+
*/
|
|
10
|
+
import type { MessageRecord } from './session.service.js';
|
|
11
|
+
import type { IFullMessageRepository } from './message.service.js';
|
|
12
|
+
/** pg Pool 抽象接口 */
|
|
13
|
+
export interface IPgPool {
|
|
14
|
+
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<{
|
|
15
|
+
rows: T[];
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
export declare class PostgresMessageRepository implements IFullMessageRepository {
|
|
19
|
+
private readonly pool;
|
|
20
|
+
constructor(pool: IPgPool);
|
|
21
|
+
/**
|
|
22
|
+
* INSERT message record with audit fields
|
|
23
|
+
*/
|
|
24
|
+
save(message: MessageRecord): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* SELECT messages by session_id, ORDER BY created_at ASC, 支持分页
|
|
27
|
+
* Excludes soft-deleted records
|
|
28
|
+
*/
|
|
29
|
+
findBySessionId(sessionId: string, limit?: number, offset?: number): Promise<MessageRecord[]>;
|
|
30
|
+
/**
|
|
31
|
+
* SELECT messages by task_id, ORDER BY created_at ASC
|
|
32
|
+
* Excludes soft-deleted records
|
|
33
|
+
*/
|
|
34
|
+
findByTaskId(taskId: string): Promise<MessageRecord[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Soft delete message
|
|
37
|
+
*/
|
|
38
|
+
saveMany(messages: MessageRecord[]): Promise<void>;
|
|
39
|
+
markAsCompressed(messageIds: string[], compressedBy: string, compressedAt: string): Promise<void>;
|
|
40
|
+
softDelete(id: string, deletedBy: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* 将 PostgreSQL 行数据转换为 MessageRecord
|
|
43
|
+
*/
|
|
44
|
+
private rowToRecord;
|
|
45
|
+
/**
|
|
46
|
+
* 解析 JSON 字段 — pg 可能已自动解析 JSONB 列,也可能返回字符串
|
|
47
|
+
*/
|
|
48
|
+
private parseJson;
|
|
49
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgresMessageRepository — Message 持久化 PostgreSQL 实现
|
|
3
|
+
*
|
|
4
|
+
* 基于 pg 连接池实现 IFullMessageRepository 接口。
|
|
5
|
+
* JSON 字段(tool_calls / reasoning / metadata)使用 JSONB 类型存储。
|
|
6
|
+
*
|
|
7
|
+
* @see design.md §1.3 数据模型 — messages 表
|
|
8
|
+
* @see message.service.ts IFullMessageRepository 接口定义
|
|
9
|
+
*/
|
|
10
|
+
// ─────────────────────────────────────────────
|
|
11
|
+
// PostgresMessageRepository
|
|
12
|
+
// ─────────────────────────────────────────────
|
|
13
|
+
export class PostgresMessageRepository {
|
|
14
|
+
pool;
|
|
15
|
+
constructor(pool) {
|
|
16
|
+
this.pool = pool;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* INSERT message record with audit fields
|
|
20
|
+
*/
|
|
21
|
+
async save(message) {
|
|
22
|
+
const sql = `
|
|
23
|
+
INSERT INTO messages (
|
|
24
|
+
id, session_id, task_id, correlation_id, role, content, tool_calls,
|
|
25
|
+
reasoning, metadata, token_count, duration_ms,
|
|
26
|
+
created_by, created_at, updated_by, updated_at
|
|
27
|
+
)
|
|
28
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
|
29
|
+
`;
|
|
30
|
+
await this.pool.query(sql, [
|
|
31
|
+
message.id,
|
|
32
|
+
message.sessionId,
|
|
33
|
+
message.taskId,
|
|
34
|
+
message.correlationId,
|
|
35
|
+
message.role,
|
|
36
|
+
message.content,
|
|
37
|
+
message.toolCalls != null ? JSON.stringify(message.toolCalls) : null,
|
|
38
|
+
message.reasoning != null ? JSON.stringify(message.reasoning) : null,
|
|
39
|
+
JSON.stringify(message.metadata ?? {}),
|
|
40
|
+
message.tokenCount,
|
|
41
|
+
message.durationMs ?? null,
|
|
42
|
+
message.createdBy || 'system',
|
|
43
|
+
message.createdAt,
|
|
44
|
+
message.updatedBy || 'system',
|
|
45
|
+
message.updatedAt,
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* SELECT messages by session_id, ORDER BY created_at ASC, 支持分页
|
|
50
|
+
* Excludes soft-deleted records
|
|
51
|
+
*/
|
|
52
|
+
async findBySessionId(sessionId, limit, offset) {
|
|
53
|
+
let sql = `SELECT * FROM messages WHERE session_id = $1 AND is_deleted = FALSE ORDER BY created_at ASC`;
|
|
54
|
+
const params = [sessionId];
|
|
55
|
+
if (limit !== undefined) {
|
|
56
|
+
sql += ` LIMIT $${params.length + 1}`;
|
|
57
|
+
params.push(limit);
|
|
58
|
+
}
|
|
59
|
+
if (offset !== undefined) {
|
|
60
|
+
sql += ` OFFSET $${params.length + 1}`;
|
|
61
|
+
params.push(offset);
|
|
62
|
+
}
|
|
63
|
+
const { rows } = await this.pool.query(sql, params);
|
|
64
|
+
return rows.map((row) => this.rowToRecord(row));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* SELECT messages by task_id, ORDER BY created_at ASC
|
|
68
|
+
* Excludes soft-deleted records
|
|
69
|
+
*/
|
|
70
|
+
async findByTaskId(taskId) {
|
|
71
|
+
const sql = `SELECT * FROM messages WHERE task_id = $1 AND is_deleted = FALSE ORDER BY created_at ASC`;
|
|
72
|
+
const { rows } = await this.pool.query(sql, [taskId]);
|
|
73
|
+
return rows.map((row) => this.rowToRecord(row));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Soft delete message
|
|
77
|
+
*/
|
|
78
|
+
async saveMany(messages) {
|
|
79
|
+
for (const message of messages) {
|
|
80
|
+
await this.save(message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async markAsCompressed(messageIds, compressedBy, compressedAt) {
|
|
84
|
+
if (messageIds.length === 0)
|
|
85
|
+
return;
|
|
86
|
+
const placeholders = messageIds.map((_, i) => `$${i + 4}`).join(',');
|
|
87
|
+
const patch = JSON.stringify({
|
|
88
|
+
compressed: true,
|
|
89
|
+
compressedBy,
|
|
90
|
+
compressedAt,
|
|
91
|
+
});
|
|
92
|
+
const sql = `
|
|
93
|
+
UPDATE messages
|
|
94
|
+
SET metadata = COALESCE(metadata, '{}'::jsonb) || $1::jsonb,
|
|
95
|
+
updated_by = $2,
|
|
96
|
+
updated_at = $3
|
|
97
|
+
WHERE id IN (${placeholders}) AND is_deleted = FALSE
|
|
98
|
+
`;
|
|
99
|
+
await this.pool.query(sql, [patch, compressedBy, compressedAt, ...messageIds]);
|
|
100
|
+
}
|
|
101
|
+
async softDelete(id, deletedBy) {
|
|
102
|
+
const sql = `
|
|
103
|
+
UPDATE messages
|
|
104
|
+
SET is_deleted = TRUE, deleted_by = $1, deleted_at = NOW()
|
|
105
|
+
WHERE id = $2 AND is_deleted = FALSE
|
|
106
|
+
`;
|
|
107
|
+
await this.pool.query(sql, [deletedBy, id]);
|
|
108
|
+
}
|
|
109
|
+
// ─────────────────────────────────────────────
|
|
110
|
+
// Private Helpers
|
|
111
|
+
// ─────────────────────────────────────────────
|
|
112
|
+
/**
|
|
113
|
+
* 将 PostgreSQL 行数据转换为 MessageRecord
|
|
114
|
+
*/
|
|
115
|
+
rowToRecord(row) {
|
|
116
|
+
const metadataRaw = row.metadata != null ? this.parseJson(row.metadata) : {};
|
|
117
|
+
return {
|
|
118
|
+
id: row.id,
|
|
119
|
+
sessionId: row.session_id,
|
|
120
|
+
taskId: row.task_id,
|
|
121
|
+
correlationId: row.correlation_id,
|
|
122
|
+
role: row.role,
|
|
123
|
+
content: row.content,
|
|
124
|
+
toolCalls: row.tool_calls != null ? this.parseJson(row.tool_calls) : null,
|
|
125
|
+
reasoning: row.reasoning != null ? this.parseJson(row.reasoning) : null,
|
|
126
|
+
metadata: metadataRaw && typeof metadataRaw === 'object' && !Array.isArray(metadataRaw)
|
|
127
|
+
? metadataRaw
|
|
128
|
+
: {},
|
|
129
|
+
tokenCount: row.token_count,
|
|
130
|
+
durationMs: row.duration_ms,
|
|
131
|
+
createdBy: row.created_by,
|
|
132
|
+
createdAt: row.created_at.toISOString(),
|
|
133
|
+
updatedBy: row.updated_by,
|
|
134
|
+
updatedAt: row.updated_at.toISOString(),
|
|
135
|
+
isDeleted: row.is_deleted,
|
|
136
|
+
deletedBy: row.deleted_by,
|
|
137
|
+
deletedAt: row.deleted_at?.toISOString(),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 解析 JSON 字段 — pg 可能已自动解析 JSONB 列,也可能返回字符串
|
|
142
|
+
*/
|
|
143
|
+
parseJson(value) {
|
|
144
|
+
if (typeof value === 'string') {
|
|
145
|
+
return JSON.parse(value);
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgresSessionRepository — Session 持久化 PostgreSQL 实现
|
|
3
|
+
*
|
|
4
|
+
* 基于 pg 连接池实现 ISessionRepository 接口。
|
|
5
|
+
* JSON 字段(metadata)使用 JSONB 类型存储。
|
|
6
|
+
*
|
|
7
|
+
* @see design.md §1.3 数据模型 — sessions 表
|
|
8
|
+
* @see session.service.ts ISessionRepository 接口定义
|
|
9
|
+
*/
|
|
10
|
+
import type { SessionRecord, ISessionRepository } from './session.service.js';
|
|
11
|
+
/** pg Pool 抽象接口 */
|
|
12
|
+
export interface IPgPool {
|
|
13
|
+
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<{
|
|
14
|
+
rows: T[];
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare class PostgresSessionRepository implements ISessionRepository {
|
|
18
|
+
private readonly pool;
|
|
19
|
+
constructor(pool: IPgPool);
|
|
20
|
+
/**
|
|
21
|
+
* INSERT session record with all fields including audit fields
|
|
22
|
+
*/
|
|
23
|
+
save(session: SessionRecord): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* SELECT session by id, deserialize JSON fields
|
|
26
|
+
* Excludes soft-deleted records
|
|
27
|
+
*/
|
|
28
|
+
findById(id: string): Promise<SessionRecord | null>;
|
|
29
|
+
/**
|
|
30
|
+
* UPDATE all mutable fields by id
|
|
31
|
+
*/
|
|
32
|
+
findByUserId(userId: string, limit?: number, offset?: number): Promise<SessionRecord[]>;
|
|
33
|
+
update(session: SessionRecord): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Soft delete session
|
|
36
|
+
*/
|
|
37
|
+
softDelete(id: string, deletedBy: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* 将 PostgreSQL 行数据转换为 SessionRecord
|
|
40
|
+
*/
|
|
41
|
+
private rowToRecord;
|
|
42
|
+
/**
|
|
43
|
+
* 解析 JSON 字段 — pg 可能已自动解析 JSONB 列,也可能返回字符串
|
|
44
|
+
*/
|
|
45
|
+
private parseJson;
|
|
46
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgresSessionRepository — Session 持久化 PostgreSQL 实现
|
|
3
|
+
*
|
|
4
|
+
* 基于 pg 连接池实现 ISessionRepository 接口。
|
|
5
|
+
* JSON 字段(metadata)使用 JSONB 类型存储。
|
|
6
|
+
*
|
|
7
|
+
* @see design.md §1.3 数据模型 — sessions 表
|
|
8
|
+
* @see session.service.ts ISessionRepository 接口定义
|
|
9
|
+
*/
|
|
10
|
+
// ─────────────────────────────────────────────
|
|
11
|
+
// PostgresSessionRepository
|
|
12
|
+
// ─────────────────────────────────────────────
|
|
13
|
+
export class PostgresSessionRepository {
|
|
14
|
+
pool;
|
|
15
|
+
constructor(pool) {
|
|
16
|
+
this.pool = pool;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* INSERT session record with all fields including audit fields
|
|
20
|
+
*/
|
|
21
|
+
async save(session) {
|
|
22
|
+
const sql = `
|
|
23
|
+
INSERT INTO sessions (id, user_id, agent_id, title, status, metadata, created_by, created_at, updated_by, updated_at)
|
|
24
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
25
|
+
`;
|
|
26
|
+
await this.pool.query(sql, [
|
|
27
|
+
session.id,
|
|
28
|
+
session.userId,
|
|
29
|
+
session.agentId,
|
|
30
|
+
session.title,
|
|
31
|
+
session.status,
|
|
32
|
+
JSON.stringify(session.metadata),
|
|
33
|
+
session.createdBy || 'system',
|
|
34
|
+
session.createdAt,
|
|
35
|
+
session.updatedBy || 'system',
|
|
36
|
+
session.updatedAt,
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* SELECT session by id, deserialize JSON fields
|
|
41
|
+
* Excludes soft-deleted records
|
|
42
|
+
*/
|
|
43
|
+
async findById(id) {
|
|
44
|
+
const sql = `SELECT * FROM sessions WHERE id = $1 AND is_deleted = FALSE`;
|
|
45
|
+
const { rows } = await this.pool.query(sql, [id]);
|
|
46
|
+
if (rows.length === 0) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return this.rowToRecord(rows[0]);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* UPDATE all mutable fields by id
|
|
53
|
+
*/
|
|
54
|
+
async findByUserId(userId, limit, offset) {
|
|
55
|
+
const sql = `SELECT * FROM sessions WHERE user_id = $1 AND is_deleted = false ORDER BY created_at DESC LIMIT $2 OFFSET $3`;
|
|
56
|
+
const result = await this.pool.query(sql, [userId, limit ?? 100, offset ?? 0]);
|
|
57
|
+
return result.rows.map((row) => this.rowToRecord(row));
|
|
58
|
+
}
|
|
59
|
+
async update(session) {
|
|
60
|
+
const sql = `
|
|
61
|
+
UPDATE sessions
|
|
62
|
+
SET title = $1, status = $2, metadata = $3, updated_by = $4, updated_at = $5
|
|
63
|
+
WHERE id = $6 AND is_deleted = FALSE
|
|
64
|
+
`;
|
|
65
|
+
await this.pool.query(sql, [
|
|
66
|
+
session.title,
|
|
67
|
+
session.status,
|
|
68
|
+
JSON.stringify(session.metadata),
|
|
69
|
+
session.updatedBy || 'system',
|
|
70
|
+
session.updatedAt,
|
|
71
|
+
session.id,
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Soft delete session
|
|
76
|
+
*/
|
|
77
|
+
async softDelete(id, deletedBy) {
|
|
78
|
+
const sql = `
|
|
79
|
+
UPDATE sessions
|
|
80
|
+
SET is_deleted = TRUE, deleted_by = $1, deleted_at = NOW()
|
|
81
|
+
WHERE id = $2 AND is_deleted = FALSE
|
|
82
|
+
`;
|
|
83
|
+
await this.pool.query(sql, [deletedBy, id]);
|
|
84
|
+
}
|
|
85
|
+
// ─────────────────────────────────────────────
|
|
86
|
+
// Private Helpers
|
|
87
|
+
// ─────────────────────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* 将 PostgreSQL 行数据转换为 SessionRecord
|
|
90
|
+
*/
|
|
91
|
+
rowToRecord(row) {
|
|
92
|
+
return {
|
|
93
|
+
id: row.id,
|
|
94
|
+
userId: row.user_id,
|
|
95
|
+
agentId: row.agent_id,
|
|
96
|
+
title: row.title,
|
|
97
|
+
status: row.status,
|
|
98
|
+
metadata: this.parseJson(row.metadata),
|
|
99
|
+
createdBy: row.created_by,
|
|
100
|
+
createdAt: row.created_at.toISOString(),
|
|
101
|
+
updatedBy: row.updated_by,
|
|
102
|
+
updatedAt: row.updated_at.toISOString(),
|
|
103
|
+
isDeleted: row.is_deleted,
|
|
104
|
+
deletedBy: row.deleted_by,
|
|
105
|
+
deletedAt: row.deleted_at?.toISOString(),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 解析 JSON 字段 — pg 可能已自动解析 JSONB 列,也可能返回字符串
|
|
110
|
+
*/
|
|
111
|
+
parseJson(value) {
|
|
112
|
+
if (typeof value === 'string') {
|
|
113
|
+
return JSON.parse(value);
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
}
|