@gt-fe/eap-runtime 0.0.29 → 0.0.30

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.
@@ -36,7 +36,7 @@ import { pickContextFields } from '../../token/context-window.js';
36
36
  import { isHitlDecision, lastAssistantHasUnansweredToolCalls } from '../hitl/hitl-handler.js';
37
37
  import { classifySkillRead } from './skill-read.js';
38
38
  import { skillEntryInvocationFromTool } from './skill-entry-span.js';
39
- import { withGraphSpan } from '../trace-span.js';
39
+ import { llmSpanInput, llmSpanOutputFromResult, withGraphSpan } from '../trace-span.js';
40
40
  import { executionTraceFromState, getExecutionStream, getExecutionTrace, runWithExecutionTrace, } from '../execution-trace-context.js';
41
41
  import { isRetryableToolFailure, resolveToolRetryConfig, sleepToolRetry, toolRetryDelayMs, } from '../../tool/tool-retry.js';
42
42
  /**
@@ -788,17 +788,30 @@ export class LangGraphAdapter {
788
788
  case 'tool':
789
789
  return this.createToolNodeFunction(node);
790
790
  case 'condition':
791
- return async (state) => withGraphSpan(state, { name: node.id, type: 'condition' }, async () => {
791
+ return async (state) => {
792
792
  const conditionExpr = node.config?.['conditionExpr']
793
793
  ?? node.config?.['conditionFn'];
794
- if (conditionExpr) {
795
- const branchValues = node.config?.['branchValues']
796
- ?? this.extractBranchValuesFromDsl(node.id);
797
- const result = await this.conditionEvaluator.evaluate(conditionExpr, state, branchValues, node.config?.['fallback'] ?? '__default__');
798
- return { currentNode: node.id, __conditionResult: result };
799
- }
800
- return { currentNode: node.id };
801
- });
794
+ const branchValues = node.config?.['branchValues']
795
+ ?? this.extractBranchValuesFromDsl(node.id);
796
+ const fallback = node.config?.['fallback'] ?? '__default__';
797
+ return withGraphSpan(state, {
798
+ name: node.id,
799
+ type: 'condition',
800
+ extra: {
801
+ input: {
802
+ conditionExpr: conditionExpr ?? null,
803
+ branchValues,
804
+ fallback,
805
+ },
806
+ },
807
+ }, async () => {
808
+ if (conditionExpr) {
809
+ const result = await this.conditionEvaluator.evaluate(conditionExpr, state, branchValues, fallback);
810
+ return { currentNode: node.id, __conditionResult: result };
811
+ }
812
+ return { currentNode: node.id };
813
+ }, { outputFrom: (result) => result['__conditionResult'] });
814
+ };
802
815
  case 'subgraph':
803
816
  return this.createSubGraphNodeFunction(node);
804
817
  case 'human':
@@ -1019,136 +1032,155 @@ export class LangGraphAdapter {
1019
1032
  lastAssistantHasUnansweredToolCalls(rawMessages)) {
1020
1033
  return { currentNode: node.id };
1021
1034
  }
1022
- return withGraphSpan(state, { name: node.id, type: 'llm' }, async () => {
1023
- const promptRoots = Array.isArray(state['promptRoots']) ? state['promptRoots'] : [];
1024
- const resolvedPrompt = resolveGraphPrompt(baseConfig.systemPrompt, promptRoots);
1025
- const loadedToolsBase = state['loadedTools'];
1026
- const agentCode = typeof state['agentCode'] === 'string' ? state['agentCode'] : undefined;
1027
- const skillDefs = state['skillDefinitions'];
1028
- const execCtx = state['executionContext'];
1029
- const userId = (typeof execCtx?.userId === 'string' && execCtx.userId) || state['userId'];
1030
- const loadedTools = disableTools
1031
- ? loadedToolsBase
1032
- : await hydrateMcpTools(loadedToolsBase, this.toolExecutor, {
1033
- authContext: {
1034
- userId: String(userId ?? ''),
1035
- delegationToken: String(execCtx?.delegationToken ?? state['delegationToken'] ?? ''),
1036
- agentCode: String(agentCode ?? ''),
1037
- },
1038
- correlationId: typeof state['correlationId'] === 'string' ? state['correlationId'] : undefined,
1039
- taskId: typeof state['taskId'] === 'string' ? state['taskId'] : undefined,
1040
- onListFailure: ({ toolset, error }) => {
1041
- this.emitToolExecutionEvents({
1042
- toolCallId: `mcp-list:${toolset}`,
1043
- toolName: MCP_LIST_TOOL_NAME,
1044
- toolset,
1045
- status: 'error',
1046
- error,
1047
- });
1048
- },
1049
- });
1050
- if (loadedTools && loadedTools !== loadedToolsBase) {
1051
- state['loadedTools'] = loadedTools;
1052
- }
1053
- // [DEBUG] 打印 loadedTools 实际状态
1054
- if (process.env.EAP_LLM_DEBUG === '1') {
1055
- const toolNames = loadedTools ? Object.keys(loadedTools) : [];
1056
- console.log(`[LLM-DEBUG] node=${node.id} loadedTools=${toolNames.length}:`, toolNames);
1057
- }
1058
- const catalog = includeSkillCatalog ? this.buildSkillCatalog(skillDefs) : undefined;
1059
- const toolCatalog = disableTools ? undefined : buildToolCatalog(loadedTools);
1060
- const jail = resolveJailFromState({
1061
- agentDir: state['agentDir'],
1062
- userId,
1063
- executionContext: execCtx,
1064
- loadedTools,
1065
- skillDefinitions: skillDefs,
1066
- });
1067
- if (this.overlayExtraRoots.length > 0) {
1068
- jail.extraRoots = [...(jail.extraRoots ?? []), ...this.overlayExtraRoots];
1069
- }
1070
- const workingDirectory = jail.workdir;
1071
- const jailRoots = resolveJailRoots(jail);
1072
- const skillDirs = Object.values(skillDefs ?? {})
1073
- .filter((skill) => Boolean(skill.bundlePath))
1074
- .map((skill) => ({
1075
- skillId: skill.skillId,
1076
- bundlePath: skill.bundlePath,
1077
- entryFile: skill.entryFile,
1078
- }));
1079
- const promptInputs = resolveLlmPromptInputs({
1080
- silent,
1081
- agentSystemPrompt: state['agentSystemPrompt'],
1082
- nodePrompt: resolvedPrompt,
1035
+ const promptRoots = Array.isArray(state['promptRoots']) ? state['promptRoots'] : [];
1036
+ const resolvedPrompt = resolveGraphPrompt(baseConfig.systemPrompt, promptRoots);
1037
+ const loadedToolsBase = state['loadedTools'];
1038
+ const agentCode = typeof state['agentCode'] === 'string' ? state['agentCode'] : undefined;
1039
+ const skillDefs = state['skillDefinitions'];
1040
+ const execCtx = state['executionContext'];
1041
+ const userId = (typeof execCtx?.userId === 'string' && execCtx.userId) || state['userId'];
1042
+ const loadedTools = disableTools
1043
+ ? loadedToolsBase
1044
+ : await hydrateMcpTools(loadedToolsBase, this.toolExecutor, {
1045
+ authContext: {
1046
+ userId: String(userId ?? ''),
1047
+ delegationToken: String(execCtx?.delegationToken ?? state['delegationToken'] ?? ''),
1048
+ agentCode: String(agentCode ?? ''),
1049
+ },
1050
+ correlationId: typeof state['correlationId'] === 'string' ? state['correlationId'] : undefined,
1051
+ taskId: typeof state['taskId'] === 'string' ? state['taskId'] : undefined,
1052
+ onListFailure: ({ toolset, error }) => {
1053
+ this.emitToolExecutionEvents({
1054
+ toolCallId: `mcp-list:${toolset}`,
1055
+ toolName: MCP_LIST_TOOL_NAME,
1056
+ toolset,
1057
+ status: 'error',
1058
+ error,
1059
+ });
1060
+ },
1083
1061
  });
1084
- const assembled = this.systemPromptBuilder.build({
1085
- agentPrompt: promptInputs.agentPrompt,
1086
- nodePrompt: promptInputs.nodePrompt,
1087
- skillCatalog: catalog,
1088
- toolCatalog,
1062
+ if (loadedTools && loadedTools !== loadedToolsBase) {
1063
+ state['loadedTools'] = loadedTools;
1064
+ }
1065
+ // [DEBUG] 打印 loadedTools 实际状态
1066
+ if (process.env.EAP_LLM_DEBUG === '1') {
1067
+ const toolNames = loadedTools ? Object.keys(loadedTools) : [];
1068
+ console.log(`[LLM-DEBUG] node=${node.id} loadedTools=${toolNames.length}:`, toolNames);
1069
+ }
1070
+ const catalog = includeSkillCatalog ? this.buildSkillCatalog(skillDefs) : undefined;
1071
+ const toolCatalog = disableTools ? undefined : buildToolCatalog(loadedTools);
1072
+ const jail = resolveJailFromState({
1073
+ agentDir: state['agentDir'],
1074
+ userId,
1075
+ executionContext: execCtx,
1076
+ loadedTools,
1077
+ skillDefinitions: skillDefs,
1078
+ });
1079
+ if (this.overlayExtraRoots.length > 0) {
1080
+ jail.extraRoots = [...(jail.extraRoots ?? []), ...this.overlayExtraRoots];
1081
+ }
1082
+ const workingDirectory = jail.workdir;
1083
+ const jailRoots = resolveJailRoots(jail);
1084
+ const skillDirs = Object.values(skillDefs ?? {})
1085
+ .filter((skill) => Boolean(skill.bundlePath))
1086
+ .map((skill) => ({
1087
+ skillId: skill.skillId,
1088
+ bundlePath: skill.bundlePath,
1089
+ entryFile: skill.entryFile,
1090
+ }));
1091
+ const promptInputs = resolveLlmPromptInputs({
1092
+ silent,
1093
+ agentSystemPrompt: state['agentSystemPrompt'],
1094
+ nodePrompt: resolvedPrompt,
1095
+ });
1096
+ const assembled = this.systemPromptBuilder.build({
1097
+ agentPrompt: promptInputs.agentPrompt,
1098
+ nodePrompt: promptInputs.nodePrompt,
1099
+ skillCatalog: catalog,
1100
+ toolCatalog,
1101
+ agentCode,
1102
+ userId,
1103
+ workingDirectory,
1104
+ os: { platform: os.platform(), release: os.release() },
1105
+ jailRoots,
1106
+ skillDirs,
1107
+ });
1108
+ const apiTools = disableTools ? [] : loadedToolsToApiTools(loadedTools);
1109
+ const finalConfig = {
1110
+ ...baseConfig,
1111
+ systemPrompt: assembled.text,
1112
+ systemSections: assembled.sections,
1113
+ turn: {
1114
+ sessionId: typeof state['sessionId'] === 'string' ? state['sessionId'] : undefined,
1115
+ taskId: typeof state['taskId'] === 'string' ? state['taskId'] : undefined,
1116
+ nodeId: node.id,
1089
1117
  agentCode,
1090
- userId,
1091
- workingDirectory,
1092
- os: { platform: os.platform(), release: os.release() },
1093
- jailRoots,
1094
- skillDirs,
1095
- });
1096
- const apiTools = disableTools ? [] : loadedToolsToApiTools(loadedTools);
1097
- const finalConfig = {
1098
- ...baseConfig,
1099
- systemPrompt: assembled.text,
1100
- systemSections: assembled.sections,
1101
- turn: {
1102
- sessionId: typeof state['sessionId'] === 'string' ? state['sessionId'] : undefined,
1103
- taskId: typeof state['taskId'] === 'string' ? state['taskId'] : undefined,
1104
- nodeId: node.id,
1105
- agentCode,
1106
- correlationId: typeof state['correlationId'] === 'string' ? state['correlationId'] : undefined,
1107
- },
1108
- tools: disableTools ? undefined : (apiTools.length > 0 ? apiTools : baseConfig.tools),
1109
- toolChoice: disableTools ? 'none' : baseConfig.toolChoice,
1110
- };
1111
- // 流式旁路:有本次 stream 袋时启用 LLM token 推送;silent 节点不推给用户
1112
- const stream = getExecutionStream();
1113
- const enableStream = Boolean(stream) && !silent;
1114
- if (stream) {
1115
- stream.reasoningStartedThisTurn = false;
1116
- stream.streamedTextThisTurn = false;
1117
- }
1118
- const llmNode = new LLMNode(node.id, finalConfig, this.llmProvider, enableStream, enableStream
1119
- ? (text) => {
1120
- const live = getExecutionStream();
1121
- if (!live || !text)
1122
- return;
1123
- const timestamp = new Date().toISOString();
1124
- if (!live.reasoningStartedThisTurn) {
1125
- live.reasoningStartedThisTurn = true;
1126
- live.iteration += 1;
1127
- live.sink({
1128
- type: 'reasoning.started',
1129
- taskId: undefined,
1130
- correlationId: undefined,
1131
- timestamp,
1132
- payload: {
1133
- iterationCount: live.iteration,
1134
- model: finalConfig.model,
1135
- },
1136
- });
1137
- }
1138
- live.streamedTextThisTurn = true;
1118
+ correlationId: typeof state['correlationId'] === 'string' ? state['correlationId'] : undefined,
1119
+ },
1120
+ tools: disableTools ? undefined : (apiTools.length > 0 ? apiTools : baseConfig.tools),
1121
+ toolChoice: disableTools ? 'none' : baseConfig.toolChoice,
1122
+ };
1123
+ // 流式旁路:有本次 stream 袋时启用 LLM token 推送;silent 节点不推给用户
1124
+ const stream = getExecutionStream();
1125
+ const enableStream = Boolean(stream) && !silent;
1126
+ if (stream) {
1127
+ stream.reasoningStartedThisTurn = false;
1128
+ stream.streamedTextThisTurn = false;
1129
+ }
1130
+ const llmNode = new LLMNode(node.id, finalConfig, this.llmProvider, enableStream, enableStream
1131
+ ? (text) => {
1132
+ const live = getExecutionStream();
1133
+ if (!live || !text)
1134
+ return;
1135
+ const timestamp = new Date().toISOString();
1136
+ if (!live.reasoningStartedThisTurn) {
1137
+ live.reasoningStartedThisTurn = true;
1138
+ live.iteration += 1;
1139
1139
  live.sink({
1140
- type: 'response.chunk',
1140
+ type: 'reasoning.started',
1141
1141
  taskId: undefined,
1142
1142
  correlationId: undefined,
1143
1143
  timestamp,
1144
1144
  payload: {
1145
- content: text,
1146
- index: live.chunkIndex++,
1147
- node: node.id,
1145
+ iterationCount: live.iteration,
1146
+ model: finalConfig.model,
1148
1147
  },
1149
1148
  });
1150
1149
  }
1151
- : undefined, this.interceptors);
1150
+ live.streamedTextThisTurn = true;
1151
+ live.sink({
1152
+ type: 'response.chunk',
1153
+ taskId: undefined,
1154
+ correlationId: undefined,
1155
+ timestamp,
1156
+ payload: {
1157
+ content: text,
1158
+ index: live.chunkIndex++,
1159
+ node: node.id,
1160
+ },
1161
+ });
1162
+ }
1163
+ : undefined, this.interceptors);
1164
+ const toolsForSpan = disableTools || !loadedTools
1165
+ ? undefined
1166
+ : Object.values(loadedTools).map((t) => ({
1167
+ name: t.name,
1168
+ ...(t.manifest.description?.trim()
1169
+ ? { description: t.manifest.description.trim() }
1170
+ : {}),
1171
+ }));
1172
+ // 组装后再 createNode:input 必须是发给模型的 system/tools/messages;completeNode 不能补 input。
1173
+ return withGraphSpan(state, {
1174
+ name: node.id,
1175
+ type: 'llm',
1176
+ extra: {
1177
+ input: llmSpanInput({
1178
+ system: assembled.text,
1179
+ tools: toolsForSpan,
1180
+ messages: state['messages'],
1181
+ }),
1182
+ },
1183
+ }, async () => {
1152
1184
  const next = await llmNode.execute(state);
1153
1185
  const hydratedUpdate = loadedTools && loadedTools !== loadedToolsBase ? { loadedTools } : {};
1154
1186
  if (!silent)
@@ -1163,7 +1195,7 @@ export class LangGraphAdapter {
1163
1195
  silentOutput: raw,
1164
1196
  ...hydratedUpdate,
1165
1197
  };
1166
- });
1198
+ }, { outputFrom: llmSpanOutputFromResult });
1167
1199
  };
1168
1200
  }
1169
1201
  /**
@@ -289,6 +289,12 @@ export class HitlHandler {
289
289
  name: extras.toolName ?? 'hitl',
290
290
  type: 'hitl',
291
291
  parentSpanId: extras.toolSpanId ?? skillSpanId,
292
+ extra: {
293
+ input: {
294
+ question: config.question,
295
+ options: config.options,
296
+ },
297
+ },
292
298
  });
293
299
  const persisted = {
294
300
  taskId,
@@ -12,4 +12,18 @@ export declare function completeGraphSpan(state: Record<string, unknown>, spanId
12
12
  export declare function withGraphSpan(state: Record<string, unknown>, spec: {
13
13
  name: string;
14
14
  type: ExecutionNodeEvent['type'];
15
- }, run: () => Promise<Record<string, unknown>>): Promise<Record<string, unknown>>;
15
+ extra?: Partial<Omit<ExecutionNodeEvent, 'traceId' | 'name' | 'type' | 'parentNodeId'>>;
16
+ }, run: () => Promise<Record<string, unknown>>, complete?: {
17
+ outputFrom?: (result: Record<string, unknown>) => unknown;
18
+ }): Promise<Record<string, unknown>>;
19
+ /** llm Span create 时的 input:组装后的 system、工具短目录、本回合 messages。 */
20
+ export declare function llmSpanInput(input: {
21
+ system?: string;
22
+ tools?: Array<{
23
+ name: string;
24
+ description?: string;
25
+ }>;
26
+ messages: unknown;
27
+ }): Record<string, unknown>;
28
+ /** llm Span complete 的 output:本回合新增 assistant(含 toolCalls)或 silentOutput。 */
29
+ export declare function llmSpanOutputFromResult(result: Record<string, unknown>): unknown;
@@ -43,15 +43,17 @@ export async function completeGraphSpan(state, spanId, params, collectorOverride
43
43
  warnTraceSpanFailure('completeNode', { spanId, traceId: resolveExecutionTrace(state).traceId }, err);
44
44
  }
