@gt-fe/eap-runtime 0.0.20 → 0.0.21

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 (118) hide show
  1. package/README.md +3 -2
  2. package/dist/api/handlers/invoke.handler.d.ts +7 -0
  3. package/dist/api/handlers/invoke.handler.js +48 -9
  4. package/dist/api/handlers/stream.handler.d.ts +6 -0
  5. package/dist/api/handlers/stream.handler.js +56 -39
  6. package/dist/api/routes/agent.routes.d.ts +8 -1
  7. package/dist/common/types.d.ts +2 -2
  8. package/dist/common/types.js +1 -1
  9. package/dist/execution/index.d.ts +2 -0
  10. package/dist/execution/index.js +2 -0
  11. package/dist/execution/interceptors.d.ts +40 -0
  12. package/dist/execution/interceptors.js +23 -0
  13. package/dist/execution/ports.d.ts +18 -0
  14. package/dist/execution/ports.js +13 -0
  15. package/dist/governance/audit-reporter.d.ts +14 -0
  16. package/dist/governance/audit-reporter.js +18 -0
  17. package/dist/governance/index.d.ts +2 -0
  18. package/dist/governance/index.js +1 -0
  19. package/dist/governance/permission-guard.d.ts +6 -2
  20. package/dist/governance/permission-guard.js +38 -5
  21. package/dist/graph/adapters/graph-nesting.d.ts +12 -0
  22. package/dist/graph/adapters/graph-nesting.js +29 -0
  23. package/dist/graph/adapters/index.d.ts +2 -0
  24. package/dist/graph/adapters/index.js +2 -0
  25. package/dist/graph/adapters/langgraph.adapter.d.ts +25 -11
  26. package/dist/graph/adapters/langgraph.adapter.js +142 -82
  27. package/dist/graph/adapters/skill-isolate.d.ts +17 -0
  28. package/dist/graph/adapters/skill-isolate.js +50 -0
  29. package/dist/graph/adapters/subgraph-compiler.d.ts +7 -2
  30. package/dist/graph/adapters/subgraph-compiler.js +68 -32
  31. package/dist/graph/contracts/executable-graph.d.ts +27 -5
  32. package/dist/graph/hitl/hitl-handler.js +2 -0
  33. package/dist/graph/nodes/index.d.ts +1 -0
  34. package/dist/graph/nodes/index.js +1 -0
  35. package/dist/graph/nodes/llm-node.d.ts +3 -1
  36. package/dist/graph/nodes/llm-node.js +26 -2
  37. package/dist/graph/nodes/rag/dedup.d.ts +20 -0
  38. package/dist/graph/nodes/rag/dedup.js +77 -0
  39. package/dist/graph/nodes/rag/denoise.d.ts +58 -0
  40. package/dist/graph/nodes/rag/denoise.js +220 -0
  41. package/dist/graph/nodes/rag/fusion/fuse.d.ts +11 -0
  42. package/dist/graph/nodes/rag/fusion/fuse.js +25 -0
  43. package/dist/graph/nodes/rag/fusion/normalize.d.ts +9 -0
  44. package/dist/graph/nodes/rag/fusion/normalize.js +135 -0
  45. package/dist/graph/nodes/rag/index.d.ts +18 -0
  46. package/dist/graph/nodes/rag/index.js +17 -0
  47. package/dist/graph/nodes/rag/prompts.d.ts +12 -0
  48. package/dist/graph/nodes/rag/prompts.js +40 -0
  49. package/dist/graph/nodes/rag/rag-node.d.ts +21 -0
  50. package/dist/graph/nodes/rag/rag-node.js +135 -0
  51. package/dist/graph/nodes/rag/rag-search-tool.d.ts +48 -0
  52. package/dist/graph/nodes/rag/rag-search-tool.js +230 -0
  53. package/dist/graph/nodes/rag/react-loop.d.ts +57 -0
  54. package/dist/graph/nodes/rag/react-loop.js +467 -0
  55. package/dist/graph/nodes/rag/retrievers/http-embedder.d.ts +29 -0
  56. package/dist/graph/nodes/rag/retrievers/http-embedder.js +66 -0
  57. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.d.ts +17 -0
  58. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.js +18 -0
  59. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.d.ts +46 -0
  60. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.js +85 -0
  61. package/dist/graph/nodes/rag/tools.d.ts +90 -0
  62. package/dist/graph/nodes/rag/tools.js +260 -0
  63. package/dist/graph/nodes/rag/types.d.ts +226 -0
  64. package/dist/graph/nodes/rag/types.js +9 -0
  65. package/dist/graph/nodes/tool-node.d.ts +19 -1
  66. package/dist/graph/nodes/tool-node.js +162 -33
  67. package/dist/index.d.ts +2 -0
  68. package/dist/index.js +1 -0
  69. package/dist/memory/semantic-retrieval.service.d.ts +1 -1
  70. package/dist/memory/semantic-retrieval.service.js +13 -23
  71. package/dist/providers/index.d.ts +1 -1
  72. package/dist/providers/index.js +1 -1
  73. package/dist/providers/routing-llm-provider.d.ts +5 -0
  74. package/dist/providers/routing-llm-provider.js +66 -1
  75. package/dist/runtime/agent-runtime.d.ts +5 -0
  76. package/dist/runtime/agent-runtime.js +86 -66
  77. package/dist/runtime/dev-rag-deps.d.ts +21 -0
  78. package/dist/runtime/dev-rag-deps.js +186 -0
  79. package/dist/runtime/dev-runtime.d.ts +16 -1
  80. package/dist/runtime/dev-runtime.js +59 -5
  81. package/dist/runtime/index.d.ts +1 -1
  82. package/dist/runtime/types.d.ts +10 -9
  83. package/dist/session/apply-session-compression.d.ts +81 -0
  84. package/dist/session/apply-session-compression.js +262 -0
  85. package/dist/session/apply-session-memory.d.ts +21 -0
  86. package/dist/session/apply-session-memory.js +48 -0
  87. package/dist/session/index.d.ts +2 -0
  88. package/dist/session/index.js +2 -0
  89. package/dist/session/persist-turn-messages.d.ts +51 -0
  90. package/dist/session/persist-turn-messages.js +140 -3
  91. package/dist/sse/events/index.d.ts +13 -4
  92. package/dist/sse/events/index.js +9 -1
  93. package/dist/sse/events/rag-events.d.ts +63 -0
  94. package/dist/sse/events/rag-events.js +9 -0
  95. package/dist/sse/events/session-events.d.ts +38 -0
  96. package/dist/sse/events/session-events.js +4 -0
  97. package/dist/token/index.d.ts +2 -1
  98. package/dist/token/index.js +2 -1
  99. package/dist/token/record-task-token-usage.d.ts +2 -0
  100. package/dist/token/record-task-token-usage.js +24 -0
  101. package/dist/token/token-detail-mode.d.ts +4 -4
  102. package/dist/token/token-detail-mode.js +4 -4
  103. package/dist/token/token-detail.d.ts +8 -0
  104. package/dist/token/token-detail.js +22 -0
  105. package/dist/token/token-stats.service.d.ts +2 -2
  106. package/dist/token/token-stats.service.js +1 -1
  107. package/dist/tool/create-tool-executor.d.ts +6 -0
  108. package/dist/tool/create-tool-executor.js +4 -3
  109. package/dist/tool/harness-primitives.d.ts +9 -1
  110. package/dist/tool/harness-primitives.js +43 -12
  111. package/dist/tool/index.d.ts +2 -1
  112. package/dist/tool/index.js +2 -1
  113. package/dist/tool/tool-executor.interface.d.ts +27 -0
  114. package/dist/tool/tool-orchestrator.d.ts +3 -1
  115. package/dist/tool/tool-orchestrator.js +15 -6
  116. package/dist/tool/tool-retry.d.ts +15 -0
  117. package/dist/tool/tool-retry.js +69 -0
  118. package/package.json +2 -2
