@librechat/agents 3.1.89 → 3.1.90

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 (77) hide show
  1. package/dist/cjs/graphs/Graph.cjs +7 -0
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hooks/executeHooks.cjs +14 -7
  4. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  5. package/dist/cjs/llm/anthropic/index.cjs +8 -2
  6. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  7. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +34 -0
  8. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  9. package/dist/cjs/main.cjs +9 -0
  10. package/dist/cjs/main.cjs.map +1 -1
  11. package/dist/cjs/tools/BashExecutor.cjs +10 -9
  12. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  13. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +12 -8
  14. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  15. package/dist/cjs/tools/CodeExecutor.cjs +35 -11
  16. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  17. package/dist/cjs/tools/CodeSessionFileSummary.cjs +63 -0
  18. package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -0
  19. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -12
  20. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  21. package/dist/cjs/tools/ToolNode.cjs +8 -5
  22. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  23. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +319 -29
  24. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  25. package/dist/esm/graphs/Graph.mjs +7 -0
  26. package/dist/esm/graphs/Graph.mjs.map +1 -1
  27. package/dist/esm/hooks/executeHooks.mjs +14 -7
  28. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  29. package/dist/esm/llm/anthropic/index.mjs +9 -3
  30. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  31. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +33 -1
  32. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  33. package/dist/esm/main.mjs +2 -1
  34. package/dist/esm/main.mjs.map +1 -1
  35. package/dist/esm/tools/BashExecutor.mjs +11 -10
  36. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  37. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +13 -9
  38. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  39. package/dist/esm/tools/CodeExecutor.mjs +29 -12
  40. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  41. package/dist/esm/tools/CodeSessionFileSummary.mjs +60 -0
  42. package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -0
  43. package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -13
  44. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  45. package/dist/esm/tools/ToolNode.mjs +8 -5
  46. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  47. package/dist/esm/tools/subagent/SubagentExecutor.mjs +320 -31
  48. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  49. package/dist/types/llm/anthropic/index.d.ts +3 -1
  50. package/dist/types/llm/anthropic/utils/message_inputs.d.ts +4 -0
  51. package/dist/types/tools/BashExecutor.d.ts +3 -3
  52. package/dist/types/tools/CodeExecutor.d.ts +10 -3
  53. package/dist/types/tools/CodeSessionFileSummary.d.ts +3 -0
  54. package/dist/types/tools/ProgrammaticToolCalling.d.ts +4 -4
  55. package/dist/types/tools/subagent/SubagentExecutor.d.ts +8 -5
  56. package/dist/types/types/tools.d.ts +2 -3
  57. package/package.json +1 -1
  58. package/src/graphs/Graph.ts +7 -0
  59. package/src/hooks/__tests__/executeHooks.test.ts +38 -0
  60. package/src/hooks/executeHooks.ts +27 -7
  61. package/src/llm/anthropic/index.ts +27 -3
  62. package/src/llm/anthropic/llm.spec.ts +60 -1
  63. package/src/llm/anthropic/utils/message_inputs.ts +46 -0
  64. package/src/tools/BashExecutor.ts +21 -10
  65. package/src/tools/BashProgrammaticToolCalling.ts +21 -9
  66. package/src/tools/CodeExecutor.ts +55 -12
  67. package/src/tools/CodeSessionFileSummary.ts +80 -0
  68. package/src/tools/ProgrammaticToolCalling.ts +25 -12
  69. package/src/tools/ToolNode.ts +8 -5
  70. package/src/tools/__tests__/BashExecutor.test.ts +9 -0
  71. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +43 -0
  72. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +100 -16
  73. package/src/tools/__tests__/SubagentExecutor.test.ts +540 -6
  74. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +52 -0
  75. package/src/tools/__tests__/subagentHooks.test.ts +237 -0
  76. package/src/tools/subagent/SubagentExecutor.ts +514 -36
  77. package/src/types/tools.ts +2 -3
@@ -4,6 +4,9 @@ import type { ToolCall } from '@langchain/core/messages/tool';
4
4
  import type * as t from '@/types';
