@gt-fe/eap-runtime 0.0.20 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +4 -3
  2. package/dist/api/handlers/invoke.handler.d.ts +7 -0
  3. package/dist/api/handlers/invoke.handler.js +52 -10
  4. package/dist/api/handlers/stream.handler.d.ts +6 -0
  5. package/dist/api/handlers/stream.handler.js +60 -39
  6. package/dist/api/routes/agent.routes.d.ts +8 -1
  7. package/dist/checkpoint/file-checkpoint-saver.d.ts +4 -0
  8. package/dist/checkpoint/file-checkpoint-saver.js +51 -22
  9. package/dist/checkpoint/index.d.ts +1 -1
  10. package/dist/checkpoint/index.js +1 -1
  11. package/dist/common/types.d.ts +2 -2
  12. package/dist/common/types.js +1 -1
  13. package/dist/execution/index.d.ts +2 -0
  14. package/dist/execution/index.js +2 -0
  15. package/dist/execution/interceptors.d.ts +40 -0
  16. package/dist/execution/interceptors.js +23 -0
  17. package/dist/execution/ports.d.ts +18 -0
  18. package/dist/execution/ports.js +13 -0
  19. package/dist/governance/audit-reporter.d.ts +14 -0
  20. package/dist/governance/audit-reporter.js +18 -0
  21. package/dist/governance/index.d.ts +2 -0
  22. package/dist/governance/index.js +1 -0
  23. package/dist/governance/permission-guard.d.ts +6 -2
  24. package/dist/governance/permission-guard.js +38 -5
  25. package/dist/graph/adapters/graph-nesting.d.ts +12 -0
  26. package/dist/graph/adapters/graph-nesting.js +29 -0
  27. package/dist/graph/adapters/index.d.ts +2 -0
  28. package/dist/graph/adapters/index.js +2 -0
  29. package/dist/graph/adapters/langgraph-checkpointer.d.ts +11 -0
  30. package/dist/graph/adapters/langgraph-checkpointer.js +48 -9
  31. package/dist/graph/adapters/langgraph.adapter.d.ts +33 -11
  32. package/dist/graph/adapters/langgraph.adapter.js +200 -98
  33. package/dist/graph/adapters/skill-isolate.d.ts +13 -0
  34. package/dist/graph/adapters/skill-isolate.js +39 -0
  35. package/dist/graph/adapters/subgraph-compiler.d.ts +7 -2
  36. package/dist/graph/adapters/subgraph-compiler.js +63 -30
  37. package/dist/graph/contracts/executable-graph.d.ts +33 -5
  38. package/dist/graph/hitl/hitl-handler.d.ts +13 -4
  39. package/dist/graph/hitl/hitl-handler.js +80 -7
  40. package/dist/graph/nodes/index.d.ts +1 -0
  41. package/dist/graph/nodes/index.js +1 -0
  42. package/dist/graph/nodes/llm-node.d.ts +3 -1
  43. package/dist/graph/nodes/llm-node.js +26 -2
  44. package/dist/graph/nodes/rag/dedup.d.ts +20 -0
  45. package/dist/graph/nodes/rag/dedup.js +77 -0
  46. package/dist/graph/nodes/rag/denoise.d.ts +58 -0
  47. package/dist/graph/nodes/rag/denoise.js +220 -0
  48. package/dist/graph/nodes/rag/fusion/fuse.d.ts +11 -0
  49. package/dist/graph/nodes/rag/fusion/fuse.js +25 -0
  50. package/dist/graph/nodes/rag/fusion/normalize.d.ts +9 -0
  51. package/dist/graph/nodes/rag/fusion/normalize.js +135 -0
  52. package/dist/graph/nodes/rag/index.d.ts +18 -0
  53. package/dist/graph/nodes/rag/index.js +17 -0
  54. package/dist/graph/nodes/rag/prompts.d.ts +12 -0
  55. package/dist/graph/nodes/rag/prompts.js +40 -0
  56. package/dist/graph/nodes/rag/rag-node.d.ts +21 -0
  57. package/dist/graph/nodes/rag/rag-node.js +135 -0
  58. package/dist/graph/nodes/rag/rag-search-tool.d.ts +48 -0
  59. package/dist/graph/nodes/rag/rag-search-tool.js +230 -0
  60. package/dist/graph/nodes/rag/react-loop.d.ts +57 -0
  61. package/dist/graph/nodes/rag/react-loop.js +467 -0
  62. package/dist/graph/nodes/rag/retrievers/http-embedder.d.ts +29 -0
  63. package/dist/graph/nodes/rag/retrievers/http-embedder.js +66 -0
  64. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.d.ts +17 -0
  65. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.js +18 -0
  66. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.d.ts +46 -0
  67. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.js +85 -0
  68. package/dist/graph/nodes/rag/tools.d.ts +90 -0
  69. package/dist/graph/nodes/rag/tools.js +260 -0
  70. package/dist/graph/nodes/rag/types.d.ts +226 -0
  71. package/dist/graph/nodes/rag/types.js +9 -0
  72. package/dist/graph/nodes/tool-node.d.ts +19 -1
  73. package/dist/graph/nodes/tool-node.js +158 -33
  74. package/dist/index.d.ts +2 -0
  75. package/dist/index.js +1 -0
  76. package/dist/memory/semantic-retrieval.service.d.ts +1 -1
  77. package/dist/memory/semantic-retrieval.service.js +13 -23
  78. package/dist/providers/index.d.ts +2 -2
  79. package/dist/providers/index.js +2 -2
  80. package/dist/providers/local-llm-provider.d.ts +6 -0
  81. package/dist/providers/local-llm-provider.js +29 -5
  82. package/dist/providers/routing-llm-provider.d.ts +7 -2
  83. package/dist/providers/routing-llm-provider.js +71 -16
  84. package/dist/registry/deployment-snapshot.js +18 -4
  85. package/dist/registry/graph-loader.d.ts +2 -0
  86. package/dist/registry/graph-loader.js +39 -2
  87. package/dist/registry/preload-flags.d.ts +1 -1
  88. package/dist/registry/preload-flags.js +1 -1
  89. package/dist/runtime/agent-runtime.d.ts +5 -0
  90. package/dist/runtime/agent-runtime.js +107 -70
  91. package/dist/runtime/dev-rag-deps.d.ts +21 -0
  92. package/dist/runtime/dev-rag-deps.js +186 -0
  93. package/dist/runtime/dev-runtime.d.ts +18 -2
  94. package/dist/runtime/dev-runtime.js +68 -8
  95. package/dist/runtime/index.d.ts +1 -1
  96. package/dist/runtime/types.d.ts +10 -9
  97. package/dist/session/apply-session-compression.d.ts +85 -0
  98. package/dist/session/apply-session-compression.js +281 -0
  99. package/dist/session/apply-session-memory.d.ts +21 -0
  100. package/dist/session/apply-session-memory.js +48 -0
  101. package/dist/session/index.d.ts +2 -0
  102. package/dist/session/index.js +2 -0
  103. package/dist/session/persist-turn-messages.d.ts +57 -1
  104. package/dist/session/persist-turn-messages.js +160 -5
  105. package/dist/sse/events/index.d.ts +13 -4
  106. package/dist/sse/events/index.js +9 -1
  107. package/dist/sse/events/rag-events.d.ts +63 -0
  108. package/dist/sse/events/rag-events.js +9 -0
  109. package/dist/sse/events/session-events.d.ts +38 -0
  110. package/dist/sse/events/session-events.js +4 -0
  111. package/dist/token/index.d.ts +2 -1
  112. package/dist/token/index.js +2 -1
  113. package/dist/token/record-task-token-usage.d.ts +25 -0
  114. package/dist/token/record-task-token-usage.js +44 -0
  115. package/dist/token/token-detail-mode.d.ts +4 -4
  116. package/dist/token/token-detail-mode.js +4 -4
  117. package/dist/token/token-detail.d.ts +8 -0
  118. package/dist/token/token-detail.js +22 -0
  119. package/dist/token/token-stats.service.d.ts +2 -2
  120. package/dist/token/token-stats.service.js +1 -1
  121. package/dist/tool/create-tool-executor.d.ts +6 -0
  122. package/dist/tool/create-tool-executor.js +4 -3
  123. package/dist/tool/harness-primitives.d.ts +9 -1
  124. package/dist/tool/harness-primitives.js +43 -12
  125. package/dist/tool/index.d.ts +2 -1
  126. package/dist/tool/index.js +2 -1
  127. package/dist/tool/tool-confirmation.js +8 -5
  128. package/dist/tool/tool-executor.interface.d.ts +27 -0
  129. package/dist/tool/tool-orchestrator.d.ts +3 -1
  130. package/dist/tool/tool-orchestrator.js +15 -6
  131. package/dist/tool/tool-retry.d.ts +15 -0
  132. package/dist/tool/tool-retry.js +69 -0
  133. package/package.json +2 -2