@@ -12,12 +12,15 @@ import { resolveAgentSystemPromptFromLoader } from '../registry/graph-loader.js'
12
12
  import { toLoadedSkillMap } from '../tool/system-prompt-builder.js';
13
13
  import { assembleLoadedTools } from '../tool/harness-primitives.js';
14
14
  import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
15
- import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractHitlThreadId, extractStreamReasoning, extractStreamTextDelta, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, } from '../session/persist-turn-messages.js';
15
+ import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractHitlThreadId, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, appendTerminalToolResult, } from '../session/persist-turn-messages.js';
16
+ import { iterSessionCompression, } from '../session/apply-session-compression.js';
17
+ import { applySessionMemory } from '../session/apply-session-memory.js';
18
+ import { resolveTokenDetail, shouldEmitTokenSse } from '../token/token-detail.js';
16
19
  import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
17
20
  import { resolveExecutionContext } from '../governance/governance-client.js';
18
21
  import { resolveAgentCode } from '../common/agent-code.js';
19
22
  import { resolveTraceId } from '../common/trace-id.js';
20
- import { collectStreamNodeTokenUsage, recordTaskTokenUsages, } from '../token/record-task-token-usage.js';
23
+ import { collectStreamNodeTokenUsage, recordTaskTokenUsages, tokenUsagesFromLlmChannel, } from '../token/record-task-token-usage.js';
21
24
  import { resolveSkillInputFromInvokeInput } from '../registry/skill-io-guard.js';
