@librechat/agents 3.1.67-dev.4 → 3.1.68

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 (162) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +3 -23
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +0 -16
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +0 -91
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +36 -0
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/main.cjs +1 -53
  10. package/dist/cjs/main.cjs.map +1 -1
  11. package/dist/cjs/messages/format.cjs +12 -74
  12. package/dist/cjs/messages/format.cjs.map +1 -1
  13. package/dist/cjs/run.cjs +0 -111
  14. package/dist/cjs/run.cjs.map +1 -1
  15. package/dist/cjs/summarization/index.cjs +41 -0
  16. package/dist/cjs/summarization/index.cjs.map +1 -1
  17. package/dist/cjs/summarization/node.cjs +121 -63
  18. package/dist/cjs/summarization/node.cjs.map +1 -1
  19. package/dist/cjs/tools/ToolNode.cjs +140 -304
  20. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  21. package/dist/esm/agents/AgentContext.mjs +3 -23
  22. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  23. package/dist/esm/common/enum.mjs +1 -15
  24. package/dist/esm/common/enum.mjs.map +1 -1
  25. package/dist/esm/graphs/Graph.mjs +0 -91
  26. package/dist/esm/graphs/Graph.mjs.map +1 -1
  27. package/dist/esm/graphs/MultiAgentGraph.mjs +36 -0
  28. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  29. package/dist/esm/main.mjs +2 -13
  30. package/dist/esm/main.mjs.map +1 -1
  31. package/dist/esm/messages/format.mjs +4 -66
  32. package/dist/esm/messages/format.mjs.map +1 -1
  33. package/dist/esm/run.mjs +0 -111
  34. package/dist/esm/run.mjs.map +1 -1
  35. package/dist/esm/summarization/index.mjs +41 -1
  36. package/dist/esm/summarization/index.mjs.map +1 -1
  37. package/dist/esm/summarization/node.mjs +121 -63
  38. package/dist/esm/summarization/node.mjs.map +1 -1
  39. package/dist/esm/tools/ToolNode.mjs +142 -306
  40. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  41. package/dist/types/agents/AgentContext.d.ts +0 -6
  42. package/dist/types/common/enum.d.ts +1 -10
  43. package/dist/types/graphs/Graph.d.ts +0 -2
  44. package/dist/types/graphs/MultiAgentGraph.d.ts +12 -0
  45. package/dist/types/index.d.ts +0 -8
  46. package/dist/types/messages/format.d.ts +1 -2
  47. package/dist/types/run.d.ts +0 -1
  48. package/dist/types/summarization/index.d.ts +2 -0
  49. package/dist/types/summarization/node.d.ts +0 -2
  50. package/dist/types/tools/ToolNode.d.ts +2 -24
  51. package/dist/types/types/graph.d.ts +2 -61
  52. package/dist/types/types/index.d.ts +0 -1
  53. package/dist/types/types/run.d.ts +0 -20
  54. package/dist/types/types/tools.d.ts +1 -38
  55. package/package.json +1 -5
  56. package/src/agents/AgentContext.ts +2 -26
  57. package/src/common/enum.ts +0 -15
  58. package/src/graphs/Graph.ts +0 -113
  59. package/src/graphs/MultiAgentGraph.ts +39 -0
  60. package/src/graphs/__tests__/MultiAgentGraph.test.ts +91 -0
  61. package/src/index.ts +0 -10
  62. package/src/messages/format.ts +4 -74
  63. package/src/run.ts +0 -126
  64. package/src/summarization/__tests__/node.test.ts +42 -0
  65. package/src/summarization/__tests__/trigger.test.ts +100 -1
  66. package/src/summarization/index.ts +47 -0
  67. package/src/summarization/node.ts +149 -77
  68. package/src/tools/ToolNode.ts +169 -391
  69. package/src/tools/__tests__/ToolNode.session.test.ts +12 -12
  70. package/src/types/graph.ts +1 -80
  71. package/src/types/index.ts +0 -1
  72. package/src/types/run.ts +0 -20
  73. package/src/types/tools.ts +1 -41
  74. package/dist/cjs/hooks/HookRegistry.cjs +0 -162
  75. package/dist/cjs/hooks/HookRegistry.cjs.map +0 -1
  76. package/dist/cjs/hooks/executeHooks.cjs +0 -276
  77. package/dist/cjs/hooks/executeHooks.cjs.map +0 -1
  78. package/dist/cjs/hooks/matchers.cjs +0 -256
  79. package/dist/cjs/hooks/matchers.cjs.map +0 -1
  80. package/dist/cjs/hooks/types.cjs +0 -27
  81. package/dist/cjs/hooks/types.cjs.map +0 -1
  82. package/dist/cjs/tools/BashExecutor.cjs +0 -175
  83. package/dist/cjs/tools/BashExecutor.cjs.map +0 -1
  84. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +0 -296
  85. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +0 -1
  86. package/dist/cjs/tools/ReadFile.cjs +0 -43
  87. package/dist/cjs/tools/ReadFile.cjs.map +0 -1
  88. package/dist/cjs/tools/SkillTool.cjs +0 -50
  89. package/dist/cjs/tools/SkillTool.cjs.map +0 -1
  90. package/dist/cjs/tools/SubagentTool.cjs +0 -92
  91. package/dist/cjs/tools/SubagentTool.cjs.map +0 -1
  92. package/dist/cjs/tools/skillCatalog.cjs +0 -84
  93. package/dist/cjs/tools/skillCatalog.cjs.map +0 -1
  94. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +0 -511
  95. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +0 -1
  96. package/dist/esm/hooks/HookRegistry.mjs +0 -160
  97. package/dist/esm/hooks/HookRegistry.mjs.map +0 -1
  98. package/dist/esm/hooks/executeHooks.mjs +0 -273
  99. package/dist/esm/hooks/executeHooks.mjs.map +0 -1
  100. package/dist/esm/hooks/matchers.mjs +0 -251
  101. package/dist/esm/hooks/matchers.mjs.map +0 -1
  102. package/dist/esm/hooks/types.mjs +0 -25
  103. package/dist/esm/hooks/types.mjs.map +0 -1
  104. package/dist/esm/tools/BashExecutor.mjs +0 -169
  105. package/dist/esm/tools/BashExecutor.mjs.map +0 -1
  106. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +0 -287
  107. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +0 -1
  108. package/dist/esm/tools/ReadFile.mjs +0 -38
  109. package/dist/esm/tools/ReadFile.mjs.map +0 -1
  110. package/dist/esm/tools/SkillTool.mjs +0 -45
  111. package/dist/esm/tools/SkillTool.mjs.map +0 -1
  112. package/dist/esm/tools/SubagentTool.mjs +0 -85
  113. package/dist/esm/tools/SubagentTool.mjs.map +0 -1
  114. package/dist/esm/tools/skillCatalog.mjs +0 -82
  115. package/dist/esm/tools/skillCatalog.mjs.map +0 -1
  116. package/dist/esm/tools/subagent/SubagentExecutor.mjs +0 -505
  117. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +0 -1
  118. package/dist/types/hooks/HookRegistry.d.ts +0 -56
  119. package/dist/types/hooks/executeHooks.d.ts +0 -79
  120. package/dist/types/hooks/index.d.ts +0 -6
  121. package/dist/types/hooks/matchers.d.ts +0 -95
  122. package/dist/types/hooks/types.d.ts +0 -320
  123. package/dist/types/tools/BashExecutor.d.ts +0 -45
  124. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +0 -72
  125. package/dist/types/tools/ReadFile.d.ts +0 -28
  126. package/dist/types/tools/SkillTool.d.ts +0 -40
  127. package/dist/types/tools/SubagentTool.d.ts +0 -36
  128. package/dist/types/tools/skillCatalog.d.ts +0 -19
  129. package/dist/types/tools/subagent/SubagentExecutor.d.ts +0 -137
  130. package/dist/types/tools/subagent/index.d.ts +0 -2
  131. package/dist/types/types/skill.d.ts +0 -9
  132. package/src/hooks/HookRegistry.ts +0 -208
  133. package/src/hooks/__tests__/HookRegistry.test.ts +0 -190
  134. package/src/hooks/__tests__/compactHooks.test.ts +0 -214
  135. package/src/hooks/__tests__/executeHooks.test.ts +0 -1013
  136. package/src/hooks/__tests__/integration.test.ts +0 -337
  137. package/src/hooks/__tests__/matchers.test.ts +0 -238
  138. package/src/hooks/__tests__/toolHooks.test.ts +0 -669
  139. package/src/hooks/executeHooks.ts +0 -375
  140. package/src/hooks/index.ts +0 -57
  141. package/src/hooks/matchers.ts +0 -280
  142. package/src/hooks/types.ts +0 -404
  143. package/src/messages/formatAgentMessages.skills.test.ts +0 -334
  144. package/src/scripts/multi-agent-subagent.ts +0 -246
  145. package/src/scripts/subagent-event-driven-debug.ts +0 -190
  146. package/src/scripts/subagent-tools-debug.ts +0 -160
  147. package/src/specs/subagent.test.ts +0 -305
  148. package/src/tools/BashExecutor.ts +0 -205
  149. package/src/tools/BashProgrammaticToolCalling.ts +0 -397
  150. package/src/tools/ReadFile.ts +0 -39
  151. package/src/tools/SkillTool.ts +0 -46
  152. package/src/tools/SubagentTool.ts +0 -100
  153. package/src/tools/__tests__/ReadFile.test.ts +0 -44
  154. package/src/tools/__tests__/SkillTool.test.ts +0 -442
  155. package/src/tools/__tests__/SubagentExecutor.test.ts +0 -1148
  156. package/src/tools/__tests__/SubagentTool.test.ts +0 -149
  157. package/src/tools/__tests__/skillCatalog.test.ts +0 -161
  158. package/src/tools/__tests__/subagentHooks.test.ts +0 -215
  159. package/src/tools/skillCatalog.ts +0 -126
  160. package/src/tools/subagent/SubagentExecutor.ts +0 -676
  161. package/src/tools/subagent/index.ts +0 -13
  162. package/src/types/skill.ts +0 -11
