@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,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgresCheckpointSaver — PostgreSQL 存储(主存储)
|
|
3
|
+
*
|
|
4
|
+
* 使用接口抽象,不引入 pg 依赖。参数占位符用 $1/$2 替代 MySQL 的 ?。
|
|
5
|
+
* 支持审计字段和软删除。
|
|
6
|
+
*
|
|
7
|
+
* @see design.md §1.3 checkpoints 数据模型
|
|
8
|
+
* @see checkpoint-saver.contract.ts ICheckpointSaver 接口定义
|
|
9
|
+
*/
|
|
10
|
+
// ─────────────────────────────────────────────
|
|
11
|
+
// PostgresCheckpointSaver
|
|
12
|
+
// ─────────────────────────────────────────────
|
|
13
|
+
export class PostgresCheckpointSaver {
|
|
14
|
+
pool;
|
|
15
|
+
constructor(pool) {
|
|
16
|
+
this.pool = pool;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 持久化 Checkpoint(INSERT or UPDATE)
|
|
20
|
+
*/
|
|
21
|
+
async save(threadId, data) {
|
|
22
|
+
const sql = `
|
|
23
|
+
INSERT INTO checkpoints (id, thread_id, checkpoint_id, current_node_id, history_path, state, created_by, created_at, updated_by, updated_at)
|
|
24
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
25
|
+
ON CONFLICT (thread_id, checkpoint_id) DO UPDATE SET
|
|
26
|
+
current_node_id = EXCLUDED.current_node_id,
|
|
27
|
+
history_path = EXCLUDED.history_path,
|
|
28
|
+
state = EXCLUDED.state,
|
|
29
|
+
updated_by = EXCLUDED.updated_by,
|
|
30
|
+
updated_at = EXCLUDED.updated_at
|
|
31
|
+
`;
|
|
32
|
+
await this.pool.query(sql, [
|
|
33
|
+
data.checkpointId,
|
|
34
|
+
threadId,
|
|
35
|
+
data.checkpointId,
|
|
36
|
+
data.currentNodeId,
|
|
37
|
+
JSON.stringify(data.historyPath),
|
|
38
|
+
JSON.stringify(data.state),
|
|
39
|
+
data.createdBy || 'system',
|
|
40
|
+
data.createdAt,
|
|
41
|
+
data.updatedBy || 'system',
|
|
42
|
+
data.updatedAt,
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 加载指定 thread 的最新 Checkpoint
|
|
47
|
+
* Excludes soft-deleted records
|
|
48
|
+
*/
|
|
49
|
+
async load(threadId) {
|
|
50
|
+
const sql = `SELECT * FROM checkpoints WHERE thread_id = $1 AND is_deleted = FALSE ORDER BY created_at DESC LIMIT 1`;
|
|
51
|
+
const { rows } = await this.pool.query(sql, [threadId]);
|
|
52
|
+
return rows[0] ? this.toCheckpointData(rows[0]) : null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 按 checkpointId 加载特定 Checkpoint
|
|
56
|
+
* Excludes soft-deleted records
|
|
57
|
+
*/
|
|
58
|
+
async loadByCheckpointId(checkpointId) {
|
|
59
|
+
const sql = `SELECT * FROM checkpoints WHERE checkpoint_id = $1 AND is_deleted = FALSE LIMIT 1`;
|
|
60
|
+
const { rows } = await this.pool.query(sql, [checkpointId]);
|
|
61
|
+
return rows[0] ? this.toCheckpointData(rows[0]) : null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 列出指定 thread 的所有 Checkpoint
|
|
65
|
+
* Excludes soft-deleted records
|
|
66
|
+
*/
|
|
67
|
+
async list(threadId) {
|
|
68
|
+
const sql = `SELECT * FROM checkpoints WHERE thread_id = $1 AND is_deleted = FALSE ORDER BY created_at ASC`;
|
|
69
|
+
const { rows } = await this.pool.query(sql, [threadId]);
|
|
70
|
+
return rows.map(r => this.toCheckpointData(r));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Soft delete checkpoint
|
|
74
|
+
*/
|
|
75
|
+
async softDelete(checkpointId, deletedBy) {
|
|
76
|
+
const sql = `
|
|
77
|
+
UPDATE checkpoints
|
|
78
|
+
SET is_deleted = TRUE, deleted_by = $1, deleted_at = NOW()
|
|
79
|
+
WHERE checkpoint_id = $2 AND is_deleted = FALSE
|
|
80
|
+
`;
|
|
81
|
+
await this.pool.query(sql, [deletedBy, checkpointId]);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Fork:从指定 Checkpoint 创建新的执行线程
|
|
85
|
+
*
|
|
86
|
+
* 用于用户调试场景,从某个 Checkpoint 恢复执行,支持修改参数后重新执行。
|
|
87
|
+
*
|
|
88
|
+
* @param sourceCheckpointId - 源 Checkpoint ID
|
|
89
|
+
* @param newThreadId - 新线程 ID
|
|
90
|
+
* @param overrides - 覆盖的参数(可选)
|
|
91
|
+
* @returns 新的 CheckpointData
|
|
92
|
+
*/
|
|
93
|
+
async fork(sourceCheckpointId, newThreadId, overrides) {
|
|
94
|
+
// 1. 加载源 Checkpoint
|
|
95
|
+
const source = await this.loadByCheckpointId(sourceCheckpointId);
|
|
96
|
+
if (!source) {
|
|
97
|
+
throw new Error(`Source checkpoint not found: ${sourceCheckpointId}`);
|
|
98
|
+
}
|
|
99
|
+
// 2. 创建新的 CheckpointData
|
|
100
|
+
const now = new Date().toISOString();
|
|
101
|
+
const newCheckpointId = crypto.randomUUID();
|
|
102
|
+
const newData = {
|
|
103
|
+
checkpointId: newCheckpointId,
|
|
104
|
+
threadId: newThreadId,
|
|
105
|
+
currentNodeId: source.currentNodeId,
|
|
106
|
+
historyPath: source.historyPath,
|
|
107
|
+
state: overrides ? { ...source.state, ...overrides } : source.state,
|
|
108
|
+
createdBy: 'fork',
|
|
109
|
+
createdAt: now,
|
|
110
|
+
updatedBy: 'fork',
|
|
111
|
+
updatedAt: now,
|
|
112
|
+
isDeleted: false,
|
|
113
|
+
};
|
|
114
|
+
// 3. 保存新的 Checkpoint
|
|
115
|
+
await this.save(newThreadId, newData);
|
|
116
|
+
return newData;
|
|
117
|
+
}
|
|
118
|
+
// ─────────────────────────────────────────────
|
|
119
|
+
// Private Helpers
|
|
120
|
+
// ─────────────────────────────────────────────
|
|
121
|
+
/**
|
|
122
|
+
* 将 PostgreSQL 行数据转换为 CheckpointData
|
|
123
|
+
*/
|
|
124
|
+
toCheckpointData(row) {
|
|
125
|
+
return {
|
|
126
|
+
checkpointId: row.checkpoint_id,
|
|
127
|
+
threadId: row.thread_id,
|
|
128
|
+
currentNodeId: row.current_node_id,
|
|
129
|
+
historyPath: typeof row.history_path === 'string' ? JSON.parse(row.history_path) : row.history_path,
|
|
130
|
+
state: typeof row.state === 'string' ? JSON.parse(row.state) : row.state,
|
|
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
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedisCheckpointSaver — 基于 Redis 的 Checkpoint 持久化实现
|
|
3
|
+
*
|
|
4
|
+
* Key 模式:
|
|
5
|
+
* - 单个 Checkpoint: `thread:{threadId}:cp:{checkpointId}` → JSON 序列化的 CheckpointData
|
|
6
|
+
* - Thread 索引(Sorted Set): `thread:{threadId}:checkpoints` → member=checkpointId, score=createdAt timestamp
|
|
7
|
+
* - 最新指针: `thread:{threadId}:latest` → 存储最新 checkpointId
|
|
8
|
+
* - 反向索引: `cp:{checkpointId}:thread` → 存储 threadId(供 loadByCheckpointId 使用)
|
|
9
|
+
*
|
|
10
|
+
* @see design.md §1.3 checkpoints 数据模型
|
|
11
|
+
*/
|
|
12
|
+
import type { Redis } from 'ioredis';
|
|
13
|
+
import type { ICheckpointSaver } from './contracts/checkpoint-saver.contract.js';
|
|
14
|
+
import type { CheckpointData } from '../graph/contracts/executable-graph.js';
|
|
15
|
+
/**
|
|
16
|
+
* RedisCheckpointSaver 配置选项
|
|
17
|
+
*/
|
|
18
|
+
export interface RedisCheckpointSaverOptions {
|
|
19
|
+
/** Key 前缀(默认无前缀) */
|
|
20
|
+
keyPrefix?: string;
|
|
21
|
+
/** Checkpoint TTL(秒),默认不过期 */
|
|
22
|
+
ttl?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* RedisCheckpointSaver — ICheckpointSaver 的 Redis 实现
|
|
26
|
+
*
|
|
27
|
+
* 通过构造函数注入 ioredis 实例(DI 友好),支持可选的 TTL 和 key 前缀配置。
|
|
28
|
+
*/
|
|
29
|
+
export declare class RedisCheckpointSaver implements ICheckpointSaver {
|
|
30
|
+
private readonly redis;
|
|
31
|
+
private readonly keyPrefix;
|
|
32
|
+
private readonly ttl;
|
|
33
|
+
constructor(redis: Redis, options?: RedisCheckpointSaverOptions);
|
|
34
|
+
private checkpointKey;
|
|
35
|
+
private threadIndexKey;
|
|
36
|
+
private latestKey;
|
|
37
|
+
private reverseIndexKey;
|
|
38
|
+
/**
|
|
39
|
+
* 持久化 Checkpoint
|
|
40
|
+
*
|
|
41
|
+
* 原子操作:SET checkpoint JSON + ZADD 到 thread 索引 + SET latest 指针 + SET 反向索引
|
|
42
|
+
* 使用 Redis pipeline 保证原子性和性能。
|
|
43
|
+
*/
|
|
44
|
+
save(threadId: string, data: CheckpointData): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* 加载指定 thread 的最新 Checkpoint
|
|
47
|
+
*
|
|
48
|
+
* 流程:GET latest 指针 → GET checkpoint JSON → 解析返回
|
|
49
|
+
*/
|
|
50
|
+
load(threadId: string): Promise<CheckpointData | null>;
|
|
51
|
+
/**
|
|
52
|
+
* 按 checkpointId 加载特定 Checkpoint
|
|
53
|
+
*
|
|
54
|
+
* 流程:通过反向索引 `cp:{checkpointId}:thread` 找到 threadId → GET checkpoint JSON
|
|
55
|
+
*/
|
|
56
|
+
loadByCheckpointId(checkpointId: string): Promise<CheckpointData | null>;
|
|
57
|
+
/**
|
|
58
|
+
* 列出指定 thread 的所有 Checkpoint
|
|
59
|
+
*
|
|
60
|
+
* 流程:ZRANGE thread 索引获取所有 checkpointId → 批量 GET → 解析返回
|
|
61
|
+
* 结果按 createdAt 升序排列(Sorted Set score 升序)。
|
|
62
|
+
*/
|
|
63
|
+
list(threadId: string): Promise<CheckpointData[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Fork: 从指定 Checkpoint 创建新的执行线程
|
|
66
|
+
*/
|
|
67
|
+
fork(sourceCheckpointId: string, newThreadId: string, overrides?: Record<string, unknown>): Promise<import('../graph/contracts/executable-graph.js').CheckpointData>;
|
|
68
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedisCheckpointSaver — 基于 Redis 的 Checkpoint 持久化实现
|
|
3
|
+
*
|
|
4
|
+
* Key 模式:
|
|
5
|
+
* - 单个 Checkpoint: `thread:{threadId}:cp:{checkpointId}` → JSON 序列化的 CheckpointData
|
|
6
|
+
* - Thread 索引(Sorted Set): `thread:{threadId}:checkpoints` → member=checkpointId, score=createdAt timestamp
|
|
7
|
+
* - 最新指针: `thread:{threadId}:latest` → 存储最新 checkpointId
|
|
8
|
+
* - 反向索引: `cp:{checkpointId}:thread` → 存储 threadId(供 loadByCheckpointId 使用)
|
|
9
|
+
*
|
|
10
|
+
* @see design.md §1.3 checkpoints 数据模型
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* RedisCheckpointSaver — ICheckpointSaver 的 Redis 实现
|
|
14
|
+
*
|
|
15
|
+
* 通过构造函数注入 ioredis 实例(DI 友好),支持可选的 TTL 和 key 前缀配置。
|
|
16
|
+
*/
|
|
17
|
+
export class RedisCheckpointSaver {
|
|
18
|
+
redis;
|
|
19
|
+
keyPrefix;
|
|
20
|
+
ttl;
|
|
21
|
+
constructor(redis, options) {
|
|
22
|
+
this.redis = redis;
|
|
23
|
+
this.keyPrefix = options?.keyPrefix ?? '';
|
|
24
|
+
this.ttl = options?.ttl;
|
|
25
|
+
}
|
|
26
|
+
// ─────────────────────────────────────────────
|
|
27
|
+
// Key 构建辅助
|
|
28
|
+
// ─────────────────────────────────────────────
|
|
29
|
+
checkpointKey(threadId, checkpointId) {
|
|
30
|
+
return `${this.keyPrefix}thread:${threadId}:cp:${checkpointId}`;
|
|
31
|
+
}
|
|
32
|
+
threadIndexKey(threadId) {
|
|
33
|
+
return `${this.keyPrefix}thread:${threadId}:checkpoints`;
|
|
34
|
+
}
|
|
35
|
+
latestKey(threadId) {
|
|
36
|
+
return `${this.keyPrefix}thread:${threadId}:latest`;
|
|
37
|
+
}
|
|
38
|
+
reverseIndexKey(checkpointId) {
|
|
39
|
+
return `${this.keyPrefix}cp:${checkpointId}:thread`;
|
|
40
|
+
}
|
|
41
|
+
// ─────────────────────────────────────────────
|
|
42
|
+
// ICheckpointSaver 实现
|
|
43
|
+
// ─────────────────────────────────────────────
|
|
44
|
+
/**
|
|
45
|
+
* 持久化 Checkpoint
|
|
46
|
+
*
|
|
47
|
+
* 原子操作:SET checkpoint JSON + ZADD 到 thread 索引 + SET latest 指针 + SET 反向索引
|
|
48
|
+
* 使用 Redis pipeline 保证原子性和性能。
|
|
49
|
+
*/
|
|
50
|
+
async save(threadId, data) {
|
|
51
|
+
const cpKey = this.checkpointKey(threadId, data.checkpointId);
|
|
52
|
+
const idxKey = this.threadIndexKey(threadId);
|
|
53
|
+
const ltKey = this.latestKey(threadId);
|
|
54
|
+
const revKey = this.reverseIndexKey(data.checkpointId);
|
|
55
|
+
const json = JSON.stringify(data);
|
|
56
|
+
const score = new Date(data.createdAt).getTime();
|
|
57
|
+
const pipeline = this.redis.pipeline();
|
|
58
|
+
// 存储 Checkpoint JSON
|
|
59
|
+
if (this.ttl) {
|
|
60
|
+
pipeline.set(cpKey, json, 'EX', this.ttl);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
pipeline.set(cpKey, json);
|
|
64
|
+
}
|
|
65
|
+
// Thread 索引 (Sorted Set): checkpointId 作为 member, createdAt timestamp 作为 score
|
|
66
|
+
pipeline.zadd(idxKey, score, data.checkpointId);
|
|
67
|
+
// 最新指针
|
|
68
|
+
pipeline.set(ltKey, data.checkpointId);
|
|
69
|
+
// 反向索引: checkpointId → threadId
|
|
70
|
+
if (this.ttl) {
|
|
71
|
+
pipeline.set(revKey, threadId, 'EX', this.ttl);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
pipeline.set(revKey, threadId);
|
|
75
|
+
}
|
|
76
|
+
// 对索引和 latest 指针也设置 TTL(如果配置了)
|
|
77
|
+
if (this.ttl) {
|
|
78
|
+
pipeline.expire(idxKey, this.ttl);
|
|
79
|
+
pipeline.expire(ltKey, this.ttl);
|
|
80
|
+
}
|
|
81
|
+
await pipeline.exec();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 加载指定 thread 的最新 Checkpoint
|
|
85
|
+
*
|
|
86
|
+
* 流程:GET latest 指针 → GET checkpoint JSON → 解析返回
|
|
87
|
+
*/
|
|
88
|
+
async load(threadId) {
|
|
89
|
+
const ltKey = this.latestKey(threadId);
|
|
90
|
+
const latestCheckpointId = await this.redis.get(ltKey);
|
|
91
|
+
if (!latestCheckpointId) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const cpKey = this.checkpointKey(threadId, latestCheckpointId);
|
|
95
|
+
const json = await this.redis.get(cpKey);
|
|
96
|
+
if (!json) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return JSON.parse(json);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 按 checkpointId 加载特定 Checkpoint
|
|
103
|
+
*
|
|
104
|
+
* 流程:通过反向索引 `cp:{checkpointId}:thread` 找到 threadId → GET checkpoint JSON
|
|
105
|
+
*/
|
|
106
|
+
async loadByCheckpointId(checkpointId) {
|
|
107
|
+
const revKey = this.reverseIndexKey(checkpointId);
|
|
108
|
+
const threadId = await this.redis.get(revKey);
|
|
109
|
+
if (!threadId) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const cpKey = this.checkpointKey(threadId, checkpointId);
|
|
113
|
+
const json = await this.redis.get(cpKey);
|
|
114
|
+
if (!json) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return JSON.parse(json);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 列出指定 thread 的所有 Checkpoint
|
|
121
|
+
*
|
|
122
|
+
* 流程:ZRANGE thread 索引获取所有 checkpointId → 批量 GET → 解析返回
|
|
123
|
+
* 结果按 createdAt 升序排列(Sorted Set score 升序)。
|
|
124
|
+
*/
|
|
125
|
+
async list(threadId) {
|
|
126
|
+
const idxKey = this.threadIndexKey(threadId);
|
|
127
|
+
// ZRANGE 默认升序(按 score 即 createdAt timestamp)
|
|
128
|
+
const checkpointIds = await this.redis.zrange(idxKey, 0, -1);
|
|
129
|
+
if (checkpointIds.length === 0) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
// 批量获取所有 Checkpoint JSON(使用 pipeline 减少 RTT)
|
|
133
|
+
const pipeline = this.redis.pipeline();
|
|
134
|
+
for (const cpId of checkpointIds) {
|
|
135
|
+
pipeline.get(this.checkpointKey(threadId, cpId));
|
|
136
|
+
}
|
|
137
|
+
const results = await pipeline.exec();
|
|
138
|
+
const checkpoints = [];
|
|
139
|
+
if (results) {
|
|
140
|
+
for (const [err, json] of results) {
|
|
141
|
+
if (!err && json && typeof json === 'string') {
|
|
142
|
+
checkpoints.push(JSON.parse(json));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return checkpoints;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Fork: 从指定 Checkpoint 创建新的执行线程
|
|
150
|
+
*/
|
|
151
|
+
async fork(sourceCheckpointId, newThreadId, overrides) {
|
|
152
|
+
const source = await this.loadByCheckpointId(sourceCheckpointId);
|
|
153
|
+
if (!source) {
|
|
154
|
+
throw new Error(`Source checkpoint not found: ${sourceCheckpointId}`);
|
|
155
|
+
}
|
|
156
|
+
const now = new Date().toISOString();
|
|
157
|
+
const { randomUUID } = await import('node:crypto');
|
|
158
|
+
const newData = {
|
|
159
|
+
checkpointId: randomUUID(),
|
|
160
|
+
threadId: newThreadId,
|
|
161
|
+
currentNodeId: source.currentNodeId,
|
|
162
|
+
historyPath: source.historyPath,
|
|
163
|
+
state: overrides ? { ...source.state, ...overrides } : source.state,
|
|
164
|
+
createdBy: 'fork',
|
|
165
|
+
createdAt: now,
|
|
166
|
+
updatedBy: 'fork',
|
|
167
|
+
updatedAt: now,
|
|
168
|
+
isDeleted: false,
|
|
169
|
+
};
|
|
170
|
+
await this.save(newThreadId, newData);
|
|
171
|
+
return newData;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* restoreCheckpoint — 供 05-Trace/Replay 模块使用的便捷函数
|
|
3
|
+
*
|
|
4
|
+
* 薄封装 ICheckpointSaver.loadByCheckpointId,提供明确的语义入口:
|
|
5
|
+
* 通过 checkpointId 恢复特定 Graph 状态,用于 Replay/Fork 场景。
|
|
6
|
+
*
|
|
7
|
+
* @see design.md §1.3 Checkpoint 数据模型
|
|
8
|
+
* @see requirements.md Requirement 2 AC4(按 checkpointId 加载 Replay/Fork)
|
|
9
|
+
*/
|
|
10
|
+
import type { ICheckpointSaver } from './contracts/checkpoint-saver.contract.js';
|
|
11
|
+
import type { CheckpointData } from '../graph/contracts/executable-graph.js';
|
|
12
|
+
/**
|
|
13
|
+
* 通过 checkpointId 恢复特定的 Graph 状态快照
|
|
14
|
+
*
|
|
15
|
+
* 供 05-Trace/Replay 模块调用,实现 Replay(重放)和 Fork(分叉)功能。
|
|
16
|
+
* 内部委托给注入的 ICheckpointSaver 实例。
|
|
17
|
+
*
|
|
18
|
+
* @param checkpointId - 目标 Checkpoint 唯一 ID
|
|
19
|
+
* @param saver - ICheckpointSaver 实现实例(通常为 CompositeCheckpointSaver)
|
|
20
|
+
* @returns 对应的 CheckpointData,不存在时返回 null
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import { restoreCheckpoint, CompositeCheckpointSaver } from '@eap/runtime';
|
|
25
|
+
*
|
|
26
|
+
* const checkpoint = await restoreCheckpoint('cp-abc-123', compositeCheckpointSaver);
|
|
27
|
+
* if (checkpoint) {
|
|
28
|
+
* // 使用 checkpoint.state 恢复 Graph 执行
|
|
29
|
+
* // 使用 checkpoint.currentNodeId 确定恢复起点
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function restoreCheckpoint(checkpointId: string, saver: ICheckpointSaver): Promise<CheckpointData | null>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* restoreCheckpoint — 供 05-Trace/Replay 模块使用的便捷函数
|
|
3
|
+
*
|
|
4
|
+
* 薄封装 ICheckpointSaver.loadByCheckpointId,提供明确的语义入口:
|
|
5
|
+
* 通过 checkpointId 恢复特定 Graph 状态,用于 Replay/Fork 场景。
|
|
6
|
+
*
|
|
7
|
+
* @see design.md §1.3 Checkpoint 数据模型
|
|
8
|
+
* @see requirements.md Requirement 2 AC4(按 checkpointId 加载 Replay/Fork)
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 通过 checkpointId 恢复特定的 Graph 状态快照
|
|
12
|
+
*
|
|
13
|
+
* 供 05-Trace/Replay 模块调用,实现 Replay(重放)和 Fork(分叉)功能。
|
|
14
|
+
* 内部委托给注入的 ICheckpointSaver 实例。
|
|
15
|
+
*
|
|
16
|
+
* @param checkpointId - 目标 Checkpoint 唯一 ID
|
|
17
|
+
* @param saver - ICheckpointSaver 实现实例(通常为 CompositeCheckpointSaver)
|
|
18
|
+
* @returns 对应的 CheckpointData,不存在时返回 null
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { restoreCheckpoint, CompositeCheckpointSaver } from '@eap/runtime';
|
|
23
|
+
*
|
|
24
|
+
* const checkpoint = await restoreCheckpoint('cp-abc-123', compositeCheckpointSaver);
|
|
25
|
+
* if (checkpoint) {
|
|
26
|
+
* // 使用 checkpoint.state 恢复 Graph 执行
|
|
27
|
+
* // 使用 checkpoint.currentNodeId 确定恢复起点
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export async function restoreCheckpoint(checkpointId, saver) {
|
|
32
|
+
return saver.loadByCheckpointId(checkpointId);
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent 对外主键解析(迁移期双读)
|
|
3
|
+
*
|
|
4
|
+
* 正式字段为 agentCode;历史 agentId 仅作同义兜底。
|
|
5
|
+
*/
|
|
6
|
+
export function resolveAgentCode(input) {
|
|
7
|
+
const code = input.agentCode ?? input.agentId ?? input.fallback;
|
|
8
|
+
if (code == null || code === '') {
|
|
9
|
+
throw new Error('agentCode is required (agentId synonym accepted during migration)');
|
|
10
|
+
}
|
|
11
|
+
return String(code);
|
|
12
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 并发控制模块
|
|
3
|
+
*
|
|
4
|
+
* 提供两种并发限制机制:
|
|
5
|
+
* 1. ConcurrencyLimiter — 基于 amqplib channel.prefetch 的 Agent 级并发控制
|
|
6
|
+
* 通过 RabbitMQ consumer prefetch 限制单 Agent 实例同时处理的任务数
|
|
7
|
+
* 2. Semaphore — 纯内存信号量,用于本地并发控制(不依赖 RabbitMQ)
|
|
8
|
+
*
|
|
9
|
+
* @see Requirement 14 AC2: 系统通过消息消费者 prefetch 参数控制单 Agent 实例的并发上限(默认 prefetch=5)
|
|
10
|
+
*/
|
|
11
|
+
import type { Channel } from 'amqplib';
|
|
12
|
+
/** ConcurrencyLimiter 默认 prefetch 值 */
|
|
13
|
+
export declare const DEFAULT_PREFETCH = 5;
|
|
14
|
+
/**
|
|
15
|
+
* 基于 amqplib consumer prefetch 的 Agent 级并发限制器
|
|
16
|
+
*
|
|
17
|
+
* RabbitMQ 的 channel.prefetch(n) 表示该 Channel 上的消费者最多同时
|
|
18
|
+
* 持有 n 条未确认消息。超出限制的消息留在 Queue 中排队等待。
|
|
19
|
+
*
|
|
20
|
+
* 整体 QPS = Pod 数 × prefetch。
|
|
21
|
+
*/
|
|
22
|
+
export declare class ConcurrencyLimiter {
|
|
23
|
+
private readonly prefetchCount;
|
|
24
|
+
/**
|
|
25
|
+
* @param prefetchCount 单 Agent 实例最大并发任务数,默认 5
|
|
26
|
+
*/
|
|
27
|
+
constructor(prefetchCount?: number);
|
|
28
|
+
/**
|
|
29
|
+
* 配置 amqplib Channel 的 prefetch
|
|
30
|
+
*
|
|
31
|
+
* 调用后该 Channel 上的消费者最多同时处理 prefetchCount 条消息。
|
|
32
|
+
* 超出限制的消息在 RabbitMQ Queue 中排队等待。
|
|
33
|
+
*
|
|
34
|
+
* @param channel amqplib Channel 实例
|
|
35
|
+
*/
|
|
36
|
+
configure(channel: Channel): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* 获取配置的最大并发数
|
|
39
|
+
*/
|
|
40
|
+
getMaxConcurrency(): number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 内存信号量 — 用于本地并发控制
|
|
44
|
+
*
|
|
45
|
+
* 当无需依赖 RabbitMQ 时(如单进程内限流、测试场景),
|
|
46
|
+
* 使用 Semaphore 控制并发执行数。
|
|
47
|
+
*
|
|
48
|
+
* 用法:
|
|
49
|
+
* ```ts
|
|
50
|
+
* const sem = new Semaphore(3);
|
|
51
|
+
* await sem.acquire(); // 等待可用槽位
|
|
52
|
+
* try {
|
|
53
|
+
* // 执行并发受限的操作
|
|
54
|
+
* } finally {
|
|
55
|
+
* sem.release(); // 释放槽位
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare class Semaphore {
|
|
60
|
+
private readonly maxSlots;
|
|
61
|
+
private currentSlots;
|
|
62
|
+
private readonly waitQueue;
|
|
63
|
+
/**
|
|
64
|
+
* @param maxConcurrency 最大并发数
|
|
65
|
+
*/
|
|
66
|
+
constructor(maxConcurrency: number);
|
|
67
|
+
/**
|
|
68
|
+
* 获取一个可用槽位。如果当前无可用槽位,则等待直到有释放。
|
|
69
|
+
*/
|
|
70
|
+
acquire(): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* 释放一个槽位。如果有等待者,唤醒队列中最先等待的。
|
|
73
|
+
*/
|
|
74
|
+
release(): void;
|
|
75
|
+
/**
|
|
76
|
+
* 当前可用槽位数
|
|
77
|
+
*/
|
|
78
|
+
get availableSlots(): number;
|
|
79
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 并发控制模块
|
|
3
|
+
*
|
|
4
|
+
* 提供两种并发限制机制:
|
|
5
|
+
* 1. ConcurrencyLimiter — 基于 amqplib channel.prefetch 的 Agent 级并发控制
|
|
6
|
+
* 通过 RabbitMQ consumer prefetch 限制单 Agent 实例同时处理的任务数
|
|
7
|
+
* 2. Semaphore — 纯内存信号量,用于本地并发控制(不依赖 RabbitMQ)
|
|
8
|
+
*
|
|
9
|
+
* @see Requirement 14 AC2: 系统通过消息消费者 prefetch 参数控制单 Agent 实例的并发上限(默认 prefetch=5)
|
|
10
|
+
*/
|
|
11
|
+
// ─────────────────────────────────────────────
|
|
12
|
+
// ConcurrencyLimiter — RabbitMQ prefetch 并发控制
|
|
13
|
+
// ─────────────────────────────────────────────
|
|
14
|
+
/** ConcurrencyLimiter 默认 prefetch 值 */
|
|
15
|
+
export const DEFAULT_PREFETCH = 5;
|
|
16
|
+
/**
|
|
17
|
+
* 基于 amqplib consumer prefetch 的 Agent 级并发限制器
|
|
18
|
+
*
|
|
19
|
+
* RabbitMQ 的 channel.prefetch(n) 表示该 Channel 上的消费者最多同时
|
|
20
|
+
* 持有 n 条未确认消息。超出限制的消息留在 Queue 中排队等待。
|
|
21
|
+
*
|
|
22
|
+
* 整体 QPS = Pod 数 × prefetch。
|
|
23
|
+
*/
|
|
24
|
+
export class ConcurrencyLimiter {
|
|
25
|
+
prefetchCount;
|
|
26
|
+
/**
|
|
27
|
+
* @param prefetchCount 单 Agent 实例最大并发任务数,默认 5
|
|
28
|
+
*/
|
|
29
|
+
constructor(prefetchCount = DEFAULT_PREFETCH) {
|
|
30
|
+
if (prefetchCount < 1) {
|
|
31
|
+
throw new Error(`prefetchCount must be >= 1, got ${prefetchCount}`);
|
|
32
|
+
}
|
|
33
|
+
this.prefetchCount = prefetchCount;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 配置 amqplib Channel 的 prefetch
|
|
37
|
+
*
|
|
38
|
+
* 调用后该 Channel 上的消费者最多同时处理 prefetchCount 条消息。
|
|
39
|
+
* 超出限制的消息在 RabbitMQ Queue 中排队等待。
|
|
40
|
+
*
|
|
41
|
+
* @param channel amqplib Channel 实例
|
|
42
|
+
*/
|
|
43
|
+
async configure(channel) {
|
|
44
|
+
await channel.prefetch(this.prefetchCount);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 获取配置的最大并发数
|
|
48
|
+
*/
|
|
49
|
+
getMaxConcurrency() {
|
|
50
|
+
return this.prefetchCount;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// ─────────────────────────────────────────────
|
|
54
|
+
// Semaphore — 内存级信号量
|
|
55
|
+
// ─────────────────────────────────────────────
|
|
56
|
+
/**
|
|
57
|
+
* 内存信号量 — 用于本地并发控制
|
|
58
|
+
*
|
|
59
|
+
* 当无需依赖 RabbitMQ 时(如单进程内限流、测试场景),
|
|
60
|
+
* 使用 Semaphore 控制并发执行数。
|
|
61
|
+
*
|
|
62
|
+
* 用法:
|
|
63
|
+
* ```ts
|
|
64
|
+
* const sem = new Semaphore(3);
|
|
65
|
+
* await sem.acquire(); // 等待可用槽位
|
|
66
|
+
* try {
|
|
67
|
+
* // 执行并发受限的操作
|
|
68
|
+
* } finally {
|
|
69
|
+
* sem.release(); // 释放槽位
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export class Semaphore {
|
|
74
|
+
maxSlots;
|
|
75
|
+
currentSlots;
|
|
76
|
+
waitQueue = [];
|
|
77
|
+
/**
|
|
78
|
+
* @param maxConcurrency 最大并发数
|
|
79
|
+
*/
|
|
80
|
+
constructor(maxConcurrency) {
|
|
81
|
+
if (maxConcurrency < 1) {
|
|
82
|
+
throw new Error(`maxConcurrency must be >= 1, got ${maxConcurrency}`);
|
|
83
|
+
}
|
|
84
|
+
this.maxSlots = maxConcurrency;
|
|
85
|
+
this.currentSlots = maxConcurrency;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 获取一个可用槽位。如果当前无可用槽位,则等待直到有释放。
|
|
89
|
+
*/
|
|
90
|
+
acquire() {
|
|
91
|
+
if (this.currentSlots > 0) {
|
|
92
|
+
this.currentSlots--;
|
|
93
|
+
return Promise.resolve();
|
|
94
|
+
}
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
this.waitQueue.push(resolve);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 释放一个槽位。如果有等待者,唤醒队列中最先等待的。
|
|
101
|
+
*/
|
|
102
|
+
release() {
|
|
103
|
+
if (this.waitQueue.length > 0) {
|
|
104
|
+
const next = this.waitQueue.shift();
|
|
105
|
+
// 使用 microtask 调度,确保调用者的 finally 块先完成
|
|
106
|
+
Promise.resolve().then(next);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
this.currentSlots++;
|
|
110
|
+
if (this.currentSlots > this.maxSlots) {
|
|
111
|
+
this.currentSlots = this.maxSlots;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 当前可用槽位数
|
|
117
|
+
*/
|
|
118
|
+
get availableSlots() {
|
|
119
|
+
return this.currentSlots;
|
|
120
|
+
}
|
|
121
|
+
}
|