45
45
  }
46
- export async function withGraphSpan(state, spec, run) {
46
+ export async function withGraphSpan(state, spec, run, complete) {
47
47
  const spanId = await startGraphSpan(state, spec);
48
48
  try {
49
49
  const result = await run();
50
50
  const usage = Array.isArray(result['__llmTokenUsage'])
51
51
  ? result['__llmTokenUsage'].at(-1)
52
52
  : undefined;
53
+ const output = complete?.outputFrom?.(result);
53
54
  await completeGraphSpan(state, spanId, {
54
55
  status: 'completed',
56
+ ...(output !== undefined ? { output } : {}),
55
57
  inputTokens: usage?.inputTokens,
56
58
  outputTokens: usage?.outputTokens,
57
59
  totalTokens: usage?.inputTokens != null || usage?.outputTokens != null
@@ -69,3 +71,69 @@ export async function withGraphSpan(state, spec, run) {
69
71
  throw err;
70
72
  }
71
73
  }
74
+ function spanGraphMessages(raw) {
75
+ if (!Array.isArray(raw))
76
+ return [];
77
+ return raw.map((msg) => {
78
+ if (typeof msg !== 'object' || msg === null) {
79
+ return { role: 'user', content: String(msg) };
80
+ }
81
+ const m = msg;
82
+ const out = {
83
+ role: typeof m['role'] === 'string' ? m['role'] : 'user',
84
+ content: typeof m['content'] === 'string' ? m['content'] : '',
85
+ };
86
+ const toolCalls = m['toolCalls'] ?? m['tool_calls'];
87
+ if (toolCalls !== undefined)
88
+ out.toolCalls = toolCalls;
89
+ const toolCallId = typeof m['toolCallId'] === 'string'
90
+ ? m['toolCallId']
91
+ : typeof m['tool_call_id'] === 'string'
92
+ ? m['tool_call_id']
93
+ : undefined;
94
+ if (toolCallId)
95
+ out.toolCallId = toolCallId;
96
+ if (typeof m['toolName'] === 'string')
97
+ out.toolName = m['toolName'];
98
+ if (typeof m['success'] === 'boolean')
99
+ out.success = m['success'];
100
+ const reasoning = typeof m['reasoningContent'] === 'string'
101
+ ? m['reasoningContent']
102
+ : typeof m['reasoning_content'] === 'string'
103
+ ? m['reasoning_content']
104
+ : undefined;
105
+ if (reasoning)
106
+ out.reasoningContent = reasoning;
107
+ return out;
108
+ });
109
+ }
110
+ /** llm Span create 时的 input:组装后的 system、工具短目录、本回合 messages。 */
111
+ export function llmSpanInput(input) {
112
+ const tools = input.tools?.filter((t) => t.name);
113
+ return {
114
+ ...(input.system ? { system: input.system } : {}),
115
+ ...(tools && tools.length > 0 ? { tools } : {}),
116
+ messages: spanGraphMessages(input.messages),
117
+ };
118
+ }
119
+ /** llm Span complete 的 output:本回合新增 assistant(含 toolCalls)或 silentOutput。 */
120
+ export function llmSpanOutputFromResult(result) {
121
+ if (typeof result['silentOutput'] === 'string') {
122
+ return { content: result['silentOutput'] };
123
+ }
124
+ const messages = Array.isArray(result['messages']) ? result['messages'] : [];
125
+ const added = messages[0];
126
+ if (!added || typeof added !== 'object')
127
+ return undefined;
128
+ const m = added;
129
+ const out = {
130
+ content: typeof m['content'] === 'string' ? m['content'] : '',
131
+ };
132
+ const toolCalls = m['toolCalls'] ?? m['tool_calls'];
133
+ if (Array.isArray(toolCalls) && toolCalls.length > 0)
134
+ out.toolCalls = toolCalls;
135
+ if (typeof m['reasoningContent'] === 'string' && m['reasoningContent']) {
136
+ out.reasoningContent = m['reasoningContent'];
137
+ }
138
+ return out;
139
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gt-fe/eap-runtime",
3
- "version": "0.0.29",
3
+ "version": "0.0.30",
4
4
  "description": "Enterprise Agent Platform — Runtime核心:Graph执行引擎、Task生命周期、Session管理、Checkpoint持久化",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",