@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.
- package/dist/cjs/graphs/Graph.cjs +7 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +14 -7
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +8 -2
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +34 -0
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/main.cjs +9 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +10 -9
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +12 -8
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +35 -11
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeSessionFileSummary.cjs +63 -0
- package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -12
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +8 -5
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +319 -29
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +7 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +14 -7
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +9 -3
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +33 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +11 -10
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +13 -9
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +29 -12
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeSessionFileSummary.mjs +60 -0
- package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -13
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +8 -5
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +320 -31
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/types/llm/anthropic/index.d.ts +3 -1
- package/dist/types/llm/anthropic/utils/message_inputs.d.ts +4 -0
- package/dist/types/tools/BashExecutor.d.ts +3 -3
- package/dist/types/tools/CodeExecutor.d.ts +10 -3
- package/dist/types/tools/CodeSessionFileSummary.d.ts +3 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +4 -4
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +8 -5
- package/dist/types/types/tools.d.ts +2 -3
- package/package.json +1 -1
- package/src/graphs/Graph.ts +7 -0
- package/src/hooks/__tests__/executeHooks.test.ts +38 -0
- package/src/hooks/executeHooks.ts +27 -7
- package/src/llm/anthropic/index.ts +27 -3
- package/src/llm/anthropic/llm.spec.ts +60 -1
- package/src/llm/anthropic/utils/message_inputs.ts +46 -0
- package/src/tools/BashExecutor.ts +21 -10
- package/src/tools/BashProgrammaticToolCalling.ts +21 -9
- package/src/tools/CodeExecutor.ts +55 -12
- package/src/tools/CodeSessionFileSummary.ts +80 -0
- package/src/tools/ProgrammaticToolCalling.ts +25 -12
- package/src/tools/ToolNode.ts +8 -5
- package/src/tools/__tests__/BashExecutor.test.ts +9 -0
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +43 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +100 -16
- package/src/tools/__tests__/SubagentExecutor.test.ts +540 -6
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +52 -0
- package/src/tools/__tests__/subagentHooks.test.ts +237 -0
- package/src/tools/subagent/SubagentExecutor.ts +514 -36
- 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
|
});
|