@@ -32,6 +32,7 @@ export async function persistAssistantTurnMessage(messageService, params) {
32
32
  reasoning: params.reasoningContent
33
33
  ? { reasoningContent: params.reasoningContent }
34
34
  : null,
35
+ durationMs: params.durationMs ?? null,
35
36
  });
36
37
  }
37
38
  /**
@@ -92,6 +93,114 @@ export function extractStreamTextDelta(eventType, payload) {
92
93
  const text = p.chunk ?? p.delta ?? p.content;
93
94
  return typeof text === 'string' ? text : '';
94
95
  }
96
+ export function createStreamAssistantBuckets() {
97
+ return { order: [], text: new Map() };
98
+ }
99
+ function streamNodeId(payload) {
100
+ if (!payload || typeof payload !== 'object')
101
+ return '_';
102
+ const p = payload;
103
+ const n = p.node ?? p.nodeId;
104
+ return typeof n === 'string' && n.trim() ? n : '_';
105
+ }
106
+ export function applyStreamTextDelta(buckets, eventType, payload) {
107
+ const delta = extractStreamTextDelta(eventType, payload);
108
+ if (!delta)
109
+ return;
110
+ const node = streamNodeId(payload);
111
+ if (eventType === 'response.final' && delta.trim()) {
112
+ if (!buckets.text.has(node))
113
+ buckets.order.push(node);
114
+ buckets.text.set(node, delta);
115
+ return;
116
+ }
117
+ if (!buckets.text.has(node))
118
+ buckets.order.push(node);
119
+ buckets.text.set(node, (buckets.text.get(node) ?? '') + delta);
120
+ }
121
+ export function streamAssistantTexts(buckets) {
122
+ return buckets.order
123
+ .map((nodeId) => ({ nodeId, text: buckets.text.get(nodeId) ?? '' }))
124
+ .filter((row) => row.text.length > 0);
125
+ }
126
+ /** 最后一个有文本的节点(有汇合节点时通常是它) */
127
+ export function lastStreamAssistantText(buckets) {
128
+ const rows = streamAssistantTexts(buckets);
129
+ return rows.at(-1)?.text ?? '';
130
+ }
131
+ /** 用户可见兜底输出:各节点文本都保留,避免无汇合时只剩后出现的一路 */
132
+ export function visibleStreamAssistantText(buckets) {
133
+ const rows = streamAssistantTexts(buckets);
134
+ if (rows.length <= 1)
135
+ return rows[0]?.text ?? '';
136
+ return rows.map((row) => row.text).join('\n\n');
137
+ }
138
+ /** `finishPoints` 里的实际节点 id;`__end__` / `__start__` 不算。空则 Session 不按结束节点筛选。 */
139
+ export function namedFinishPoints(finishPoints) {
140
+ return (finishPoints ?? []).filter((id) => id.trim() && id !== '__end__' && id !== '__start__');
141
+ }
142
+ /**
143
+ * Session 里的助手正文。
144
+ * - `finishPoints` 只含一个实际节点 id(DAG `merge`):只留该节点
145
+ * - 没有实际节点 id,或含多个:全部保留,由调用方拼成一条消息(规划执行的计划+结果)
146
+ * 不把 planner 写进 finishPoints:那会变成 LangGraph 终止点,计划跑完图就结束。
147
+ */
148
+ export function sessionVisibleAssistantTexts(texts, finishPoints) {
149
+ const nonempty = texts.filter((row) => row.text.trim().length > 0);
150
+ const named = namedFinishPoints(finishPoints);
151
+ if (named.length !== 1)
152
+ return nonempty;
153
+ const hit = nonempty.filter((row) => row.nodeId === named[0]);
154
+ if (hit.length > 0)
155
+ return hit;
156
+ const last = nonempty.at(-1);
157
+ return last ? [last] : [];
158
+ }
159
+ /** 可写入 Session 的纯文本助手;带 tool_calls 的行不算,避免把调用行拼进正文。 */
160
+ function isTextAssistant(message) {
161
+ if (message.role !== 'assistant')
162
+ return false;
163
+ if (message.toolCalls && message.toolCalls.length > 0)
164
+ return false;
165
+ return Boolean(message.content?.trim());
166
+ }
167
+ /** Session 只收成对的 tool 结果;code 节点没有 toolCallId,留在 Checkpoint。 */
168
+ function isSessionToolMessage(message) {
169
+ if (message.role !== 'tool')
170
+ return false;
171
+ return Boolean(message.toolCallId?.trim());
172
+ }
173
+ function sessionGraphMessages(messages) {
174
+ return messages.filter((message) => message.role !== 'tool' || isSessionToolMessage(message));
175
+ }
176
+ /**
177
+ * invoke 路径图增量。tool_calls / tool 行原样保留,纯文本助手收成一条放在末尾(与 stream 落库顺序一致)。
178
+ * GraphMessage 无 nodeId:`finishPoints` 只含一个实际节点 id 时取最后一条纯文本,不是按 id 筛选(官方 DAG 的 `merge` 在末尾)。
179
+ * 没有实际节点 id 或仅为 `__end__` 时把多段正文用空行拼进这一条。
180
+ * 无 toolCallId 的 tool 行不进 Session(code 节点结果)。
181
+ */
182
+ export function sessionVisibleGraphMessages(messages, finishPoints) {
183
+ const eligible = sessionGraphMessages(messages);
184
+ const texts = eligible.filter(isTextAssistant);
185
+ if (texts.length === 0)
186
+ return eligible;
187
+ const named = namedFinishPoints(finishPoints);
188
+ const visible = named.length === 1 ? [texts.at(-1)] : texts;
189
+ const keep = visible.at(-1);
190
+ const joined = visible.map((message) => message.content).join('\n\n');
191
+ const out = [];
192
+ for (const message of eligible) {
193
+ if (isTextAssistant(message))
194
+ continue;
195
+ out.push(message);
196
+ }
197
+ out.push({
198
+ ...keep,
199
+ content: joined,
200
+ ...(keep.reasoningContent ? { reasoningContent: keep.reasoningContent } : {}),
201
+ });
202
+ return out;
203
+ }
95
204
  /**
96
205
  * 从流式事件抽取 assistant reasoning。
97
206
  * 子图在 skill.completed 上回传(父图不再为 subgraph 节点发空 response.chunk)。
@@ -108,6 +217,33 @@ export function extractStreamReasoning(eventType, payload) {
108
217
  const value = payload.reasoningContent;
109
218
  return typeof value === 'string' && value ? value : undefined;
110
219
  }
220
+ /** 落盘 / 回填历史只收终态:result,或不会再试的 error。willRetry 的 error 只给直播 UI。 */
221
+ export function isTerminalToolStreamEvent(eventType, payload) {
222
+ if (eventType === 'tool.result')
223
+ return true;
224
+ if (eventType !== 'tool.error')
225
+ return false;
226
+ if (!payload || typeof payload !== 'object')
227
+ return true;
228
+ return payload.willRetry !== true;
229
+ }
230
+ export function appendTerminalToolResult(eventType, payload, bucket) {
231
+ if (!isTerminalToolStreamEvent(eventType, payload))
232
+ return;
233
+ if (eventType !== 'tool.result' && eventType !== 'tool.error')
234
+ return;
235
+ if (!payload || typeof payload !== 'object')
236
+ return;
237
+ const p = payload;
238
+ if (!p.toolCallId || !p.toolName)
239
+ return;
240
+ bucket.push({
241
+ toolCallId: p.toolCallId,
242
+ toolName: p.toolName,
243
+ content: p.content ?? p.error,
244
+ success: eventType === 'tool.result' && p.success !== false,
245
+ });
246
+ }
111
247
  /** 历史 MessageRecord → GraphMessage(保留 tool 元数据与 reasoning) */