22
25
  const HITL_TERMINAL_STATUSES = [
23
26
  'success',
@@ -208,6 +211,8 @@ export class AgentRuntime {
208
211
  executionContext: ctx,
209
212
  userId,
210
213
  traceId: resolveTraceId(params),
214
+ ragScope: params.ragScope,
215
+ ragSources: params.ragSources,
211
216
  }, userId);
212
217
  return response.data;
213
218
  }
@@ -274,7 +279,31 @@ export class AgentRuntime {
274
279
  ? (await this.deps.graphLoader.resolveAgentDir(agentCode, params.agentVersion)) ?? undefined
275
280
  : undefined;
276
281
  const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, params.agentVersion);
277
- const history = messageRecordsToGraphMessages(sessionContext.messages);
282
+ let history = messageRecordsToGraphMessages(sessionContext.messages);
283
+ for await (const step of iterSessionCompression({
284
+ history,
285
+ sessionId,
286
+ userId,
287
+ taskId: task.id,
288
+ options: {
289
+ ...this.deps.sessionCompression,
290
+ turnLog: this.deps.sessionCompression?.turnLog ?? this.deps.turnLog,
291
+ },
292
+ })) {
293
+ if (step.kind === 'history') {
294
+ history = step.messages;
295
+ continue;
296
+ }
297
+ yield this.toSSE(step.kind === 'started' ? 'session.compression.started' : 'session.compression.completed', correlationId, task.id, { ...step.payload });
298
+ }
299
+ history = await applySessionMemory({
300
+ history,
301
+ userId,
302
+ agentCode,
303
+ query: typeof params.input === 'string' ? params.input : undefined,
304
+ options: this.deps.sessionMemory,
305
+ });
306
+ const tokenDetail = resolveTokenDetail(params.config?.tokenDetail);
278
307
  const messages = await buildTurnGraphMessages({
279
308
  history,
280
309
  input: params.input,
@@ -282,7 +311,7 @@ export class AgentRuntime {
282
311
  threadId: sessionId,
283
312
  });
284
313
  const startTime = Date.now();
285
- let assistantText = '';
314
+ const assistantBuckets = createStreamAssistantBuckets();
286
315
  let toolCallReasoningContent;
287
316
  let assistantReasoningContent;
288
317
  const pendingToolCalls = [];
@@ -300,6 +329,8 @@ export class AgentRuntime {
300
329
  sessionId,
301
330
  executionContext: ctx,
302
331
  forceToolServer: params.config?.forceToolServer,
332
+ ragScope: params.ragScope,
333
+ ragSources: params.ragSources,
303
334
  loadedTools,
304
335
  loadedSkills,
305
336
  skillDefinitions,
@@ -323,32 +354,16 @@ export class AgentRuntime {
323
354
  toolCallReasoningContent = p.reasoningContent;
324
355
  }
325
356
  }
326
- if (runtimeEvent.type === 'tool.result') {
327
- const p = runtimeEvent.payload;
328
- if (p.toolCallId && p.toolName) {
329
- toolResults.push({
330
- toolCallId: p.toolCallId,
331
- toolName: p.toolName,
332
- content: p.content,
333
- success: p.success !== false,
334
- });
335
- }
336
- }
357
+ // 终态 tool.result / tool.error 回填历史;willRetry 的 error 不进 transcript
358
+ appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
337
359
  const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
338
360
  if (streamedReasoning)
339
361
  assistantReasoningContent = streamedReasoning;
340
- const delta = extractStreamTextDelta(runtimeEvent.type, runtimeEvent.payload);
341
- if (delta) {
342
- if (runtimeEvent.type === 'response.final') {
343
- if (delta.trim())
344
- assistantText = delta;
345
- }
346
- else {
347
- assistantText += delta;
348
- }
349
- }
362
+ applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
350
363
  collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
351
- yield this.runtimeEventToSSE(runtimeEvent, correlationId, task.id);
364
+ if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
365
+ yield this.runtimeEventToSSE(runtimeEvent, correlationId, task.id);
366
+ }
352
367
  }
353
368
  await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
354
369
  await this.deps.taskService.updateStatus(task.id, 'success');
