@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.
- package/dist/cjs/agents/AgentContext.cjs +24 -15
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +0 -13
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +0 -3
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +0 -40
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +12 -74
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/run.cjs +0 -111
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +140 -304
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +24 -15
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +1 -12
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +0 -3
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -10
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +4 -66
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/run.mjs +0 -111
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +142 -306
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +6 -0
- package/dist/types/common/enum.d.ts +1 -7
- package/dist/types/graphs/Graph.d.ts +0 -2
- package/dist/types/index.d.ts +0 -6
- package/dist/types/messages/format.d.ts +1 -2
- package/dist/types/run.d.ts +0 -1
- package/dist/types/tools/ToolNode.d.ts +2 -24
- package/dist/types/types/index.d.ts +0 -1
- package/dist/types/types/llm.d.ts +14 -2
- package/dist/types/types/run.d.ts +0 -20
- package/dist/types/types/tools.d.ts +1 -38
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +28 -15
- package/src/agents/__tests__/AgentContext.test.ts +110 -0
- package/src/common/enum.ts +0 -12
- package/src/graphs/Graph.ts +0 -4
- package/src/index.ts +0 -8
- package/src/messages/format.ts +4 -74
- package/src/run.ts +0 -126
- package/src/tools/ToolNode.ts +169 -391
- package/src/tools/__tests__/ToolNode.session.test.ts +12 -12
- package/src/types/index.ts +0 -1
- package/src/types/llm.ts +16 -2
- package/src/types/run.ts +0 -20
- package/src/types/tools.ts +1 -41
- package/dist/cjs/hooks/HookRegistry.cjs +0 -162
- package/dist/cjs/hooks/HookRegistry.cjs.map +0 -1
- package/dist/cjs/hooks/executeHooks.cjs +0 -276
- package/dist/cjs/hooks/executeHooks.cjs.map +0 -1
- package/dist/cjs/hooks/matchers.cjs +0 -256
- package/dist/cjs/hooks/matchers.cjs.map +0 -1
- package/dist/cjs/hooks/types.cjs +0 -27
- package/dist/cjs/hooks/types.cjs.map +0 -1
- package/dist/cjs/tools/BashExecutor.cjs +0 -175
- package/dist/cjs/tools/BashExecutor.cjs.map +0 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +0 -296
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +0 -1
- package/dist/cjs/tools/ReadFile.cjs +0 -43
- package/dist/cjs/tools/ReadFile.cjs.map +0 -1
- package/dist/cjs/tools/SkillTool.cjs +0 -50
- package/dist/cjs/tools/SkillTool.cjs.map +0 -1
- package/dist/cjs/tools/skillCatalog.cjs +0 -84
- package/dist/cjs/tools/skillCatalog.cjs.map +0 -1
- package/dist/esm/hooks/HookRegistry.mjs +0 -160
- package/dist/esm/hooks/HookRegistry.mjs.map +0 -1
- package/dist/esm/hooks/executeHooks.mjs +0 -273
- package/dist/esm/hooks/executeHooks.mjs.map +0 -1
- package/dist/esm/hooks/matchers.mjs +0 -251
- package/dist/esm/hooks/matchers.mjs.map +0 -1
- package/dist/esm/hooks/types.mjs +0 -25
- package/dist/esm/hooks/types.mjs.map +0 -1
- package/dist/esm/tools/BashExecutor.mjs +0 -169
- package/dist/esm/tools/BashExecutor.mjs.map +0 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +0 -287
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +0 -1
- package/dist/esm/tools/ReadFile.mjs +0 -38
- package/dist/esm/tools/ReadFile.mjs.map +0 -1
- package/dist/esm/tools/SkillTool.mjs +0 -45
- package/dist/esm/tools/SkillTool.mjs.map +0 -1
- package/dist/esm/tools/skillCatalog.mjs +0 -82
- package/dist/esm/tools/skillCatalog.mjs.map +0 -1
- package/dist/types/hooks/HookRegistry.d.ts +0 -56
- package/dist/types/hooks/executeHooks.d.ts +0 -79
- package/dist/types/hooks/index.d.ts +0 -6
- package/dist/types/hooks/matchers.d.ts +0 -95
- package/dist/types/hooks/types.d.ts +0 -309
- package/dist/types/tools/BashExecutor.d.ts +0 -45
- package/dist/types/tools/BashProgrammaticToolCalling.d.ts +0 -72
- package/dist/types/tools/ReadFile.d.ts +0 -28
- package/dist/types/tools/SkillTool.d.ts +0 -40
- package/dist/types/tools/skillCatalog.d.ts +0 -19
- package/dist/types/types/skill.d.ts +0 -9
- package/src/hooks/HookRegistry.ts +0 -208
- package/src/hooks/__tests__/HookRegistry.test.ts +0 -190
- package/src/hooks/__tests__/executeHooks.test.ts +0 -1013
- package/src/hooks/__tests__/integration.test.ts +0 -337
- package/src/hooks/__tests__/matchers.test.ts +0 -238
- package/src/hooks/__tests__/toolHooks.test.ts +0 -669
- package/src/hooks/executeHooks.ts +0 -375
- package/src/hooks/index.ts +0 -55
- package/src/hooks/matchers.ts +0 -280
- package/src/hooks/types.ts +0 -388
- package/src/messages/formatAgentMessages.skills.test.ts +0 -334
- package/src/tools/BashExecutor.ts +0 -205
- package/src/tools/BashProgrammaticToolCalling.ts +0 -397
- package/src/tools/ReadFile.ts +0 -39
- package/src/tools/SkillTool.ts +0 -46
- package/src/tools/__tests__/ReadFile.test.ts +0 -44
- package/src/tools/__tests__/SkillTool.test.ts +0 -442
- package/src/tools/__tests__/skillCatalog.test.ts +0 -161
- package/src/tools/skillCatalog.ts +0 -126
- package/src/types/skill.ts +0 -11
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { isBaseMessage, ToolMessage,
|
|
1
|
+
import { isBaseMessage, ToolMessage, isAIMessage } from '@langchain/core/messages';
|
|
2
2
|
import { isCommand, isGraphInterrupt, Command, Send, END } from '@langchain/langgraph';
|
|
3
|
-
import { Constants,
|
|
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
|
-
|
|
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 (
|
|
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,
|
|
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 =
|
|
287
|
-
if (
|
|
288
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
|
537
|
+
const eventOutputs = eventCalls.length > 0
|
|
695
538
|
? await this.dispatchToolEvents(eventCalls, config)
|
|
696
|
-
:
|
|
697
|
-
|
|
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)));
|