5
5
  import type {
6
6
  HookCallback,
7
+ PermissionDeniedHookOutput,
8
+ PostToolUseHookOutput,
9
+ PreToolUseHookOutput,
7
10
  SubagentStartHookInput,
8
11
  SubagentStartHookOutput,
9
12
  SubagentStopHookInput,
@@ -11,6 +14,7 @@ import type {
11
14
  } from '@/hooks/types';
12
15
  import { HookRegistry } from '@/hooks/HookRegistry';
13
16
  import { Run } from '@/run';
17
+ import { FakeChatModel } from '@/llm/fake';
14
18
  import {
15
19
  Constants,
16
20
  GraphEvents,
@@ -22,6 +26,18 @@ import * as providers from '@/llm/providers';
22
26
 
23
27
  const CHILD_RESPONSE = 'Hook test child response.';
24
28
 
29
+ const calculatorDef: t.LCTool = {
30
+ name: 'calculator',
31
+ description: 'Evaluate a math expression.',
32
+ parameters: {
33
+ type: 'object',
34
+ properties: {
35
+ expression: { type: 'string' },
36
+ },
37
+ required: ['expression'],
38
+ },
39
+ };
40
+
25
41
  const callerConfig = {
26
42
  configurable: { thread_id: 'hook-test-thread' },
27
43
  streamMode: 'values' as const,
@@ -66,6 +82,40 @@ function createParentAgent(): t.AgentInputs {
66
82
  };
67
83
  }
68
84
 
85
+ function createParentAgentWithChildTool(): t.AgentInputs {
86
+ return {
87
+ agentId: 'hook-parent',
88
+ provider: Providers.OPENAI,
89
+ clientOptions: { modelName: 'gpt-4o-mini', apiKey: 'test-key' },
90
+ instructions: 'Delegate research tasks to subagents.',
91
+ maxContextTokens: 8000,
92
+ subagentConfigs: [
93
+ {
94
+ type: 'researcher',
95
+ name: 'Researcher',
96
+ description: 'Researches topics',
97
+ agentInputs: {
98
+ agentId: 'researcher-child',
99
+ provider: Providers.OPENAI,
100
+ clientOptions: { modelName: 'gpt-4o-mini', apiKey: 'test-key' },
101
+ instructions: 'Use calculator for arithmetic, then answer concisely.',
102
+ maxContextTokens: 8000,
103
+ toolDefinitions: [calculatorDef],
104
+ },
105
+ },
106
+ ],
107
+ };
108
+ }
109
+
110
+ function createCalculatorToolCall(): ToolCall {
111
+ return {
112
+ name: 'calculator',
113
+ args: { expression: '21 * 2' },
114
+ id: 'call_child_calculator',
115
+ type: 'tool_call',
116
+ };
117
+ }
118
+
69
119
  async function createSubagentRun(
70
120
  hooks: HookRegistry,
71
121
  runId = `subagent-hook-${Date.now()}`
@@ -212,4 +262,191 @@ describe('Subagent hook integration (end-to-end via Run)', () => {
212
262
  'Blocked: policy violation'
213
263
  );
214
264
  });
265
+
266
+ it('PreToolUse and PostToolUse fire for event-driven tools inside subagents', async () => {
267
+ getChatModelClassSpy.mockImplementation(((provider: Providers) => {
268
+ if (provider === Providers.OPENAI) {
269
+ return class extends FakeChatModel {
270
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
271
+ constructor(_options: any) {
272
+ super({
273
+ responses: ['Using calculator.', CHILD_RESPONSE],
274
+ sleep: 1,
275
+ toolCalls: [createCalculatorToolCall()],
276
+ });
277
+ }
278
+ bindTools(
279
+ tools: unknown
280
+ ): ReturnType<FakeChatModel['withConfig']> {
281
+ const config = {
282
+ tools,
283
+ } as Parameters<FakeChatModel['withConfig']>[0];
284
+ return this.withConfig(config);
285
+ }
286
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
287
+ } as any;
288
+ }
289
+ return originalGetChatModelClass(provider);
290
+ }) as typeof providers.getChatModelClass);
291
+
292
+ const registry = new HookRegistry();
293
+ const preToolEvents: string[] = [];
294
+ const postToolEvents: string[] = [];
295
+
296
+ const preHook: HookCallback<'PreToolUse'> = async (
297
+ input
298
+ ): Promise<PreToolUseHookOutput> => {
299
+ preToolEvents.push(`${input.agentId ?? '-'}:${input.toolName}`);
300
+ return { decision: 'allow' };
301
+ };
302
+ registry.register('PreToolUse', { hooks: [preHook] });
303
+
304
+ const postHook: HookCallback<'PostToolUse'> = async (
305
+ input
306
+ ): Promise<PostToolUseHookOutput> => {
307
+ postToolEvents.push(`${input.agentId ?? '-'}:${input.toolName}`);
308
+ return {};
309
+ };
310
+ registry.register('PostToolUse', { hooks: [postHook] });
311
+
312
+ const customHandlers: Record<string, t.EventHandler> = {
313
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
314
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
315
+ [GraphEvents.ON_TOOL_EXECUTE]: {
316
+ handle: (_event, rawData): void => {
317
+ const request = rawData as t.ToolExecuteBatchRequest;
318
+ const results: t.ToolExecuteResult[] = request.toolCalls.map(
319
+ (call) => ({
320
+ toolCallId: call.id,
321
+ status: 'success',
322
+ content: '42',
323
+ })
324
+ );
325
+ request.resolve(results);
326
+ },
327
+ },
328
+ };
329
+
330
+ const run = await Run.create<t.IState>({
331
+ runId: `subagent-tool-hook-${Date.now()}`,
332
+ graphConfig: {
333
+ type: 'standard',
334
+ agents: [createParentAgentWithChildTool()],
335
+ },
336
+ returnContent: true,
337
+ skipCleanup: true,
338
+ customHandlers,
339
+ hooks: registry,
340
+ });
341
+
342
+ const tc = makeSubagentToolCall();
343
+ run.Graph!.overrideTestModel(['Delegating...', 'Final answer.'], 5, [tc]);
344
+
345
+ await run.processStream(
346
+ { messages: [new HumanMessage('calculate something')] },
347
+ callerConfig
348
+ );
349
+
350
+ expect(preToolEvents).toContain('-:subagent');
351
+ expect(preToolEvents).toContain('researcher-child:calculator');
352
+ expect(postToolEvents).toContain('-:subagent');
353
+ expect(postToolEvents).toContain('researcher-child:calculator');
354
+ });
355
+
356
+ it('child subagent tool ask hooks fail closed instead of starting unsupported nested HITL', async () => {
357
+ getChatModelClassSpy.mockImplementation(((provider: Providers) => {
358
+ if (provider === Providers.OPENAI) {
359
+ return class extends FakeChatModel {
360
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
361
+ constructor(_options: any) {
362
+ super({
363
+ responses: ['Using calculator.', CHILD_RESPONSE],
364
+ sleep: 1,
365
+ toolCalls: [createCalculatorToolCall()],
366
+ });
367
+ }
368
+ bindTools(
369
+ tools: unknown
370
+ ): ReturnType<FakeChatModel['withConfig']> {
371
+ const config = {
372
+ tools,
373
+ } as Parameters<FakeChatModel['withConfig']>[0];
374
+ return this.withConfig(config);
375
+ }
376
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
377
+ } as any;
378
+ }
379
+ return originalGetChatModelClass(provider);
380
+ }) as typeof providers.getChatModelClass);
381
+
382
+ const registry = new HookRegistry();
383
+ const deniedTools: string[] = [];
384
+ const executedTools: string[] = [];
385
+
386
+ const preHook: HookCallback<'PreToolUse'> = async (
387
+ input
388
+ ): Promise<PreToolUseHookOutput> => {
389
+ if (input.toolName === 'calculator') {
390
+ return { decision: 'ask', reason: 'review calculator' };
391
+ }
392
+ return { decision: 'allow' };
393
+ };
394
+ registry.register('PreToolUse', { hooks: [preHook] });
395
+
396
+ const deniedHook: HookCallback<'PermissionDenied'> = async (
397
+ input
398
+ ): Promise<PermissionDeniedHookOutput> => {
399
+ deniedTools.push(
400
+ `${input.agentId ?? '-'}:${input.toolName}:${input.reason}`
401
+ );
402
+ return {};
403
+ };
404
+ registry.register('PermissionDenied', { hooks: [deniedHook] });
405
+
406
+ const customHandlers: Record<string, t.EventHandler> = {
407
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
408
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
409
+ [GraphEvents.ON_TOOL_EXECUTE]: {
410
+ handle: (_event, rawData): void => {
411
+ const request = rawData as t.ToolExecuteBatchRequest;
412
+ executedTools.push(...request.toolCalls.map((call) => call.name));
413
+ const results: t.ToolExecuteResult[] = request.toolCalls.map(
414
+ (call) => ({
415
+ toolCallId: call.id,
416
+ status: 'success',
417
+ content: '42',
418
+ })
419
+ );
420
+ request.resolve(results);
421
+ },
422
+ },
423
+ };
424
+
425
+ const run = await Run.create<t.IState>({
426
+ runId: `subagent-tool-ask-${Date.now()}`,
427
+ graphConfig: {
428
+ type: 'standard',
429
+ agents: [createParentAgentWithChildTool()],
430
+ },
431
+ returnContent: true,
432
+ skipCleanup: true,
433
+ customHandlers,
434
+ hooks: registry,
435
+ humanInTheLoop: { enabled: true },
436
+ });
437
+
438
+ const tc = makeSubagentToolCall();
439
+ run.Graph!.overrideTestModel(['Delegating...', 'Final answer.'], 5, [tc]);
440
+
441
+ await run.processStream(
442
+ { messages: [new HumanMessage('calculate something')] },
443
+ callerConfig
444
+ );
445
+
446
+ expect(run.getInterrupt()).toBeUndefined();
447
+ expect(deniedTools).toContain(
448
+ 'researcher-child:calculator:review calculator'
449
+ );
450
+ expect(executedTools).not.toContain('calculator');
451
+ });
215
452
  });