@librechat/agents 3.1.66-dev.0 → 3.1.67

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