@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,7 +1,6 @@
1
1
  import { ToolCall } from '@langchain/core/messages/tool';
2
2
  import {
3
3
  ToolMessage,
4
- HumanMessage,
5
4
  isAIMessage,
6
5
  isBaseMessage,
7
6
  } from '@langchain/core/messages';
@@ -20,15 +19,13 @@ import type {
20
19
  import type { BaseMessage, AIMessage } from '@langchain/core/messages';
21
20
  import type { StructuredToolInterface } from '@langchain/core/tools';
22
21
  import type * as t from '@/types';
23
- import type { HookRegistry, AggregatedHookResult } from '@/hooks';
24
22
  import { RunnableCallable } from '@/utils';
25
23
  import {
26
24
  calculateMaxToolResultChars,
27
25
  truncateToolResultContent,
28
26
  } from '@/utils/truncation';
29
27
  import { safeDispatchCustomEvent } from '@/utils/events';
30
- import { executeHooks } from '@/hooks';
31
- import { Constants, GraphEvents, CODE_EXECUTION_TOOLS } from '@/common';
28
+ import { Constants, GraphEvents } from '@/common';
32
29
 
33
30
  /**
34
31
  * Helper to check if a value is a Send object
@@ -37,41 +34,6 @@ function isSend(value: unknown): value is Send {
37
34
  return value instanceof Send;
38
35
  }
39
36
 
40
- /** Merges code execution session context into the sessions map. */
41
- function updateCodeSession(
42
- sessions: t.ToolSessionMap,
43
- sessionId: string,
44
- files: t.FileRefs | undefined
45
- ): void {
46
- const newFiles = files ?? [];
47
- const existingSession = sessions.get(Constants.EXECUTE_CODE) as
48
- | t.CodeSessionContext
49
- | undefined;
50
- const existingFiles = existingSession?.files ?? [];
51
-
52
- if (newFiles.length > 0) {
53
- const filesWithSession: t.FileRefs = newFiles.map((file) => ({
54
- ...file,
55
- session_id: sessionId,
56
- }));
57
- const newFileNames = new Set(filesWithSession.map((f) => f.name));
58
- const filteredExisting = existingFiles.filter(
59
- (f) => !newFileNames.has(f.name)
60
- );
61
- sessions.set(Constants.EXECUTE_CODE, {
62
- session_id: sessionId,
63
- files: [...filteredExisting, ...filesWithSession],
64
- lastUpdated: Date.now(),
65
- });
66
- } else {
67
- sessions.set(Constants.EXECUTE_CODE, {
68
- session_id: sessionId,
69
- files: existingFiles,
70
- lastUpdated: Date.now(),
71
- });
72
- }
73
- }
74
-
75
37
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
38
  export class ToolNode<T = any> extends RunnableCallable<T, T> {
77
39
  private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;
@@ -97,8 +59,6 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
97
59
  private directToolNames?: Set<string>;
98
60
  /** Maximum characters allowed in a single tool result before truncation. */
99
61
  private maxToolResultChars: number;
100
- /** Hook registry for PreToolUse/PostToolUse lifecycle hooks */
101
- private hookRegistry?: HookRegistry;
102
62
 
103
63
  constructor({
104
64
  tools,
@@ -116,7 +76,6 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
116
76
  directToolNames,
117
77
  maxContextTokens,
118
78
  maxToolResultChars,
119
- hookRegistry,
120
79
  }: t.ToolNodeConstructorParams) {
121
80
  super({ name, tags, func: (input, config) => this.run(input, config) });
122
81
  this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
@@ -132,7 +91,6 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
132
91
  this.directToolNames = directToolNames;
133
92
  this.maxToolResultChars =
134
93
  maxToolResultChars ?? calculateMaxToolResultChars(maxContextTokens);
135
- this.hookRegistry = hookRegistry;
136
94
  }
137
95
 
138
96
  /**
@@ -199,10 +157,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
199
157
  };
200
158
 
201
159
  // Inject runtime data for special tools (becomes available at config.toolCall)
202
- if (
203
- call.name === Constants.PROGRAMMATIC_TOOL_CALLING ||
204
- call.name === Constants.BASH_PROGRAMMATIC_TOOL_CALLING
205
- ) {
160
+ if (call.name === Constants.PROGRAMMATIC_TOOL_CALLING) {
206
161
  const { toolMap, toolDefs } = this.getProgrammaticTools();
207
162
  invokeParams = {
208
163
  ...invokeParams,
@@ -225,7 +180,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
225
180
  * session_id is always injected when available (even without tracked files)
226
181
  * so the CodeExecutor can fall back to the /files endpoint for session continuity.
227
182
  */
228
- if (CODE_EXECUTION_TOOLS.has(call.name)) {
183
+ if (
184
+ call.name === Constants.EXECUTE_CODE ||
185
+ call.name === Constants.PROGRAMMATIC_TOOL_CALLING
186
+ ) {
229
187
  const codeSession = this.sessions?.get(Constants.EXECUTE_CODE) as
230
188
  | t.CodeSessionContext
231
189
  | undefined;
@@ -355,7 +313,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
355
313
  */
356
314
  private storeCodeSessionFromResults(
357
315
  results: t.ToolExecuteResult[],
358
- requestMap: Map<string, t.ToolCallRequest>
316
+ requests: t.ToolCallRequest[]
359
317
  ): void {
360
318
  if (!this.sessions) {
361
319
  return;
@@ -367,11 +325,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
367
325
  continue;
368
326
  }
369
327
 
370
- const request = requestMap.get(result.toolCallId);
328
+ const request = requests.find((r) => r.id === result.toolCallId);
371
329
  if (
372
- !request?.name ||
373
- (!CODE_EXECUTION_TOOLS.has(request.name) &&
374
- request.name !== Constants.SKILL_TOOL)
330
+ request?.name !== Constants.EXECUTE_CODE &&
331
+ request?.name !== Constants.PROGRAMMATIC_TOOL_CALLING
375
332
  ) {
376
333
  continue;
377
334
  }
@@ -381,7 +338,35 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
381
338
  continue;
382
339
  }
383
340
 
384
- updateCodeSession(this.sessions, artifact.session_id!, artifact.files);
341
+ const newFiles = artifact.files ?? [];
342
+ const existingSession = this.sessions.get(Constants.EXECUTE_CODE) as
343
+ | t.CodeSessionContext
344
+ | undefined;
345
+ const existingFiles = existingSession?.files ?? [];
346
+
347
+ if (newFiles.length > 0) {
348
+ const filesWithSession: t.FileRefs = newFiles.map((file) => ({
349
+ ...file,
350
+ session_id: artifact.session_id,
351
+ }));
352
+
353
+ const newFileNames = new Set(filesWithSession.map((f) => f.name));
354
+ const filteredExisting = existingFiles.filter(
355
+ (f) => !newFileNames.has(f.name)
356
+ );
357
+
358
+ this.sessions.set(Constants.EXECUTE_CODE, {
359
+ session_id: artifact.session_id,
360
+ files: [...filteredExisting, ...filesWithSession],
361
+ lastUpdated: Date.now(),
362
+ });
363
+ } else {
364
+ this.sessions.set(Constants.EXECUTE_CODE, {
365
+ session_id: artifact.session_id,
366
+ files: existingFiles,
367
+ lastUpdated: Date.now(),
368
+ });
369
+ }
385
370
  }
386
371
  }
387
372
 
@@ -417,12 +402,43 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
417
402
  continue;
418
403
  }
419
404
 
420
- if (this.sessions && CODE_EXECUTION_TOOLS.has(call.name)) {
405
+ // Store code session context from tool results
406
+ if (
407
+ this.sessions &&
408
+ (call.name === Constants.EXECUTE_CODE ||
409
+ call.name === Constants.PROGRAMMATIC_TOOL_CALLING)
410
+ ) {
421
411
  const artifact = toolMessage.artifact as
422
412
  | t.CodeExecutionArtifact
423
413
  | undefined;
424
414
  if (artifact?.session_id != null && artifact.session_id !== '') {
425
- updateCodeSession(this.sessions, artifact.session_id, artifact.files);
415
+ const newFiles = artifact.files ?? [];
416
+ const existingSession = this.sessions.get(Constants.EXECUTE_CODE) as
417
+ | t.CodeSessionContext
418
+ | undefined;
419
+ const existingFiles = existingSession?.files ?? [];
420
+
421
+ if (newFiles.length > 0) {
422
+ const filesWithSession: t.FileRefs = newFiles.map((file) => ({
423
+ ...file,
424
+ session_id: artifact.session_id,
425
+ }));
426
+ const newFileNames = new Set(filesWithSession.map((f) => f.name));
427
+ const filteredExisting = existingFiles.filter(
428
+ (f) => !newFileNames.has(f.name)
429
+ );
430
+ this.sessions.set(Constants.EXECUTE_CODE, {
431
+ session_id: artifact.session_id,
432
+ files: [...filteredExisting, ...filesWithSession],
433
+ lastUpdated: Date.now(),
434
+ });
435
+ } else {
436
+ this.sessions.set(Constants.EXECUTE_CODE, {
437
+ session_id: artifact.session_id,
438
+ files: existingFiles,
439
+ lastUpdated: Date.now(),
440
+ });
441
+ }
426
442
  }
427
443
  }
428
444
 
@@ -466,355 +482,128 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
466
482
  /**
467
483
  * Dispatches tool calls to the host via ON_TOOL_EXECUTE event and returns raw ToolMessages.
468
484
  * Core logic for event-driven execution, separated from output shaping.
469
- *
470
- * Hook lifecycle (when `hookRegistry` is set):
471
- * 1. **PreToolUse** fires per call in parallel before dispatch. Denied
472
- * calls produce error ToolMessages and fire **PermissionDenied**;
473
- * surviving calls proceed with optional `updatedInput`.
474
- * 2. Surviving calls are dispatched to the host via `ON_TOOL_EXECUTE`.
475
- * 3. **PostToolUse** / **PostToolUseFailure** fire per result. Post hooks
476
- * can replace tool output via `updatedOutput`.
477
- * 4. Injected messages from results are collected and returned alongside
478
- * ToolMessages (appended AFTER to respect provider ordering).
479
485
  */
480
486
  private async dispatchToolEvents(
481
487
  toolCalls: ToolCall[],
482
488
  config: RunnableConfig
483
- ): Promise<{ toolMessages: ToolMessage[]; injected: BaseMessage[] }> {
484
- const runId = (config.configurable?.run_id as string | undefined) ?? '';
485
- const threadId = config.configurable?.thread_id as string | undefined;
486
-
487
- const preToolCalls = toolCalls.map((call) => ({
488
- call,
489
- stepId: this.toolCallStepIds?.get(call.id!) ?? '',
490
- args: call.args as Record<string, unknown>,
491
- }));
492
-
493
- const messageByCallId = new Map<string, ToolMessage>();
494
- const approvedEntries: typeof preToolCalls = [];
495
- const HOOK_FALLBACK: AggregatedHookResult = Object.freeze({
496
- additionalContexts: [] as string[],
497
- errors: [] as string[],
498
- });
489
+ ): Promise<ToolMessage[]> {
490
+ const requests: t.ToolCallRequest[] = toolCalls.map((call) => {
491
+ const turn = this.toolUsageCount.get(call.name) ?? 0;
492
+ this.toolUsageCount.set(call.name, turn + 1);
499
493
 
500
- if (this.hookRegistry?.hasHookFor('PreToolUse', runId) === true) {
501
- const preResults = await Promise.all(
502
- preToolCalls.map((entry) =>
503
- executeHooks({
504
- registry: this.hookRegistry!,
505
- input: {
506
- hook_event_name: 'PreToolUse',
507
- runId,
508
- threadId,
509
- agentId: this.agentId,
510
- toolName: entry.call.name,
511
- toolInput: entry.args,
512
- toolUseId: entry.call.id!,
513
- stepId: entry.stepId,
514
- turn: this.toolUsageCount.get(entry.call.name) ?? 0,
515
- },
516
- sessionId: runId,
517
- matchQuery: entry.call.name,
518
- }).catch((): AggregatedHookResult => HOOK_FALLBACK)
519
- )
520
- );
494
+ const request: t.ToolCallRequest = {
495
+ id: call.id!,
496
+ name: call.name,
497
+ args: call.args as Record<string, unknown>,
498
+ stepId: this.toolCallStepIds?.get(call.id!),
499
+ turn,
500
+ };
521
501
 
522
- for (let i = 0; i < preToolCalls.length; i++) {
523
- const hookResult = preResults[i];
524
- const entry = preToolCalls[i];
525
- const isDenied =
526
- hookResult.decision === 'deny' || hookResult.decision === 'ask';
527
- if (isDenied) {
528
- const reason = hookResult.reason ?? 'Blocked by hook';
529
- const contentString = `Blocked: ${reason}`;
530
- messageByCallId.set(
531
- entry.call.id!,
532
- new ToolMessage({
533
- status: 'error',
534
- content: contentString,
535
- name: entry.call.name,
536
- tool_call_id: entry.call.id!,
537
- })
538
- );
539
- this.dispatchStepCompleted(
540
- entry.call.id!,
541
- entry.call.name,
542
- entry.args,
543
- contentString,
544
- config
545
- );
546
- if (this.hookRegistry.hasHookFor('PermissionDenied', runId)) {
547
- executeHooks({
548
- registry: this.hookRegistry,
549
- input: {
550
- hook_event_name: 'PermissionDenied',
551
- runId,
552
- threadId,
553
- agentId: this.agentId,
554
- toolName: entry.call.name,
555
- toolInput: entry.args,
556
- toolUseId: entry.call.id!,
557
- reason,
558
- },
559
- sessionId: runId,
560
- matchQuery: entry.call.name,
561
- }).catch(() => {
562
- /* PermissionDenied is observational — swallow errors */
563
- });
564
- }
565
- continue;
566
- }
567
- if (hookResult.updatedInput != null) {
568
- entry.args = hookResult.updatedInput;
569
- }
570
- approvedEntries.push(entry);
502
+ if (
503
+ call.name === Constants.EXECUTE_CODE ||
504
+ call.name === Constants.PROGRAMMATIC_TOOL_CALLING
505
+ ) {
506
+ request.codeSessionContext = this.getCodeSessionContext();
571
507
  }
572
- } else {
573
- approvedEntries.push(...preToolCalls);
574
- }
575
-
576
- const injected: BaseMessage[] = [];
577
508
 
578
- if (approvedEntries.length > 0) {
579
- const requests: t.ToolCallRequest[] = approvedEntries.map((entry) => {
580
- const turn = this.toolUsageCount.get(entry.call.name) ?? 0;
581
- this.toolUsageCount.set(entry.call.name, turn + 1);
509
+ return request;
510
+ });
582
511
 
583
- const request: t.ToolCallRequest = {
584
- id: entry.call.id!,
585
- name: entry.call.name,
586
- args: entry.args,
587
- stepId: entry.stepId,
588
- turn,
512
+ const results = await new Promise<t.ToolExecuteResult[]>(
513
+ (resolve, reject) => {
514
+ const request: t.ToolExecuteBatchRequest = {
515
+ toolCalls: requests,
516
+ userId: config.configurable?.user_id as string | undefined,
517
+ agentId: this.agentId,
518
+ configurable: config.configurable as
519
+ | Record<string, unknown>
520
+ | undefined,
521
+ metadata: config.metadata as Record<string, unknown> | undefined,
522
+ resolve,
523
+ reject,
589
524
  };
590
525
 
591
- if (
592
- CODE_EXECUTION_TOOLS.has(entry.call.name) ||
593
- entry.call.name === Constants.SKILL_TOOL
594
- ) {
595
- request.codeSessionContext = this.getCodeSessionContext();
596
- }
597
-
598
- return request;
599
- });
600
-
601
- const requestMap = new Map(requests.map((r) => [r.id, r]));
602
-
603
- const results = await new Promise<t.ToolExecuteResult[]>(
604
- (resolve, reject) => {
605
- const batchRequest: t.ToolExecuteBatchRequest = {
606
- toolCalls: requests,
607
- userId: config.configurable?.user_id as string | undefined,
608
- agentId: this.agentId,
609
- configurable: config.configurable as
610
- | Record<string, unknown>
611
- | undefined,
612
- metadata: config.metadata as Record<string, unknown> | undefined,
613
- resolve,
614
- reject,
615
- };
616
-
617
- safeDispatchCustomEvent(
618
- GraphEvents.ON_TOOL_EXECUTE,
619
- batchRequest,
620
- config
621
- );
622
- }
623
- );
624
-
625
- this.storeCodeSessionFromResults(results, requestMap);
626
-
627
- const hasPostHook =
628
- this.hookRegistry?.hasHookFor('PostToolUse', runId) === true;
629
- const hasFailureHook =
630
- this.hookRegistry?.hasHookFor('PostToolUseFailure', runId) === true;
631
-
632
- for (const result of results) {
633
- if (result.injectedMessages && result.injectedMessages.length > 0) {
634
- try {
635
- injected.push(
636
- ...this.convertInjectedMessages(result.injectedMessages)
637
- );
638
- } catch (e) {
639
- // eslint-disable-next-line no-console
640
- console.warn(
641
- `[ToolNode] Failed to convert injectedMessages for toolCallId=${result.toolCallId}:`,
642
- e instanceof Error ? e.message : e
643
- );
644
- }
645
- }
646
- const request = requestMap.get(result.toolCallId);
647
- const toolName = request?.name ?? 'unknown';
648
-
649
- let contentString: string;
650
- let toolMessage: ToolMessage;
651
-
652
- if (result.status === 'error') {
653
- contentString = `Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`;
654
- toolMessage = new ToolMessage({
655
- status: 'error',
656
- content: contentString,
657
- name: toolName,
658
- tool_call_id: result.toolCallId,
659
- });
526
+ safeDispatchCustomEvent(GraphEvents.ON_TOOL_EXECUTE, request, config);
527
+ }
528
+ );
660
529
 
661
- if (hasFailureHook) {
662
- await executeHooks({
663
- registry: this.hookRegistry!,
664
- input: {
665
- hook_event_name: 'PostToolUseFailure',
666
- runId,
667
- threadId,
668
- agentId: this.agentId,
669
- toolName,
670
- toolInput: request?.args ?? {},
671
- toolUseId: result.toolCallId,
672
- error: result.errorMessage ?? 'Unknown error',
673
- stepId: request?.stepId,
674
- turn: request?.turn,
675
- },
676
- sessionId: runId,
677
- matchQuery: toolName,
678
- }).catch(() => {
679
- /* PostToolUseFailure is observational — swallow errors */
680
- });
681
- }
682
- } else {
683
- const rawContent =
684
- typeof result.content === 'string'
685
- ? result.content
686
- : JSON.stringify(result.content);
687
- contentString = truncateToolResultContent(
688
- rawContent,
689
- this.maxToolResultChars
690
- );
530
+ this.storeCodeSessionFromResults(results, requests);
691
531
 
692
- if (hasPostHook) {
693
- const hookResult = await executeHooks({
694
- registry: this.hookRegistry!,
695
- input: {
696
- hook_event_name: 'PostToolUse',
697
- runId,
698
- threadId,
699
- agentId: this.agentId,
700
- toolName,
701
- toolInput: request?.args ?? {},
702
- toolOutput: result.content,
703
- toolUseId: result.toolCallId,
704
- stepId: request?.stepId,
705
- turn: request?.turn,
706
- },
707
- sessionId: runId,
708
- matchQuery: toolName,
709
- }).catch((): undefined => undefined);
710
- if (hookResult?.updatedOutput != null) {
711
- const replaced =
712
- typeof hookResult.updatedOutput === 'string'
713
- ? hookResult.updatedOutput
714
- : JSON.stringify(hookResult.updatedOutput);
715
- contentString = truncateToolResultContent(
716
- replaced,
717
- this.maxToolResultChars
718
- );
719
- }
720
- }
532
+ return results.map((result) => {
533
+ const request = requests.find((r) => r.id === result.toolCallId);
534
+ const toolName = request?.name ?? 'unknown';
535
+ const stepId = this.toolCallStepIds?.get(result.toolCallId) ?? '';
536
+ if (!stepId) {
537
+ // eslint-disable-next-line no-console
538
+ console.warn(
539
+ `[ToolNode] toolCallStepIds missing entry for toolCallId=${result.toolCallId} (tool=${toolName}). ` +
540
+ 'This indicates a race between the stream consumer and graph execution. ' +
541
+ `Map size: ${this.toolCallStepIds?.size ?? 0}`
542
+ );
543
+ }
721
544
 
722
- toolMessage = new ToolMessage({
723
- status: 'success',
724
- name: toolName,
725
- content: contentString,
726
- artifact: result.artifact,
727
- tool_call_id: result.toolCallId,
728
- });
729
- }
545
+ let toolMessage: ToolMessage;
546
+ let contentString: string;
730
547
 
731
- this.dispatchStepCompleted(
732
- result.toolCallId,
733
- toolName,
734
- request?.args ?? {},
735
- contentString,
736
- config,
737
- request?.turn
548
+ if (result.status === 'error') {
549
+ contentString = `Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`;
550
+ toolMessage = new ToolMessage({
551
+ status: 'error',
552
+ content: contentString,
553
+ name: toolName,
554
+ tool_call_id: result.toolCallId,
555
+ });
556
+ } else {
557
+ const rawContent =
558
+ typeof result.content === 'string'
559
+ ? result.content
560
+ : JSON.stringify(result.content);
561
+ contentString = truncateToolResultContent(
562
+ rawContent,
563
+ this.maxToolResultChars
738
564
  );
739
-
740
- messageByCallId.set(result.toolCallId, toolMessage);
565
+ toolMessage = new ToolMessage({
566
+ status: 'success',
567
+ name: toolName,
568
+ content: contentString,
569
+ artifact: result.artifact,
570
+ tool_call_id: result.toolCallId,
571
+ });
741
572
  }
742
- }
743
-
744
- const toolMessages = toolCalls
745
- .map((call) => messageByCallId.get(call.id!))
746
- .filter((m): m is ToolMessage => m != null);
747
- return { toolMessages, injected };
748
- }
749
573
 
750
- private dispatchStepCompleted(
751
- toolCallId: string,
752
- toolName: string,
753
- args: Record<string, unknown>,
754
- output: string,
755
- config: RunnableConfig,
756
- turn?: number
757
- ): void {
758
- const stepId = this.toolCallStepIds?.get(toolCallId) ?? '';
759
- if (!stepId) {
760
- // eslint-disable-next-line no-console
761
- console.warn(
762
- `[ToolNode] toolCallStepIds missing entry for toolCallId=${toolCallId} (tool=${toolName}). ` +
763
- 'This indicates a race between the stream consumer and graph execution. ' +
764
- `Map size: ${this.toolCallStepIds?.size ?? 0}`
765
- );
766
- }
574
+ const tool_call: t.ProcessedToolCall = {
575
+ args:
576
+ typeof request?.args === 'string'
577
+ ? request.args
578
+ : JSON.stringify(request?.args ?? {}),
579
+ name: toolName,
580
+ id: result.toolCallId,
581
+ output: contentString,
582
+ progress: 1,
583
+ };
767
584
 
768
- safeDispatchCustomEvent(
769
- GraphEvents.ON_RUN_STEP_COMPLETED,
770
- {
585
+ const runStepCompletedData = {
771
586
  result: {
772
587
  id: stepId,
773
- index: turn ?? this.toolUsageCount.get(toolName) ?? 0,
588
+ index: request?.turn ?? 0,
774
589
  type: 'tool_call' as const,
775
- tool_call: {
776
- args: JSON.stringify(args),
777
- name: toolName,
778
- id: toolCallId,
779
- output,
780
- progress: 1,
781
- } as t.ProcessedToolCall,
590
+ tool_call,
782
591
  },
783
- },
784
- config
785
- );
786
- }
787
-
788
- /**
789
- * Converts InjectedMessage instances to LangChain HumanMessage objects.
790
- * Both 'user' and 'system' roles become HumanMessage to avoid provider
791
- * rejections (Anthropic/Google reject non-leading SystemMessages).
792
- * The original role is preserved in additional_kwargs for downstream consumers.
793
- */
794
- private convertInjectedMessages(
795
- messages: t.InjectedMessage[]
796
- ): BaseMessage[] {
797
- const converted: BaseMessage[] = [];
798
- for (const msg of messages) {
799
- const additional_kwargs: Record<string, unknown> = {
800
- role: msg.role,
801
592
  };
802
- if (msg.isMeta != null) additional_kwargs.isMeta = msg.isMeta;
803
- if (msg.source != null) additional_kwargs.source = msg.source;
804
- if (msg.skillName != null) additional_kwargs.skillName = msg.skillName;
805
593
 
806
- converted.push(
807
- new HumanMessage({ content: msg.content, additional_kwargs })
594
+ safeDispatchCustomEvent(
595
+ GraphEvents.ON_RUN_STEP_COMPLETED,
596
+ runStepCompletedData,
597
+ config
808
598
  );
809
- }
810
- return converted;
599
+
600
+ return toolMessage;
601
+ });
811
602
  }
812
603
 
813
604
  /**
814
605
  * Execute all tool calls via ON_TOOL_EXECUTE event dispatch.
815
- * Injected messages are placed AFTER ToolMessages to respect provider
816
- * message ordering (AIMessage tool_calls must be immediately followed
817
- * by their ToolMessage results).
606
+ * Used in event-driven mode where the host handles actual tool execution.
818
607
  */
819
608
  private async executeViaEvent(
820
609
  toolCalls: ToolCall[],
@@ -822,11 +611,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
822
611
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
823
612
  input: any
824
613
  ): Promise<T> {
825
- const { toolMessages, injected } = await this.dispatchToolEvents(
826
- toolCalls,
827
- config
828
- );
829
- const outputs: BaseMessage[] = [...toolMessages, ...injected];
614
+ const outputs = await this.dispatchToolEvents(toolCalls, config);
830
615
  return (Array.isArray(input) ? outputs : { messages: outputs }) as T;
831
616
  }
832
617
 
@@ -922,19 +707,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
922
707
  this.handleRunToolCompletions(directCalls, directOutputs, config);
923
708
  }
924
709
 
925
- const eventResult =
710
+ const eventOutputs: ToolMessage[] =
926
711
  eventCalls.length > 0
927
712
  ? await this.dispatchToolEvents(eventCalls, config)
928
- : {
929
- toolMessages: [] as ToolMessage[],
930
- injected: [] as BaseMessage[],
931
- };
932
-
933
- outputs = [
934
- ...directOutputs,
935
- ...eventResult.toolMessages,
936
- ...eventResult.injected,
937
- ];
713
+ : [];
714
+
715
+ outputs = [...directOutputs, ...eventOutputs];
938
716
  } else {
939
717
  outputs = await Promise.all(
940
718
  filteredCalls.map((call) => this.runTool(call, config))