112
248
  export function messageRecordsToGraphMessages(records) {
113
249
  return records.map((m) => {
@@ -220,8 +356,10 @@ export function extractHitlThreadId(output) {
220
356
  return undefined;
221
357
  }
222
358
  /**
223
- * Checkpoint thread_id 契约:HITL/多轮优先且应唯一等于 sessionId;
359
+ * Checkpoint 父图 thread_id:HITL/多轮优先且应唯一等于 sessionId;
224
360
  * 无 session 时退到 `task-${taskId}`。勿用 correlationId 作为 Checkpoint key。
361
+ * 子图槽位再经 `checkpointStorageThreadId`(`thread_id#父节点id`),
362
+ * 与 LangGraph put / 子图内 HITL interrupt 同一把钥匙。
225
363
  *
226
364
  * 注意:session 级 thread + 全量 Session 历史同时注入会导致 messages 双重 append。
227
365
  * 组装本轮 Graph 输入请用 {@link buildTurnGraphMessages}(有 Checkpoint 历史则只追加 user)。
@@ -243,7 +381,11 @@ export function buildTurnTranscriptFromStreamEvents(params) {
243
381
  const out = [];
244
382
  const pending = params.pendingToolCalls ?? [];
245
383
  const results = params.toolResults ?? [];
246
- const text = params.assistantText ?? '';
384
+ const visible = sessionVisibleAssistantTexts((params.assistantTexts ?? []).map((row) => ({ nodeId: row.nodeId, text: row.text })), params.finishPoints);
385
+ const texts = visible.map((row) => row.text).filter((text) => text.trim().length > 0);
386
+ if (texts.length === 0 && (params.assistantText ?? '').trim()) {
387
+ texts.push(params.assistantText ?? '');
388
+ }
247
389
  if (pending.length > 0) {
248
390
  out.push({
249
391
  role: 'assistant',
@@ -267,10 +409,19 @@ export function buildTurnTranscriptFromStreamEvents(params) {
267
409
  success: tr.success,
268
410
  });
269
411
  }
270
- if (text.trim() || (params.persistEmptyAssistant && out.length === 0)) {
412
+ if (texts.length > 0) {
413
+ out.push({
414
+ role: 'assistant',
415
+ content: texts.join('\n\n'),
416
+ ...(params.assistantReasoningContent
417
+ ? { reasoningContent: params.assistantReasoningContent }
418
+ : {}),
419
+ });
420
+ }
421
+ else if (params.persistEmptyAssistant && out.length === 0) {
271
422
  out.push({
272
423
  role: 'assistant',
273
- content: text,
424
+ content: '',
274
425
  ...(params.assistantReasoningContent
275
426
  ? { reasoningContent: params.assistantReasoningContent }
276
427
  : {}),
@@ -299,14 +450,17 @@ export async function persistTurnTranscript(messageService, params) {
299
450
  toolCalls: m.toolCalls,
300
451
  tokenCount: isToolCallOnly ? 0 : params.assistantTokenCount,
301
452
  reasoningContent: m.reasoningContent,
453
+ durationMs: isToolCallOnly ? null : params.assistantDurationMs,
302
454
  });
303
455
  }
304
456
  else if (m.role === 'tool') {
457
+ if (!m.toolCallId?.trim())
458
+ continue;
305
459
  await persistToolTurnMessage(messageService, {
306
460
  sessionId: params.sessionId,
307
461
  taskId: params.taskId,
308
462
  correlationId: params.correlationId,
309
- toolCallId: m.toolCallId ?? 'unknown',
463
+ toolCallId: m.toolCallId,
310
464
  toolName: m.toolName ?? 'unknown',
311
465
  content: m.content,
312
466
  success: m.success !== false,
@@ -320,6 +474,7 @@ export async function persistTurnTranscript(messageService, params) {
320
474
  correlationId: params.correlationId,
321
475
  content: params.fallbackAssistantContent,
322
476
  tokenCount: params.assistantTokenCount,
477
+ durationMs: params.assistantDurationMs,
323
478
  });
324
479
  }
325
480
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SSE 事件 Payload 类型 — 统一导出
3
3
  *
4
- * 定义 SSE 事件的 Payload 接口和 SSEEventType 联合类型。
4
+ * 定义 SSE 事件的 Payload 接口和 SSEEventType 联合类型(含 RAG 节点旁路观测事件)。
5
5
  *
6
6
  * 命名规范:
7
7
  * - 格式:{domain}.{action}
@@ -28,6 +28,7 @@ export * from './plan-events.js';
28
28
  export * from './response-events.js';
29
29
  export * from './stats-events.js';
30
30
  export * from './connection-events.js';
31
+ export * from './rag-events.js';
31
32
  /**
32
33
  * 事件层级
33
34
  */
@@ -39,12 +40,12 @@ export declare const CORE_SSE_EVENTS: readonly ["session.created", "task.started
39
40
  /**
40
41
  * 可选事件类型(Agent 按需支持)
41
42
  */
42
- export declare const OPTIONAL_SSE_EVENTS: readonly ["skill.matched", "skill.unmatched", "skill.started", "skill.completed", "skill.failed", "skill.loaded", "skill.resource_read", "reasoning.step", "tool.progress", "file.created", "file.updated", "code.executed", "collaboration.handed_off", "collaboration.subtask.started", "collaboration.subtask.completed", "plan.created", "plan.step.started", "plan.step.completed", "response.max_iterations_reached", "stats.node_token_usage"];
43
+ export declare const OPTIONAL_SSE_EVENTS: readonly ["skill.matched", "skill.unmatched", "skill.started", "skill.completed", "skill.failed", "skill.loaded", "skill.resource_read", "reasoning.step", "tool.progress", "file.created", "file.updated", "code.executed", "collaboration.handed_off", "collaboration.subtask.started", "collaboration.subtask.completed", "plan.created", "plan.step.started", "plan.step.completed", "response.max_iterations_reached", "stats.node_token_usage", "session.compression.started", "session.compression.completed", "rag.round", "rag.search", "rag.denoise", "rag.rewrite", "rag.finish"];
43
44
  /**
44
45
  * SSEEventType — SSE 事件类型字符串联合类型
45
46
  */
46
- export type SSEEventType = 'session.created' | 'task.started' | 'task.completed' | 'task.error' | 'reasoning.started' | 'response.chunk' | 'tool.called' | 'tool.result' | 'tool.error' | 'hitl.waited' | 'hitl.resumed' | 'response.final' | 'stats.token_usage' | 'connection.heartbeat' | 'skill.matched' | 'skill.unmatched' | 'skill.started' | 'skill.completed' | 'skill.failed' | 'skill.loaded' | 'skill.resource_read' | 'reasoning.step' | 'tool.progress' | 'file.created' | 'file.updated' | 'code.executed' | 'collaboration.handed_off' | 'collaboration.subtask.started' | 'collaboration.subtask.completed' | 'plan.created' | 'plan.step.started' | 'plan.step.completed' | 'response.max_iterations_reached' | 'stats.node_token_usage';
47
- import type { SessionCreatedPayload, TaskStartedPayload, TaskCompletedPayload, TaskErrorPayload } from './session-events.js';
47
+ export type SSEEventType = 'session.created' | 'task.started' | 'task.completed' | 'task.error' | 'reasoning.started' | 'response.chunk' | 'tool.called' | 'tool.result' | 'tool.error' | 'hitl.waited' | 'hitl.resumed' | 'response.final' | 'stats.token_usage' | 'connection.heartbeat' | 'skill.matched' | 'skill.unmatched' | 'skill.started' | 'skill.completed' | 'skill.failed' | 'skill.loaded' | 'skill.resource_read' | 'reasoning.step' | 'tool.progress' | 'file.created' | 'file.updated' | 'code.executed' | 'collaboration.handed_off' | 'collaboration.subtask.started' | 'collaboration.subtask.completed' | 'plan.created' | 'plan.step.started' | 'plan.step.completed' | 'response.max_iterations_reached' | 'stats.node_token_usage' | 'session.compression.started' | 'session.compression.completed' | 'rag.round' | 'rag.search' | 'rag.denoise' | 'rag.rewrite' | 'rag.finish';
48
+ import type { SessionCreatedPayload, TaskStartedPayload, TaskCompletedPayload, TaskErrorPayload, SessionCompressionStartedPayload, SessionCompressionCompletedPayload } from './session-events.js';
48
49
  import type { SkillMatchedPayload, SkillUnmatchedPayload, SkillStartedPayload, SkillCompletedPayload, SkillFailedPayload, SkillLoadedPayload, SkillResourceReadPayload } from './skill-events.js';
49
50
  import type { ReasoningStartedPayload, ReasoningStepPayload, ResponseChunkPayload } from './reasoning-events.js';
50
51
  import type { ToolCalledPayload, ToolResultPayload, ToolProgressPayload, ToolErrorPayload } from './tool-events.js';
@@ -56,6 +57,7 @@ import type { PlanCreatedPayload, PlanStepStartedPayload, PlanStepCompletedPaylo
56
57
  import type { ResponseFinalPayload, ResponseMaxIterationsReachedPayload } from './response-events.js';
57
58
  import type { StatsTokenUsagePayload, StatsNodeTokenUsagePayload } from './stats-events.js';
58
59
  import type { ConnectionHeartbeatPayload } from './connection-events.js';
60
+ import type { RagRoundPayload, RagSearchPayload, RagDenoisePayload, RagRewritePayload, RagFinishPayload } from './rag-events.js';
59
61
  /**
60
62
  * SSEEventPayloadMap — 事件类型名称到 Payload 类型的完整映射
61
63
  */
@@ -94,6 +96,13 @@ export interface SSEEventPayloadMap {
94
96
  'plan.step.completed': PlanStepCompletedPayload;
95
97
  'response.max_iterations_reached': ResponseMaxIterationsReachedPayload;
96
98
  'stats.node_token_usage': StatsNodeTokenUsagePayload;
99
+ 'session.compression.started': SessionCompressionStartedPayload;
100
+ 'session.compression.completed': SessionCompressionCompletedPayload;
101
+ 'rag.round': RagRoundPayload;
102
+ 'rag.search': RagSearchPayload;
103
+ 'rag.denoise': RagDenoisePayload;
104
+ 'rag.rewrite': RagRewritePayload;
105
+ 'rag.finish': RagFinishPayload;
97
106
  }
98
107
  /**
99
108
  * 判断事件是否为核心事件
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SSE 事件 Payload 类型 — 统一导出
3
3
  *
4
- * 定义 SSE 事件的 Payload 接口和 SSEEventType 联合类型。
4
+ * 定义 SSE 事件的 Payload 接口和 SSEEventType 联合类型(含 RAG 节点旁路观测事件)。
5
5
  *
6
6
  * 命名规范:
7
7
  * - 格式:{domain}.{action}
@@ -31,6 +31,7 @@ export * from './plan-events.js';
31
31
  export * from './response-events.js';
32
32
  export * from './stats-events.js';
33
33
  export * from './connection-events.js';
34
+ export * from './rag-events.js';
34
35
  /**
35
36
  * 核心事件类型(所有 Agent 必须支持)
36
37
  */
@@ -74,6 +75,13 @@ export const OPTIONAL_SSE_EVENTS = [
74
75
  'plan.step.completed',
75
76
  'response.max_iterations_reached',
76
77
  'stats.node_token_usage',
78
+ 'session.compression.started',
79
+ 'session.compression.completed',
80
+ 'rag.round',
81
+ 'rag.search',
82
+ 'rag.denoise',
83
+ 'rag.rewrite',
84
+ 'rag.finish',
77
85
  ];
78
86
  // ─────────────────────────────────────────────
79
87
  // 工具函数
@@ -0,0 +1,63 @@
1
+ /**
2
+ * RAG 节点 SSE 事件 Payload — rag.* 五事件(可选事件层)
3
+ *
4
+ * rag 循环发生在节点黑盒内部(不经 ToolNode、不成图超步),这些事件把
5
+ * 内部过程经 streamSink 旁路推给外部观测,弥补"循环内调用不可见"的缺口。
6
+ *
7
+ * @see mt-docs/1-requirements/specs/agentic-rag/design-v2.md §2.2(事件输出)
8
+ */
9
+ export interface RagRoundPayload {
10
+ nodeId: string;
11
+ /** 决策轮次(从 1 起) */
12
+ round: number;
13
+ /** 本轮 LLM 点名的工具(name + arguments) */
14
+ toolCalls: Array<{
15
+ name: string;
16
+ arguments: Record<string, unknown>;
17
+ }>;
18
+ }
19
+ export interface RagSearchPayload {
20
+ nodeId: string;
21
+ /** local = 内置 local_search 直连;keyword = 内置 keyword_search 直连(FTS5);kb = 注册工具经 ToolOrchestrator */
22
+ kind: 'local' | 'keyword' | 'kb';
23
+ query?: string;
24
+ toolName?: string;
25
+ topK?: number;
26
+ /** 命中条数(归一化后) */
27
+ hits: number;
28
+ success: boolean;
29
+ error?: string;
30
+ durationMs: number;
31
+ /**
32
+ * 实际生效的本地检索范围摘要(仅 local/keyword:与传给 IRetriever 的 scope 同源),
33
+ * 形如 `dirs=2[共享资料,月报] files=3`(目录取末段名);'closed'=会话勾选显式关闭本地源;
34
+ * 缺省 = 未圈定(本地不限)或 kb 路径(ragScope 只约束本地检索源,kb 侧看 toolName)。
35
+ * 会话级勾选数据源 id(ragSources)在节点入口已折算进 scope/工具表,不单独透出。
36
+ */
37
+ scope?: string;
38
+ }
39
+ export interface RagDenoisePayload {
40
+ nodeId: string;
41
+ /** 判定候选数(近重去重后) */
42
+ candidates: number;
43
+ /** 存活条数 */
44
+ kept: number;
45
+ /** 被滤文档与原因 */
46
+ filtered: Array<{
47
+ rawId: string;
48
+ reason: string;
49
+ }>;
50
+ }
51
+ export interface RagRewritePayload {
52
+ nodeId: string;
53
+ from: string;
54
+ to: string;
55
+ }
56
+ export interface RagFinishPayload {
57
+ nodeId: string;
58
+ rounds: number;
59
+ /** 空手标识(全失败/全被滤,含重查后仍空) */
60
+ empty: boolean;
61
+ /** 最终上下文条数 */
62
+ docs: number;
63
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * RAG 节点 SSE 事件 Payload — rag.* 五事件(可选事件层)
3
+ *
4
+ * rag 循环发生在节点黑盒内部(不经 ToolNode、不成图超步),这些事件把
5
+ * 内部过程经 streamSink 旁路推给外部观测,弥补"循环内调用不可见"的缺口。
6
+ *
7
+ * @see mt-docs/1-requirements/specs/agentic-rag/design-v2.md §2.2(事件输出)
8
+ */
9
+ export {};
@@ -7,6 +7,10 @@
7
7
  * - task.completed
8
8
  * - task.error
9
9
  *
10
+ * 可选事件:
11
+ * - session.compression.started
12
+ * - session.compression.completed
13
+ *
10
14
  * @see design.md §1.6 SSE事件协议定义
11
15
  */
12
16
  /** 会话创建或恢复 */
@@ -41,3 +45,37 @@ export interface TaskErrorPayload {
41
45
  /** 是否可恢复 */
42
46
  recoverable: boolean;
43
47
  }
48
+ /** 触发压缩的原因:上下文窗占用,或轮次上限 */
49
+ export type SessionCompressionReason = 'window' | 'max_rounds';
50
+ /** session.compression.started — 即将摘要压缩历史;摘要 LLM 调用之前发出 */
51
+ export interface SessionCompressionStartedPayload {
52
+ sessionId: string;
53
+ taskId?: string;
54
+ reason: SessionCompressionReason;
55
+ originalMessageCount: number;
56
+ /** 上一轮 llm_turns 的 prompt 占用 */
57
+ used?: number;
58
+ /** 模型上下文窗 */
59
+ window?: number;
60
+ /** round(used/window×100);无窗口则省略 */
61
+ percent?: number;
62
+ /** 触发阈值:floor(window×0.7) 或 maxRounds */
63
+ threshold?: number;
64
+ /** 客户端可直接展示,例如 `Context 80% full. Compacting...` */
65
+ displayMessage: string;
66
+ }
67
+ /** session.compression.completed — 压缩结束(成功或失败都发) */
68
+ export interface SessionCompressionCompletedPayload {
69
+ sessionId: string;
70
+ taskId?: string;
71
+ reason: SessionCompressionReason;
72
+ success: boolean;
73
+ originalMessageCount: number;
74
+ compressedMessageCount: number;
75
+ used?: number;
76
+ window?: number;
77
+ percent?: number;
78
+ persisted: boolean;
79
+ error?: string;
80
+ displayMessage: string;
81
+ }
@@ -7,6 +7,10 @@
7
7
  * - task.completed
8
8
  * - task.error
9
9
  *
10
+ * 可选事件:
11
+ * - session.compression.started
12
+ * - session.compression.completed
13
+ *
10
14
  * @see design.md §1.6 SSE事件协议定义
11
15
  */
12
16
  export {};
@@ -6,9 +6,10 @@ export { TokenBudgetGuard, TokenBudgetExhaustedError, } from './token-budget-gua
6
6
  export { SessionBudgetChecker, SessionBudgetExhaustedError, } from './session-budget-checker.js';
7
7
  export type { NodeTokenUsage, TokenBreakdown, TaskTokenSummary, } from './token-stats.service.js';
8
8
  export { TokenStatsService } from './token-stats.service.js';
9
- export { recordTaskTokenUsages, tokenUsageFromNodePayload, collectStreamNodeTokenUsage, } from './record-task-token-usage.js';
9
+ export { recordTaskTokenUsages, tokenUsageFromNodePayload, collectStreamNodeTokenUsage, tokenUsagesFromLlmChannel, } from './record-task-token-usage.js';
10
10
  export type { TaskTokenUsageWriter } from './record-task-token-usage.js';
11
11
  export type { TokenDetailMode } from './token-detail-mode.js';
12
+ export { resolveTokenDetail, shouldEmitTokenSse } from './token-detail.js';
12
13
  export { contextWindowUsage, buildContextUsage, splitContextUsage, turnSectionChars, pickContextFields, type ContextWindowUsage, type ContextUsageParts, } from './context-window.js';
13
14
  export { TokenStatsEmitter } from './token-detail-mode.js';
14
15
  export { ContextWindowBudget, createContextBudget, type ContextWindowBudgetConfig, type ContextSource, type SourceAllocation, type BudgetAllocation, DEFAULT_CONTEXT_BUDGET_CONFIG, } from './context-window-budget.js';
@@ -3,7 +3,8 @@ export { PostgresTokenBudgetRepository } from './pg-token-budget.repository.js';
3
3
  export { TokenBudgetGuard, TokenBudgetExhaustedError, } from './token-budget-guard.js';
4
4
  export { SessionBudgetChecker, SessionBudgetExhaustedError, } from './session-budget-checker.js';
5
5
  export { TokenStatsService } from './token-stats.service.js';
6
- export { recordTaskTokenUsages, tokenUsageFromNodePayload, collectStreamNodeTokenUsage, } from './record-task-token-usage.js';
6
+ export { recordTaskTokenUsages, tokenUsageFromNodePayload, collectStreamNodeTokenUsage, tokenUsagesFromLlmChannel, } from './record-task-token-usage.js';
7
+ export { resolveTokenDetail, shouldEmitTokenSse } from './token-detail.js';
7
8
  export { contextWindowUsage, buildContextUsage, splitContextUsage, turnSectionChars, pickContextFields, } from './context-window.js';
8
9
  export { TokenStatsEmitter } from './token-detail-mode.js';
9
10
  // 上下文窗口预算分配
@@ -12,6 +12,31 @@ export interface TaskTokenUsageWriter {
12
12
  }
13
13
  export declare function tokenUsageFromNodePayload(payload: unknown): TokenUsageRecord | undefined;
14
14
  export declare function collectStreamNodeTokenUsage(event: Pick<RuntimeEvent, 'type' | 'payload'>, bucket: TokenUsageRecord[]): void;
15
+ /** Graph `__llmTokenUsage` channel → Task.token_usage 行(invoke / fork 抛错后从 Checkpoint 读)。 */
16
+ export declare function tokenUsagesFromLlmChannel(state: unknown): TokenUsageRecord[];
17
+ /** 本轮 LLM 用量汇总,供 stats.token_usage 与 assistantTokenCount。 */
18
+ export declare function summarizeTokenUsages(usages: Array<{
19
+ nodeId: string;
20
+ model: string;
21
+ inputTokens: number;
22
+ outputTokens: number;
23
+ costEstimate?: number;
24
+ }>): {
25
+ total: {
26
+ input: number;
27
+ output: number;
28
+ total: number;
29
+ };
30
+ byNode: Record<string, {
31
+ input: number;
32
+ output: number;
33
+ }>;
34
+ byModel: Record<string, {
35
+ input: number;
36
+ output: number;
37
+ }>;
38
+ costEstimate: number;
39
+ };
15
40
  export declare function recordTaskTokenUsages(taskService: TaskTokenUsageWriter, taskId: string, usages: Array<{
16
41
  nodeId: string;
17
42
  model: string;
@@ -29,6 +29,50 @@ export function collectStreamNodeTokenUsage(event, bucket) {
29
29
  if (usage)
30
30
  bucket.push(usage);
31
31
  }
32
+ /** Graph `__llmTokenUsage` channel → Task.token_usage 行(invoke / fork 抛错后从 Checkpoint 读)。 */
33
+ export function tokenUsagesFromLlmChannel(state) {
34
+ if (!state || typeof state !== 'object' || Array.isArray(state))
35
+ return [];
36
+ const raw = state['__llmTokenUsage'];
37
+ if (!Array.isArray(raw))
38
+ return [];
39
+ const rows = [];
40
+ for (const item of raw) {
41
+ if (!item || typeof item !== 'object' || Array.isArray(item))
42
+ continue;
43
+ const rec = item;
44
+ if (typeof rec['nodeId'] !== 'string' &&
45
+ typeof rec['model'] !== 'string' &&
46
+ rec['inputTokens'] == null &&
47
+ rec['outputTokens'] == null) {
48
+ continue;
49
+ }
50
+ const usage = tokenUsageFromNodePayload(item);
51
+ if (usage)
52
+ rows.push(usage);
53
+ }
54
+ return rows;
55
+ }
56
+ /** 本轮 LLM 用量汇总,供 stats.token_usage 与 assistantTokenCount。 */
57
+ export function summarizeTokenUsages(usages) {
58
+ const byNode = {};
59
+ const byModel = {};
60
+ let input = 0;
61
+ let output = 0;
62
+ let costEstimate = 0;
63
+ for (const usage of usages) {
64
+ input += usage.inputTokens;
65
+ output += usage.outputTokens;
66
+ costEstimate += usage.costEstimate ?? 0;
67
+ const node = byNode[usage.nodeId] ?? (byNode[usage.nodeId] = { input: 0, output: 0 });
68
+ node.input += usage.inputTokens;
69
+ node.output += usage.outputTokens;
70
+ const model = byModel[usage.model] ?? (byModel[usage.model] = { input: 0, output: 0 });
71
+ model.input += usage.inputTokens;
72
+ model.output += usage.outputTokens;
73
+ }
74
+ return { total: { input, output, total: input + output }, byNode, byModel, costEstimate };
75
+ }
32
76
  export async function recordTaskTokenUsages(taskService, taskId, usages) {
33
77
  for (const usage of usages) {
34
78
  try {
@@ -4,7 +4,7 @@
4
4
  * 通过 tokenDetail 参数控制 Token 消耗的 SSE 输出行为:
5
5
  * - none: 不输出任何 Token 统计事件
6
6
  * - summary: 仅 Task 结束时输出 stats.token_usage 汇总
7
- * - detail: 每次 LLM 调用输出 stats.node_token + 结束时汇总
7
+ * - detail: 每次 LLM 调用输出 stats.node_token_usage + 结束时汇总
8
8
  *
9
9
  * `eap dev` 默认 detail;线上默认 summary。
10
10
  *
@@ -21,7 +21,7 @@ import { TokenStatsService } from './token-stats.service.js';
21
21
  * |---------|----------------------------------------------|-------------------|
22
22
  * | none | 不输出任何 Token 统计事件 | 生产环境前端 |
23
23
  * | summary | 仅 Task 结束时输出 stats.token_usage 汇总 | 生产环境后台/API |
24
- * | detail | 每次 LLM 调用输出 stats.node_token + 汇总 | 本地开发调试 |
24
+ * | detail | 每次 LLM 调用输出 stats.node_token_usage + 汇总 | 本地开发调试 |
25
25
  */
26
26
  export type TokenDetailMode = 'none' | 'summary' | 'detail';
27
27
  /**
@@ -46,10 +46,10 @@ export declare class TokenStatsEmitter {
46
46
  private readonly mode;
47
47
  constructor(statsService: TokenStatsService, sseEmitter: ISSEEmitter | null, mode: TokenDetailMode);
48
48
  /**
49
- * 记录 Token 消耗并按模式决定是否立即发射 stats.node_token 事件
49
+ * 记录 Token 消耗并按模式决定是否立即发射 stats.node_token_usage 事件
50
50
  *
51
51
  * - 始终调用 statsService.record() 记录数据
52
- * - mode='detail': 立即 emit stats.node_token SSE 事件
52
+ * - mode='detail': 立即 emit stats.node_token_usage SSE 事件
53
53
  * - mode='none' / 'summary': 不 emit per-call 事件
54
54
  *
55
55
  * @param taskId - 所属 Task ID
@@ -4,7 +4,7 @@
4
4
  * 通过 tokenDetail 参数控制 Token 消耗的 SSE 输出行为:
5
5
  * - none: 不输出任何 Token 统计事件
6
6
  * - summary: 仅 Task 结束时输出 stats.token_usage 汇总
7
- * - detail: 每次 LLM 调用输出 stats.node_token + 结束时汇总
7
+ * - detail: 每次 LLM 调用输出 stats.node_token_usage + 结束时汇总
8
8
  *
9
9
  * `eap dev` 默认 detail;线上默认 summary。
10
10
  *
@@ -40,10 +40,10 @@ export class TokenStatsEmitter {
40
40
  this.mode = mode;
41
41
  }
42
42
  /**
43
- * 记录 Token 消耗并按模式决定是否立即发射 stats.node_token 事件
43
+ * 记录 Token 消耗并按模式决定是否立即发射 stats.node_token_usage 事件
44
44
  *
45
45
  * - 始终调用 statsService.record() 记录数据
46
- * - mode='detail': 立即 emit stats.node_token SSE 事件
46
+ * - mode='detail': 立即 emit stats.node_token_usage SSE 事件
47
47
  * - mode='none' / 'summary': 不 emit per-call 事件
48
48
  *
49
49
  * @param taskId - 所属 Task ID
@@ -56,7 +56,7 @@ export class TokenStatsEmitter {
56
56
  // detail 模式: 立即发射 per-call 事件
57
57
  if (this.mode === 'detail' && this.sseEmitter) {
58
58
  const event = {
59
- type: 'stats.node_token',
59
+ type: 'stats.node_token_usage',
60
60
  data: {
61
61
  nodeId: usage.nodeId,
62
62
  model: usage.model,
@@ -0,0 +1,8 @@
1
+ /**
2
+ * tokenDetail:控制发给客户端的 Token SSE,不影响 Task.token_usage 落盘。
3
+ * 未设时保持现状:发 node + summary。
4
+ */
5
+ import type { TokenDetailLevel } from '@gt-fe/eap-contracts';
6
+ export type TokenDetailMode = TokenDetailLevel;
7
+ export declare function resolveTokenDetail(explicit?: TokenDetailLevel, env?: NodeJS.ProcessEnv): TokenDetailMode;
8
+ export declare function shouldEmitTokenSse(mode: TokenDetailMode, eventType: string): boolean;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * tokenDetail:控制发给客户端的 Token SSE,不影响 Task.token_usage 落盘。
3
+ * 未设时保持现状:发 node + summary。
4
+ */
5
+ export function resolveTokenDetail(explicit, env = process.env) {
6
+ if (explicit === 'none' || explicit === 'summary' || explicit === 'detail')
7
+ return explicit;
8
+ const raw = env.EAP_RUNTIME_TOKEN_DETAIL;
9
+ if (raw === 'none' || raw === 'summary' || raw === 'detail')
10
+ return raw;
11
+ return 'detail';
12
+ }
13
+ export function shouldEmitTokenSse(mode, eventType) {
14
+ if (eventType !== 'stats.node_token_usage' && eventType !== 'stats.token_usage') {
15
+ return true;
16
+ }
17
+ if (mode === 'none')
18
+ return false;
19
+ if (mode === 'summary')
20
+ return eventType === 'stats.token_usage';
21
+ return true;
22
+ }