@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
package/src/tools/ToolNode.ts
CHANGED
|
@@ -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 {
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
328
|
+
const request = requests.find((r) => r.id === result.toolCallId);
|
|
371
329
|
if (
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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<
|
|
484
|
-
const
|
|
485
|
-
|
|
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
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
-
|
|
579
|
-
|
|
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
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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
|
-
|
|
723
|
-
|
|
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
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
contentString,
|
|
736
|
-
|
|
737
|
-
|
|
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
|
-
|
|
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
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
|
|
769
|
-
GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
770
|
-
{
|
|
585
|
+
const runStepCompletedData = {
|
|
771
586
|
result: {
|
|
772
587
|
id: stepId,
|
|
773
|
-
index: turn ??
|
|
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
|
-
|
|
807
|
-
|
|
594
|
+
safeDispatchCustomEvent(
|
|
595
|
+
GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
596
|
+
runStepCompletedData,
|
|
597
|
+
config
|
|
808
598
|
);
|
|
809
|
-
|
|
810
|
-
|
|
599
|
+
|
|
600
|
+
return toolMessage;
|
|
601
|
+
});
|
|
811
602
|
}
|
|
812
603
|
|
|
813
604
|
/**
|
|
814
605
|
* Execute all tool calls via ON_TOOL_EXECUTE event dispatch.
|
|
815
|
-
*
|
|
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
|
|
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
|
|
710
|
+
const eventOutputs: ToolMessage[] =
|
|
926
711
|
eventCalls.length > 0
|
|
927
712
|
? await this.dispatchToolEvents(eventCalls, config)
|
|
928
|
-
:
|
|
929
|
-
|
|
930
|
-
|
|
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))
|