@@ -359,7 +374,9 @@ export class AgentRuntime {
359
374
  messages: buildTurnTranscriptFromStreamEvents({
360
375
  pendingToolCalls,
361
376
  toolResults,
362
- assistantText,
377
+ assistantTexts: streamAssistantTexts(assistantBuckets),
378
+ assistantText: visibleStreamAssistantText(assistantBuckets),
379
+ finishPoints: graph.finishPoints,
363
380
  toolCallReasoningContent,
364
381
  assistantReasoningContent,
365
382
  }),
@@ -390,6 +407,7 @@ export class AgentRuntime {
390
407
  return;
391
408
  }
392
409
  const message = err instanceof Error ? err.message : 'Unknown error';
410
+ await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
393
411
  await this.deps.taskService.updateStatus(task.id, 'failed', message);
394
412
  yield this.toSSE('task.error', correlationId, task.id, {
395
413
  code: 'runtime.graph.execution_error',
@@ -409,7 +427,7 @@ export class AgentRuntime {
409
427
  let output = '';
410
428
  let waiting;
411
429
  let sawWait = false;
412
- let assistantText = '';
430
+ const assistantBuckets = createStreamAssistantBuckets();
413
431
  for await (const event of this.streamResumeHitl(taskId, response)) {
414
432
  if (event.type === 'hitl.waited') {
415
433
  sawWait = true;
@@ -421,21 +439,12 @@ export class AgentRuntime {
421
439
  };
422
440
  output = waiting.question;
423
441
  }
424
- const delta = extractStreamTextDelta(event.type, event.data);
425
- if (delta) {
426
- if (event.type === 'response.final') {
427
- if (delta.trim())
428
- assistantText = delta;
429
- }
430
- else {
431
- assistantText += delta;
432
- }
433
- }
442
+ applyStreamTextDelta(assistantBuckets, event.type, event.data);
434
443
  }
435
444
  if (sawWait) {
436
445
  return { taskId, status: 'waiting_hitl', output, hitl: waiting };
437
446
  }
438
- return { taskId, status: 'success', output: assistantText || output };
447
+ return { taskId, status: 'success', output: visibleStreamAssistantText(assistantBuckets) || output };
439
448
  }
440
449
  /**
441
450
  * HITL 恢复的流式入口:新开一条 SSE(第一条 stream 在 `hitl.waited` 后已结束)。
@@ -465,6 +474,7 @@ export class AgentRuntime {
465
474
  if (!claimed) {
466
475
  throw new AppError(ErrorCodes.TASK_HITL_INVALID_STATE, `Task ${taskId} is no longer waiting for HITL`, { exposeMessage: true });
467
476
  }
477
+ const tokenDetail = resolveTokenDetail(response.tokenDetail);
468
478
  yield this.toSSE('hitl.resumed', correlationId, taskId, {
469
479
  choice: response.choice,
470
480
  userId: response.userId,
@@ -516,7 +526,7 @@ export class AgentRuntime {
516
526
  traceCollector: this.deps.traceCollector,
517
527
  };
518
528
  const startTime = Date.now();
519
- let assistantText = '';
529
+ const assistantBuckets = createStreamAssistantBuckets();
520
530
  let toolCallReasoningContent;
521
531
  let assistantReasoningContent;
522
532
  const pendingToolCalls = [];
@@ -537,35 +547,18 @@ export class AgentRuntime {
537
547
  toolCallReasoningContent = p.reasoningContent;
538
548
  }
539
549
  }
540
- if (runtimeEvent.type === 'tool.result' || runtimeEvent.type === 'tool.error') {
541
- const p = runtimeEvent.payload;
542
- if (p.toolCallId && p.toolName) {
543
- toolResults.push({
544
- toolCallId: p.toolCallId,
545
- toolName: p.toolName,
546
- content: p.content ?? p.error,
547
- success: runtimeEvent.type === 'tool.result' && p.success !== false,
548
- });
549
- }
550
- }
550
+ appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
551
551
  const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
552
552
  if (streamedReasoning)
553
553
  assistantReasoningContent = streamedReasoning;
554
- const delta = extractStreamTextDelta(runtimeEvent.type, runtimeEvent.payload);
555
- if (delta) {
556
- if (runtimeEvent.type === 'response.final') {
557
- if (delta.trim())
558
- assistantText = delta;
559
- }
560
- else {
561
- assistantText += delta;
562
- }
563
- }
554
+ applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
564
555
  collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
565
- yield this.runtimeEventToSSE(runtimeEvent, correlationId, taskId);
556
+ if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
557
+ yield this.runtimeEventToSSE(runtimeEvent, correlationId, taskId);
558
+ }
566
559
  }
567
560
  await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
568
- await this.deps.taskService.updateStatus(taskId, 'success', assistantText || undefined);
561
+ await this.deps.taskService.updateStatus(taskId, 'success', visibleStreamAssistantText(assistantBuckets) || undefined);
569
562
  if (sessionId) {
570
563
  await persistTurnTranscript(this.deps.messageService, {
571
564
  sessionId,
@@ -574,7 +567,9 @@ export class AgentRuntime {
574
567
  messages: buildTurnTranscriptFromStreamEvents({
575
568
  pendingToolCalls,
576
569
  toolResults,
577
- assistantText,
570
+ assistantTexts: streamAssistantTexts(assistantBuckets),
571
+ assistantText: visibleStreamAssistantText(assistantBuckets),
572
+ finishPoints: graph.finishPoints,
578
573
  toolCallReasoningContent,
579
574
  assistantReasoningContent,
580
575
  }),
@@ -609,6 +604,7 @@ export class AgentRuntime {
609
604
  }
610
605
  const appErr = err instanceof AppError ? err : AppError.fromUnknown(err);
611
606
  const failMsg = appErr.exposeMessage ? appErr.message : '系统内部错误';
607
+ await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
612
608
  await this.deps.taskService.updateStatus(taskId, 'failed', failMsg);
613
609
  yield this.toSSE('task.error', correlationId, taskId, {
614
610
  code: appErr.code,
@@ -671,8 +667,14 @@ export class AgentRuntime {
671
667
  ? forkedCheckpoint.state.agentSystemPrompt
672
668
  : undefined);
673
669
  try {
670
+ const forkState = { ...forkedCheckpoint.state };
671
+ delete forkState.toolSpanId;
672
+ delete forkState.hitlToolCallId;
674
673
  const result = await this.deps.graphEngine.invoke(graph, {
675
- ...forkedCheckpoint.state,
674
+ messages: Array.isArray(forkState.messages)
675
+ ? forkState.messages
676
+ : [],
677
+ ...forkState,
676
678
  agentVersion,
677
679
  agentDir,
678
680
  agentSystemPrompt,
@@ -729,6 +731,20 @@ export class AgentRuntime {
729
731
  }
730
732
  catch (err) {
731
733
  const message = err instanceof Error ? err.message : 'Unknown error';
734
+ const sessionId = typeof forkedCheckpoint.state.sessionId === 'string'
735
+ ? forkedCheckpoint.state.sessionId
736
+ : undefined;
737
+ const threadId = resolveCheckpointThreadId({
738
+ sessionId,
739
+ taskId: task.id,
740
+ });
741
+ const ck = await this.deps.checkpointSaver.load(threadId);
742
+ const baseline = tokenUsagesFromLlmChannel(forkedCheckpoint.state);
743
+ const latest = tokenUsagesFromLlmChannel(ck?.state ?? forkedCheckpoint.state);
744
+ const usages = latest.length >= baseline.length ? latest.slice(baseline.length) : latest;
745
+ await recordTaskTokenUsages(this.deps.taskService, task.id, usages);
746
+ const input_tokens = usages.reduce((sum, row) => sum + row.inputTokens, 0);
747
+ const output_tokens = usages.reduce((sum, row) => sum + row.outputTokens, 0);
732
748
  await this.deps.taskService.updateStatus(task.id, 'failed', message);
733
749
  return {
734
750
  newTraceId: traceId,
@@ -737,7 +753,11 @@ export class AgentRuntime {
737
753
  checkpointId,
738
754
  status: 'failed',
739
755
  output: message,
740
- tokenUsage: { input_tokens: 0, output_tokens: 0, total_tokens: 0 },
756
+ tokenUsage: {
757
+ input_tokens,
758
+ output_tokens,
759
+ total_tokens: input_tokens + output_tokens,
760
+ },
741
761
  };
742
762
  }
743
763
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * dev 装配的 rag 依赖 — createDevRuntime 检测显式配置后自动注入
3
+ *
4
+ * 启用条件:EAP_EMBED_API_KEY 存在(缺省不注入,行为与无 rag 的 master 一致)。
5
+ * retriever 惰性初始化:vector-engine(better-sqlite3+sqlite-vec,native 依赖)
6
+ * 只在首次检索时动态 import——装配期零 native 加载,服务端部署不受影响。
7
+ * import 目标可用 EAP_VECTOR_ENGINE_PATH 指定绝对路径(默认解析 @eap/vector-engine;
8
+ * eap-cli 发版链等无该包的环境走绝对路径注入)。
9
+ *
10
+ * 环境变量(与 .local 演示脚本同一套):
11
+ * - EAP_EMBED_API_KEY / EAP_EMBED_BASE_URL / EAP_EMBED_MODEL / EAP_EMBED_DIMS:embedding 网关
12
+ * - RAG_DB:向量库路径(缺省 ~/.eap/rag/agent.db)
13
+ */
14
+ import type { IRetriever, RagNodeDeps } from '../graph/nodes/rag/index.js';
15
+ /**
16
+ * dev rag 装配:EAP_EMBED_API_KEY 显式配置才启用,否则 undefined(不注入)。
17
+ * llmProvider 复用 dev 装配已构造的实例(与 llm 节点同一体系)。
18
+ * EAP_RAG_TRACE=1 时接 console 观测 rag.* 事件流(轮次决策/各源检索/降噪/收束),
19
+ * 供多源融合行为观察;缺省静默。
20
+ */
21
+ export declare function createDevRagNodeDeps(llmProvider: RagNodeDeps['llmProvider'], loadFn?: () => Promise<IRetriever>): RagNodeDeps | undefined;
@@ -0,0 +1,186 @@
1
+ /**
2
+ * dev 装配的 rag 依赖 — createDevRuntime 检测显式配置后自动注入
3
+ *
4
+ * 启用条件:EAP_EMBED_API_KEY 存在(缺省不注入,行为与无 rag 的 master 一致)。
5
+ * retriever 惰性初始化:vector-engine(better-sqlite3+sqlite-vec,native 依赖)
6
+ * 只在首次检索时动态 import——装配期零 native 加载,服务端部署不受影响。
7
+ * import 目标可用 EAP_VECTOR_ENGINE_PATH 指定绝对路径(默认解析 @eap/vector-engine;
8
+ * eap-cli 发版链等无该包的环境走绝对路径注入)。
9
+ *
10
+ * 环境变量(与 .local 演示脚本同一套):
11
+ * - EAP_EMBED_API_KEY / EAP_EMBED_BASE_URL / EAP_EMBED_MODEL / EAP_EMBED_DIMS:embedding 网关
12
+ * - RAG_DB:向量库路径(缺省 ~/.eap/rag/agent.db)
13
+ */
14
+ import { homedir } from 'node:os';
15
+ import { dirname, join } from 'node:path';
16
+ import { existsSync, mkdirSync, readFileSync, watchFile } from 'node:fs';
17
+ import { pathToFileURL } from 'node:url';
18
+ import { appendFileSync } from 'node:fs';
19
+ // 惰性读 env:模块顶层读不到(runtime 经 sdk import 链早于 CLI 灌 .env.local 执行)
20
+ const trace = (line) => {
21
+ console.log(line);
22
+ const f = process.env.EAP_RAG_TRACE_FILE;
23
+ if (f)
24
+ appendFileSync(f, line + '\n', 'utf-8');
25
+ };
26
+ /** 入库日志环形缓冲(globalThis 桥接:Playground 服务同进程读,GET /api/rag-ingest-log) */
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ const ingestLogBuf = (globalThis.__ragIngestLog ??= []);
29
+ const ingestLog = (line) => {
30
+ const ts = new Date().toISOString().slice(11, 19);
31
+ ingestLogBuf.push(`[${ts}] ${line}`);
32
+ if (ingestLogBuf.length > 200)
33
+ ingestLogBuf.splice(0, ingestLogBuf.length - 200);
34
+ };
35
+ import { HttpEmbedder, LocalSqliteRetriever } from '../graph/nodes/rag/index.js';
36
+ /** 惰性 IRetriever:首次调用才加载真实现;加载失败不缓存,允许下轮重试 */
37
+ class LazyLocalRetriever {
38
+ load;
39
+ sourceId = 'local';
40
+ implPromise;
41
+ constructor(load) {
42
+ this.load = load;
43
+ }
44
+ ensure() {
45
+ this.implPromise ??= this.load().catch((err) => {
46
+ this.implPromise = undefined; // 失败不缓存,下次调用重试加载
47
+ throw err;
48
+ });
49
+ return this.implPromise;
50
+ }
51
+ async retrieve(query, opts) {
52
+ return (await this.ensure()).retrieve(query, opts);
53
+ }
54
+ async retrieveByKeyword(query, opts) {
55
+ const impl = await this.ensure();
56
+ if (!impl.retrieveByKeyword)
57
+ return [];
58
+ return impl.retrieveByKeyword(query, opts);
59
+ }
60
+ }
61
+ /** 默认加载器:HttpEmbedder + vector-engine + LocalSqliteRetriever(单库:RAG_DB) */
62
+ async function loadLocalRetriever() {
63
+ const dims = Number(process.env.EAP_EMBED_DIMS ?? 1024);
64
+ const embedder = new HttpEmbedder({
65
+ baseUrl: String(process.env.EAP_EMBED_BASE_URL ?? 'https://gtaiapi.gtcloud.cn'),
66
+ model: String(process.env.EAP_EMBED_MODEL ?? 'Qwen3-Embedding-8B'),
67
+ apiKey: String(process.env.EAP_EMBED_API_KEY ?? ''),
68
+ dims,
69
+ dimensions: dims,
70
+ });
71
+ const moduleName = process.env.EAP_VECTOR_ENGINE_PATH ?? '@eap/vector-engine';
72
+ // Windows 绝对路径须转 file:// URL(盘符字母否则被解析为 URL scheme)
73
+ const spec = /^[a-zA-Z]:[\\/]/.test(moduleName) ? pathToFileURL(moduleName).href : moduleName;
74
+ const mod = (await import(spec));
75
+ const dbPath = process.env.RAG_DB ?? join(homedir(), '.eap', 'rag', 'agent.db');
76
+ mkdirSync(dirname(dbPath), { recursive: true });
77
+ const engine = await mod.createAsyncEngine({ dbPath, dims });
78
+ // 自动向量化:EAP_RAG_AUTO_INGEST=1 时常驻调度器扫授权目录,文件放入即入库
79
+ // (写入侧归宿主/桌面语义;dev 演示用轮询,生产为授权事件触发)。
80
+ // RAG_DOCS_DIR 支持 `;` 分隔多目录——"授权一个新文件夹" = 追加一个路径。
81
+ //
82
+ // 授权清单热更新(客户端授权动作的 dev 模拟):RAG_AUTHORIZED_DIRS_FILE(json
83
+ // {"dirs":[...]},缺省 <库目录>/authorized-dirs.json)存在则覆盖 RAG_DOCS_DIR,
84
+ // 文件变更 → scheduler.updateDirs 即时生效(新目录入库/移除软删),全程不重启。
85
+ if (process.env.EAP_RAG_AUTO_INGEST) {
86
+ const envDirs = (process.env.RAG_DOCS_DIR ?? join(dirname(dbPath), 'docs'))
87
+ .split(';')
88
+ .map((s) => s.trim())
89
+ .filter(Boolean);
90
+ const authFile = process.env.RAG_AUTHORIZED_DIRS_FILE ?? join(dirname(dbPath), 'authorized-dirs.json');
91
+ const readAuthDirs = () => {
92
+ try {
93
+ const parsed = JSON.parse(readFileSync(authFile, 'utf-8'));
94
+ if (Array.isArray(parsed.dirs) && parsed.dirs.every((d) => typeof d === 'string')) {
95
+ const dirs = parsed.dirs.map((d) => d.trim()).filter(Boolean);
96
+ return dirs.length ? dirs : undefined; // 空清单视为无效,防误清库
97
+ }
98
+ }
99
+ catch {
100
+ // 文件不存在/解析失败:保持现清单
101
+ }
102
+ return undefined;
103
+ };
104
+ let currentDirs = readAuthDirs() ?? envDirs;
105
+ const scheduler = new mod.IngestScheduler({
106
+ engine,
107
+ embedder,
108
+ userId: process.env.RAG_USER ?? 'anonymous',
109
+ dirs: currentDirs,
110
+ intervalMs: Number(process.env.EAP_RAG_SCAN_INTERVAL_MS ?? 30_000),
111
+ watch: process.env.EAP_RAG_SCAN_MODE !== 'interval',
112
+ onEvent: (e) => {
113
+ if (e.type === 'file.ingested') {
114
+ const line = `[rag-ingest] 📥 ${String(e['filePath']).split('\\').pop()} → ${e['chunks']} 片`;
115
+ console.log(line);
116
+ ingestLog(line);
117
+ }
118
+ else if (e.type === 'file.failed') {
119
+ const line = `[rag-ingest] ✗ ${String(e['filePath']).split('\\').pop()}: ${e['error']}`;
120
+ console.log(line);
121
+ ingestLog(line);
122
+ }
123
+ },
124
+ });
125
+ scheduler.start();
126
+ ingestLog(`🚀 自动向量化已启动:${currentDirs.length} 个授权目录`);
127
+ console.log(`[rag-ingest] 自动向量化已启动:目录 ${currentDirs.join('; ')}(${Number(process.env.EAP_RAG_SCAN_INTERVAL_MS ?? 30_000) / 1000}s 轮询${existsSync(authFile) ? `,授权清单 ${authFile} 热更新` : ''})`);
128
+ // 授权清单文件变更 → updateDirs(编辑器保存/前端写入均触发;stat 轮询最稳)
129
+ watchFile(authFile, { interval: 2000 }, () => {
130
+ const next = readAuthDirs();
131
+ if (next && next.join(';') !== currentDirs.join(';')) {
132
+ currentDirs = next;
133
+ scheduler.updateDirs(next);
134
+ const line = `[rag-ingest] 🔑 授权目录更新(免重启生效):${next.length} 个目录`;
135
+ console.log(line + `(${next.join('; ')})`);
136
+ ingestLog(line);
137
+ }
138
+ });
139
+ }
140
+ return new LocalSqliteRetriever({ engine, embedder });
141
+ }
142
+ /**
143
+ * dev rag 装配:EAP_EMBED_API_KEY 显式配置才启用,否则 undefined(不注入)。
144
+ * llmProvider 复用 dev 装配已构造的实例(与 llm 节点同一体系)。
145
+ * EAP_RAG_TRACE=1 时接 console 观测 rag.* 事件流(轮次决策/各源检索/降噪/收束),
146
+ * 供多源融合行为观察;缺省静默。
147
+ */
148
+ export function createDevRagNodeDeps(llmProvider, loadFn = loadLocalRetriever) {
149
+ if (!process.env.EAP_EMBED_API_KEY)
150
+ return undefined;
151
+ const fmt = (v, n = 40) => String(v ?? '').replace(/\s+/g, ' ').slice(0, n);
152
+ const emitEvent = process.env.EAP_RAG_TRACE
153
+ ? (type, p) => {
154
+ if (type === 'rag.round') {
155
+ const calls = (p['toolCalls'] ?? [])
156
+ .map((t) => `${t.name}(${JSON.stringify(t.arguments)})`).join(' ');
157
+ trace(`[rag] 第${p['round']}轮: ${calls}`);
158
+ }
159
+ else if (type === 'rag.search') {
160
+ const head = p['kind'] === 'kb' ? `kb:${p['toolName']}` : `local:"${fmt(p['query'], 20)}"`;
161
+ trace(`[rag] search ${head} topK=${p['topK']} → hits=${p['hits']} ${p['durationMs']}ms${p['success'] ? '' : ` ✗${p['error']}`}${p['scope'] ? ` scope=${p['scope']}` : ''}`);
162
+ }
163
+ else if (type === 'rag.denoise') {
164
+ trace(`[rag] 降噪 候选${p['candidates']} → 保留${p['kept']}`);
165
+ for (const f of p['filtered'] ?? [])
166
+ trace(`[rag] 滤 ${fmt(f.rawId, 30)}(${fmt(f.reason, 30)})`);
167
+ }
168
+ else if (type === 'rag.rewrite') {
169
+ trace(`[rag] 空结果重写 "${fmt(p['from'], 20)}" → "${fmt(p['to'], 20)}"`);
170
+ }
171
+ else if (type === 'rag.finish') {
172
+ trace(`[rag] 收束: ${p['rounds']}轮 最终${p['docs']}条${p['empty'] ? '(空)' : ''}`);
173
+ }
174
+ }
175
+ : undefined;
176
+ // 预热:自动向量化开启时立即后台装配一次——调度器/授权热更新/入库日志不等到首次
177
+ // local_search 才启动(演示"先授权后提问"场景需要授权即生效)。失败静默,检索时重试。
178
+ if (process.env.EAP_RAG_AUTO_INGEST) {
179
+ void Promise.resolve(loadFn()).catch(() => undefined);
180
+ }
181
+ return {
182
+ llmProvider,
183
+ retrievers: [new LazyLocalRetriever(loadFn)],
184
+ emitEvent,
185
+ };
186
+ }
@@ -28,6 +28,19 @@ import { type AgentRuntime } from './agent-runtime.js';
28
28
  import type { RuntimeStorageProfile } from './storage-profile.js';
29
29
  import type { HitlResponse, ResumeHitlResult, SSEEvent } from './types.js';
30
30
  import type { ILlmTurnLog } from '../debug/llm-turn-log.js';
31
+ import type { ExecutionInterceptors } from '../execution/interceptors.js';
32
+ import { LongTermMemoryService } from '../memory/long-term-memory.service.js';
33
+ export interface CreateDevRuntimeOptions {
34
+ interceptors?: ExecutionInterceptors;
35
+ compression?: {
36
+ enabled?: boolean;
37
+ persistCompressed?: boolean;
38
+ };
39
+ memory?: {
40
+ enabled?: boolean;
41
+ service?: LongTermMemoryService;
42
+ };
43
+ }
31
44
  /** createDevRuntime 返回值(CLI 嵌入用,可选字段后续扩展) */
32
45
  export interface DevRuntime {
33
46
  invokeOrchestrator: InvokeOrchestrator;
@@ -63,6 +76,8 @@ export interface DevRuntime {
63
76
  llmTurnKind: 'file' | 'postgres';
64
77
  foundationConfigured: boolean;
65
78
  skillHotReload: boolean;
79
+ /** rag 节点依赖已注入(EAP_EMBED_API_KEY 显式配置时 true) */
80
+ ragEnabled: boolean;
66
81
  };
67
82
  }
68
83
  /**
@@ -71,4 +86,4 @@ export interface DevRuntime {
71
86
  * @returns DevRuntime(含 invokeOrchestrator / streamOrchestrator / 诊断摘要)
72
87
  * @throws EAP_LLM_MODE=local 但解不出任何密钥时抛错,避免静默 Echo
73
88
  */
74
- export declare function createDevRuntime(): DevRuntime;
89
+ export declare function createDevRuntime(options?: CreateDevRuntimeOptions): DevRuntime;