@@ -1,6 +1,6 @@
1
- import { isBaseMessage, ToolMessage, HumanMessage, isAIMessage } from '@langchain/core/messages';
1
+ import { isBaseMessage, ToolMessage, isAIMessage } from '@langchain/core/messages';
2
2
  import { isCommand, isGraphInterrupt, Command, Send, END } from '@langchain/langgraph';
3
- import { Constants, CODE_EXECUTION_TOOLS, GraphEvents } from '../common/enum.mjs';
3
+ import { Constants, GraphEvents } from '../common/enum.mjs';
4
4
  import 'nanoid';
5
5
  import '../messages/core.mjs';
6
6
  import { calculateMaxToolResultChars, truncateToolResultContent } from '../utils/truncation.mjs';
@@ -9,7 +9,6 @@ import 'uuid';
9
9
  import { RunnableCallable } from '../utils/run.mjs';
10
10
  import 'ai-tokenizer';
11
11
  import 'zod-to-json-schema';
12
- import { executeHooks } from '../hooks/executeHooks.mjs';
13
12
 
14
13
  /**
15
14
  * Helper to check if a value is a Send object
@@ -17,32 +16,6 @@ import { executeHooks } from '../hooks/executeHooks.mjs';
17
16
  function isSend(value) {
18
17
  return value instanceof Send;
19
18
  }
20
- /** Merges code execution session context into the sessions map. */
21
- function updateCodeSession(sessions, sessionId, files) {
22
- const newFiles = files ?? [];
23
- const existingSession = sessions.get(Constants.EXECUTE_CODE);
24
- const existingFiles = existingSession?.files ?? [];
25
- if (newFiles.length > 0) {
26
- const filesWithSession = newFiles.map((file) => ({
27
- ...file,
28
- session_id: sessionId,
29
- }));
30
- const newFileNames = new Set(filesWithSession.map((f) => f.name));
31
- const filteredExisting = existingFiles.filter((f) => !newFileNames.has(f.name));
32
- sessions.set(Constants.EXECUTE_CODE, {
33
- session_id: sessionId,
34
- files: [...filteredExisting, ...filesWithSession],
35
- lastUpdated: Date.now(),
36
- });
37
- }
38
- else {
39
- sessions.set(Constants.EXECUTE_CODE, {
40
- session_id: sessionId,
41
- files: existingFiles,
42
- lastUpdated: Date.now(),
43
- });
44
- }
45
- }
46
19
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
20
  class ToolNode extends RunnableCallable {
48
21
  toolMap;
@@ -68,9 +41,7 @@ class ToolNode extends RunnableCallable {
68
41
  directToolNames;
69
42
  /** Maximum characters allowed in a single tool result before truncation. */
70
43
  maxToolResultChars;
71
- /** Hook registry for PreToolUse/PostToolUse lifecycle hooks */
72
- hookRegistry;
73
- constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, agentId, directToolNames, maxContextTokens, maxToolResultChars, hookRegistry, }) {
44
+ constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, agentId, directToolNames, maxContextTokens, maxToolResultChars, }) {
74
45
  super({ name, tags, func: (input, config) => this.run(input, config) });
75
46
  this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
76
47
  this.toolCallStepIds = toolCallStepIds;
@@ -85,7 +56,6 @@ class ToolNode extends RunnableCallable {
85
56
  this.directToolNames = directToolNames;
86
57
  this.maxToolResultChars =
87
58
  maxToolResultChars ?? calculateMaxToolResultChars(maxContextTokens);
88
- this.hookRegistry = hookRegistry;
89
59
  }
90
60
  /**
91
61
  * Returns cached programmatic tools, computing once on first access.
@@ -141,8 +111,7 @@ class ToolNode extends RunnableCallable {
141
111
  turn,
142
112
  };
143
113
  // Inject runtime data for special tools (becomes available at config.toolCall)
144
- if (call.name === Constants.PROGRAMMATIC_TOOL_CALLING ||
145
- call.name === Constants.BASH_PROGRAMMATIC_TOOL_CALLING) {
114
+ if (call.name === Constants.PROGRAMMATIC_TOOL_CALLING) {
146
115
  const { toolMap, toolDefs } = this.getProgrammaticTools();
147
116
  invokeParams = {
148
117
  ...invokeParams,
@@ -165,7 +134,8 @@ class ToolNode extends RunnableCallable {
165
134
  * session_id is always injected when available (even without tracked files)
166
135
  * so the CodeExecutor can fall back to the /files endpoint for session continuity.
167
136
  */
168
- if (CODE_EXECUTION_TOOLS.has(call.name)) {
137
+ if (call.name === Constants.EXECUTE_CODE ||
138
+ call.name === Constants.PROGRAMMATIC_TOOL_CALLING) {
169
139
  const codeSession = this.sessions?.get(Constants.EXECUTE_CODE);
170
140
  if (codeSession?.session_id != null && codeSession.session_id !== '') {
171
141
  invokeParams = {
@@ -274,7 +244,7 @@ class ToolNode extends RunnableCallable {
274
244
  * Extracts code execution session context from tool results and stores in Graph.sessions.
275
245
  * Mirrors the session storage logic in handleRunToolCompletions for direct execution.
276
246
  */
277
- storeCodeSessionFromResults(results, requestMap) {
247
+ storeCodeSessionFromResults(results, requests) {
278
248
  if (!this.sessions) {
279
249
  return;
280
250
  }
@@ -283,17 +253,38 @@ class ToolNode extends RunnableCallable {
283
253
  if (result.status !== 'success' || result.artifact == null) {
284
254
  continue;
285
255
  }
286
- const request = requestMap.get(result.toolCallId);
287
- if (!request?.name ||
288
- (!CODE_EXECUTION_TOOLS.has(request.name) &&
289
- request.name !== Constants.SKILL_TOOL)) {
256
+ const request = requests.find((r) => r.id === result.toolCallId);
257
+ if (request?.name !== Constants.EXECUTE_CODE &&
258
+ request?.name !== Constants.PROGRAMMATIC_TOOL_CALLING) {
290
259
  continue;
291
260
  }
292
261
  const artifact = result.artifact;
293
262
  if (artifact?.session_id == null || artifact.session_id === '') {
294
263
  continue;
295
264
  }
296
- updateCodeSession(this.sessions, artifact.session_id, artifact.files);
265
+ const newFiles = artifact.files ?? [];
266
+ const existingSession = this.sessions.get(Constants.EXECUTE_CODE);
267
+ const existingFiles = existingSession?.files ?? [];
268
+ if (newFiles.length > 0) {
269
+ const filesWithSession = newFiles.map((file) => ({
270
+ ...file,
271
+ session_id: artifact.session_id,
272
+ }));
273
+ const newFileNames = new Set(filesWithSession.map((f) => f.name));
274
+ const filteredExisting = existingFiles.filter((f) => !newFileNames.has(f.name));
275
+ this.sessions.set(Constants.EXECUTE_CODE, {
276
+ session_id: artifact.session_id,
277
+ files: [...filteredExisting, ...filesWithSession],
278
+ lastUpdated: Date.now(),
279
+ });
280
+ }
281
+ else {
282
+ this.sessions.set(Constants.EXECUTE_CODE, {
283
+ session_id: artifact.session_id,
284
+ files: existingFiles,
285
+ lastUpdated: Date.now(),
286
+ });
287
+ }
297
288
  }
298
289
  }
299
290
  /**
@@ -320,10 +311,35 @@ class ToolNode extends RunnableCallable {
320
311
  if (toolMessage.status === 'error' && this.errorHandler != null) {
321
312
  continue;
322
313
  }
323
- if (this.sessions && CODE_EXECUTION_TOOLS.has(call.name)) {
314
+ // Store code session context from tool results
315
+ if (this.sessions &&
316
+ (call.name === Constants.EXECUTE_CODE ||
317
+ call.name === Constants.PROGRAMMATIC_TOOL_CALLING)) {
324
318
  const artifact = toolMessage.artifact;
325
319
  if (artifact?.session_id != null && artifact.session_id !== '') {
326
- updateCodeSession(this.sessions, artifact.session_id, artifact.files);
320
+ const newFiles = artifact.files ?? [];
321
+ const existingSession = this.sessions.get(Constants.EXECUTE_CODE);
322
+ const existingFiles = existingSession?.files ?? [];
323
+ if (newFiles.length > 0) {
324
+ const filesWithSession = newFiles.map((file) => ({
325
+ ...file,
326
+ session_id: artifact.session_id,
327
+ }));
328
+ const newFileNames = new Set(filesWithSession.map((f) => f.name));
329
+ const filteredExisting = existingFiles.filter((f) => !newFileNames.has(f.name));
330
+ this.sessions.set(Constants.EXECUTE_CODE, {
331
+ session_id: artifact.session_id,
332
+ files: [...filteredExisting, ...filesWithSession],
333
+ lastUpdated: Date.now(),
334
+ });
335
+ }
336
+ else {
337
+ this.sessions.set(Constants.EXECUTE_CODE, {
338
+ session_id: artifact.session_id,
339
+ files: existingFiles,
340
+ lastUpdated: Date.now(),
341
+ });
342
+ }
327
343
  }
328
344
  }
329
345
  // Dispatch ON_RUN_STEP_COMPLETED via custom event (same path as dispatchToolEvents)
@@ -356,273 +372,100 @@ class ToolNode extends RunnableCallable {
356
372
  /**
357
373
  * Dispatches tool calls to the host via ON_TOOL_EXECUTE event and returns raw ToolMessages.
358
374
  * Core logic for event-driven execution, separated from output shaping.
359
- *
360
- * Hook lifecycle (when `hookRegistry` is set):
361
- * 1. **PreToolUse** fires per call in parallel before dispatch. Denied
362
- * calls produce error ToolMessages and fire **PermissionDenied**;
363
- * surviving calls proceed with optional `updatedInput`.
364
- * 2. Surviving calls are dispatched to the host via `ON_TOOL_EXECUTE`.
365
- * 3. **PostToolUse** / **PostToolUseFailure** fire per result. Post hooks
366
- * can replace tool output via `updatedOutput`.
367
- * 4. Injected messages from results are collected and returned alongside
368
- * ToolMessages (appended AFTER to respect provider ordering).
369
375
  */
370
376
  async dispatchToolEvents(toolCalls, config) {
371
- const runId = config.configurable?.run_id ?? '';
372
- const threadId = config.configurable?.thread_id;
373
- const preToolCalls = toolCalls.map((call) => ({
374
- call,
375
- stepId: this.toolCallStepIds?.get(call.id) ?? '',
376
- args: call.args,
377
- }));
378
- const messageByCallId = new Map();
379
- const approvedEntries = [];
380
- const HOOK_FALLBACK = Object.freeze({
381
- additionalContexts: [],
382
- errors: [],
377
+ const requests = toolCalls.map((call) => {
378
+ const turn = this.toolUsageCount.get(call.name) ?? 0;
379
+ this.toolUsageCount.set(call.name, turn + 1);
380
+ const request = {
381
+ id: call.id,
382
+ name: call.name,
383
+ args: call.args,
384
+ stepId: this.toolCallStepIds?.get(call.id),
385
+ turn,
386
+ };
387
+ if (call.name === Constants.EXECUTE_CODE ||
388
+ call.name === Constants.PROGRAMMATIC_TOOL_CALLING) {
389
+ request.codeSessionContext = this.getCodeSessionContext();
390
+ }
391
+ return request;
383
392
  });
384
- if (this.hookRegistry?.hasHookFor('PreToolUse', runId) === true) {
385
- const preResults = await Promise.all(preToolCalls.map((entry) => executeHooks({
386
- registry: this.hookRegistry,
387
- input: {
388
- hook_event_name: 'PreToolUse',
389
- runId,
390
- threadId,
391
- agentId: this.agentId,
392
- toolName: entry.call.name,
393
- toolInput: entry.args,
394
- toolUseId: entry.call.id,
395
- stepId: entry.stepId,
396
- turn: this.toolUsageCount.get(entry.call.name) ?? 0,
397
- },
398
- sessionId: runId,
399
- matchQuery: entry.call.name,
400
- }).catch(() => HOOK_FALLBACK)));
401
- for (let i = 0; i < preToolCalls.length; i++) {
402
- const hookResult = preResults[i];
403
- const entry = preToolCalls[i];
404
- const isDenied = hookResult.decision === 'deny' || hookResult.decision === 'ask';
405
- if (isDenied) {
406
- const reason = hookResult.reason ?? 'Blocked by hook';
407
- const contentString = `Blocked: ${reason}`;
408
- messageByCallId.set(entry.call.id, new ToolMessage({
409
- status: 'error',
410
- content: contentString,
411
- name: entry.call.name,
412
- tool_call_id: entry.call.id,
413
- }));
414
- this.dispatchStepCompleted(entry.call.id, entry.call.name, entry.args, contentString, config);
415
- if (this.hookRegistry.hasHookFor('PermissionDenied', runId)) {
416
- executeHooks({
417
- registry: this.hookRegistry,
418
- input: {
419
- hook_event_name: 'PermissionDenied',
420
- runId,
421
- threadId,
422
- agentId: this.agentId,
423
- toolName: entry.call.name,
424
- toolInput: entry.args,
425
- toolUseId: entry.call.id,
426
- reason,
427
- },
428
- sessionId: runId,
429
- matchQuery: entry.call.name,
430
- }).catch(() => {
431
- /* PermissionDenied is observational — swallow errors */
432
- });
433
- }
434
- continue;
435
- }
436
- if (hookResult.updatedInput != null) {
437
- entry.args = hookResult.updatedInput;
438
- }
439
- approvedEntries.push(entry);
393
+ const results = await new Promise((resolve, reject) => {
394
+ const request = {
395
+ toolCalls: requests,
396
+ userId: config.configurable?.user_id,
397
+ agentId: this.agentId,
398
+ configurable: config.configurable,
399
+ metadata: config.metadata,
400
+ resolve,
401
+ reject,
402
+ };
403
+ safeDispatchCustomEvent(GraphEvents.ON_TOOL_EXECUTE, request, config);
404
+ });
405
+ this.storeCodeSessionFromResults(results, requests);
406
+ return results.map((result) => {
407
+ const request = requests.find((r) => r.id === result.toolCallId);
408
+ const toolName = request?.name ?? 'unknown';
409
+ const stepId = this.toolCallStepIds?.get(result.toolCallId) ?? '';
410
+ if (!stepId) {
411
+ // eslint-disable-next-line no-console
412
+ console.warn(`[ToolNode] toolCallStepIds missing entry for toolCallId=${result.toolCallId} (tool=${toolName}). ` +
413
+ 'This indicates a race between the stream consumer and graph execution. ' +
414
+ `Map size: ${this.toolCallStepIds?.size ?? 0}`);
440
415
  }
441
- }
442
- else {
443
- approvedEntries.push(...preToolCalls);
444
- }
445
- const injected = [];
446
- if (approvedEntries.length > 0) {
447
- const requests = approvedEntries.map((entry) => {
448
- const turn = this.toolUsageCount.get(entry.call.name) ?? 0;
449
- this.toolUsageCount.set(entry.call.name, turn + 1);
450
- const request = {
451
- id: entry.call.id,
452
- name: entry.call.name,
453
- args: entry.args,
454
- stepId: entry.stepId,
455
- turn,
456
- };
457
- if (CODE_EXECUTION_TOOLS.has(entry.call.name) ||
458
- entry.call.name === Constants.SKILL_TOOL) {
459
- request.codeSessionContext = this.getCodeSessionContext();
460
- }
461
- return request;
462
- });
463
- const requestMap = new Map(requests.map((r) => [r.id, r]));
464
- const results = await new Promise((resolve, reject) => {
465
- const batchRequest = {
466
- toolCalls: requests,
467
- userId: config.configurable?.user_id,
468
- agentId: this.agentId,
469
- configurable: config.configurable,
470
- metadata: config.metadata,
471
- resolve,
472
- reject,
473
- };
474
- safeDispatchCustomEvent(GraphEvents.ON_TOOL_EXECUTE, batchRequest, config);
475
- });
476
- this.storeCodeSessionFromResults(results, requestMap);
477
- const hasPostHook = this.hookRegistry?.hasHookFor('PostToolUse', runId) === true;
478
- const hasFailureHook = this.hookRegistry?.hasHookFor('PostToolUseFailure', runId) === true;
479
- for (const result of results) {
480
- if (result.injectedMessages && result.injectedMessages.length > 0) {
481
- try {
482
- injected.push(...this.convertInjectedMessages(result.injectedMessages));
483
- }
484
- catch (e) {
485
- // eslint-disable-next-line no-console
486
- console.warn(`[ToolNode] Failed to convert injectedMessages for toolCallId=${result.toolCallId}:`, e instanceof Error ? e.message : e);
487
- }
488
- }
489
- const request = requestMap.get(result.toolCallId);
490
- const toolName = request?.name ?? 'unknown';
491
- let contentString;
492
- let toolMessage;
493
- if (result.status === 'error') {
494
- contentString = `Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`;
495
- toolMessage = new ToolMessage({
496
- status: 'error',
497
- content: contentString,
498
- name: toolName,
499
- tool_call_id: result.toolCallId,
500
- });
501
- if (hasFailureHook) {
502
- await executeHooks({
503
- registry: this.hookRegistry,
504
- input: {
505
- hook_event_name: 'PostToolUseFailure',
506
- runId,
507
- threadId,
508
- agentId: this.agentId,
509
- toolName,
510
- toolInput: request?.args ?? {},
511
- toolUseId: result.toolCallId,
512
- error: result.errorMessage ?? 'Unknown error',
513
- stepId: request?.stepId,
514
- turn: request?.turn,
515
- },
516
- sessionId: runId,
517
- matchQuery: toolName,
518
- }).catch(() => {
519
- /* PostToolUseFailure is observational — swallow errors */
520
- });
521
- }
522
- }
523
- else {
524
- const rawContent = typeof result.content === 'string'
525
- ? result.content
526
- : JSON.stringify(result.content);
527
- contentString = truncateToolResultContent(rawContent, this.maxToolResultChars);
528
- if (hasPostHook) {
529
- const hookResult = await executeHooks({
530
- registry: this.hookRegistry,
531
- input: {
532
- hook_event_name: 'PostToolUse',
533
- runId,
534
- threadId,
535
- agentId: this.agentId,
536
- toolName,
537
- toolInput: request?.args ?? {},
538
- toolOutput: result.content,
539
- toolUseId: result.toolCallId,
540
- stepId: request?.stepId,
541
- turn: request?.turn,
542
- },
543
- sessionId: runId,
544
- matchQuery: toolName,
545
- }).catch(() => undefined);
546
- if (hookResult?.updatedOutput != null) {
547
- const replaced = typeof hookResult.updatedOutput === 'string'
548
- ? hookResult.updatedOutput
549
- : JSON.stringify(hookResult.updatedOutput);
550
- contentString = truncateToolResultContent(replaced, this.maxToolResultChars);
551
- }
552
- }
553
- toolMessage = new ToolMessage({
554
- status: 'success',
555
- name: toolName,
556
- content: contentString,
557
- artifact: result.artifact,
558
- tool_call_id: result.toolCallId,
559
- });
560
- }
561
- this.dispatchStepCompleted(result.toolCallId, toolName, request?.args ?? {}, contentString, config, request?.turn);
562
- messageByCallId.set(result.toolCallId, toolMessage);
416
+ let toolMessage;
417
+ let contentString;
418
+ if (result.status === 'error') {
419
+ contentString = `Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`;
420
+ toolMessage = new ToolMessage({
421
+ status: 'error',
422
+ content: contentString,
423
+ name: toolName,
424
+ tool_call_id: result.toolCallId,
425
+ });
563
426
  }
564
- }
565
- const toolMessages = toolCalls
566
- .map((call) => messageByCallId.get(call.id))
567
- .filter((m) => m != null);
568
- return { toolMessages, injected };
569
- }
570
- dispatchStepCompleted(toolCallId, toolName, args, output, config, turn) {
571
- const stepId = this.toolCallStepIds?.get(toolCallId) ?? '';
572
- if (!stepId) {
573
- // eslint-disable-next-line no-console
574
- console.warn(`[ToolNode] toolCallStepIds missing entry for toolCallId=${toolCallId} (tool=${toolName}). ` +
575
- 'This indicates a race between the stream consumer and graph execution. ' +
576
- `Map size: ${this.toolCallStepIds?.size ?? 0}`);
577
- }
578
- safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP_COMPLETED, {
579
- result: {
580
- id: stepId,
581
- index: turn ?? this.toolUsageCount.get(toolName) ?? 0,
582
- type: 'tool_call',
583
- tool_call: {
584
- args: JSON.stringify(args),
427
+ else {
428
+ const rawContent = typeof result.content === 'string'
429
+ ? result.content
430
+ : JSON.stringify(result.content);
431
+ contentString = truncateToolResultContent(rawContent, this.maxToolResultChars);
432
+ toolMessage = new ToolMessage({
433
+ status: 'success',
585
434
  name: toolName,
586
- id: toolCallId,
587
- output,
588
- progress: 1,
435
+ content: contentString,
436
+ artifact: result.artifact,
437
+ tool_call_id: result.toolCallId,
438
+ });
439
+ }
440
+ const tool_call = {
441
+ args: typeof request?.args === 'string'
442
+ ? request.args
443
+ : JSON.stringify(request?.args ?? {}),
444
+ name: toolName,
445
+ id: result.toolCallId,
446
+ output: contentString,
447
+ progress: 1,
448
+ };
449
+ const runStepCompletedData = {
450
+ result: {
451
+ id: stepId,
452
+ index: request?.turn ?? 0,
453
+ type: 'tool_call',
454
+ tool_call,
589
455
  },
590
- },
591
- }, config);
592
- }
593
- /**
594
- * Converts InjectedMessage instances to LangChain HumanMessage objects.
595
- * Both 'user' and 'system' roles become HumanMessage to avoid provider
596
- * rejections (Anthropic/Google reject non-leading SystemMessages).
597
- * The original role is preserved in additional_kwargs for downstream consumers.
598
- */
599
- convertInjectedMessages(messages) {
600
- const converted = [];
601
- for (const msg of messages) {
602
- const additional_kwargs = {
603
- role: msg.role,
604
456
  };
605
- if (msg.isMeta != null)
606
- additional_kwargs.isMeta = msg.isMeta;
607
- if (msg.source != null)
608
- additional_kwargs.source = msg.source;
609
- if (msg.skillName != null)
610
- additional_kwargs.skillName = msg.skillName;
611
- converted.push(new HumanMessage({ content: msg.content, additional_kwargs }));
612
- }
613
- return converted;
457
+ safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP_COMPLETED, runStepCompletedData, config);
458
+ return toolMessage;
459
+ });
614
460
  }
615
461
  /**
616
462
  * Execute all tool calls via ON_TOOL_EXECUTE event dispatch.
617
- * Injected messages are placed AFTER ToolMessages to respect provider
618
- * message ordering (AIMessage tool_calls must be immediately followed
619
- * by their ToolMessage results).
463
+ * Used in event-driven mode where the host handles actual tool execution.
620
464
  */
621
465
  async executeViaEvent(toolCalls, config,
622
466
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
623
467
  input) {
624
- const { toolMessages, injected } = await this.dispatchToolEvents(toolCalls, config);
625
- const outputs = [...toolMessages, ...injected];
468
+ const outputs = await this.dispatchToolEvents(toolCalls, config);
626
469
  return (Array.isArray(input) ? outputs : { messages: outputs });
627
470
  }
628
471
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -691,17 +534,10 @@ class ToolNode extends RunnableCallable {
691
534
  if (directCalls.length > 0 && directOutputs.length > 0) {
692
535
  this.handleRunToolCompletions(directCalls, directOutputs, config);
693
536
  }
694
- const eventResult = eventCalls.length > 0
537
+ const eventOutputs = eventCalls.length > 0
695
538
  ? await this.dispatchToolEvents(eventCalls, config)
696
- : {
697
- toolMessages: [],
698
- injected: [],
699
- };
700
- outputs = [
701
- ...directOutputs,
702
- ...eventResult.toolMessages,
703
- ...eventResult.injected,
704
- ];
539
+ : [];
540
+ outputs = [...directOutputs, ...eventOutputs];
705
541
  }
706
542
  else {
707
543
  outputs = await Promise.all(filteredCalls.map((call) => this.runTool(call, config)));