@librechat/agents 3.2.62 → 3.2.64
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 +6 -2
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +82 -34
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +1 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +6 -2
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +82 -34
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +1 -1
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +3 -1
- package/dist/types/types/graph.d.ts +10 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +12 -4
- package/src/llm/bedrock/utils/message_inputs.test.ts +276 -1
- package/src/llm/bedrock/utils/message_inputs.ts +121 -73
- package/src/tools/ToolNode.ts +5 -1
- package/src/tools/__tests__/subagentHooks.test.ts +124 -0
- package/src/tools/subagent/SubagentExecutor.ts +1 -0
- package/src/types/graph.ts +10 -0
|
@@ -4,6 +4,7 @@ import type { ToolCall } from '@langchain/core/messages/tool';
|
|
|
4
4
|
import type {
|
|
5
5
|
HookCallback,
|
|
6
6
|
PermissionDeniedHookOutput,
|
|
7
|
+
PostToolBatchHookOutput,
|
|
7
8
|
PostToolUseHookOutput,
|
|
8
9
|
PreToolUseHookOutput,
|
|
9
10
|
SubagentStartHookInput,
|
|
@@ -365,6 +366,129 @@ describe('Subagent hook integration (end-to-end via Run)', () => {
|
|
|
365
366
|
expect(postExecEvents).toContain('researcher-child:calculator');
|
|
366
367
|
});
|
|
367
368
|
|
|
369
|
+
it('top-level event-driven dispatches leave agentId unset (subagent-scope marker)', async () => {
|
|
370
|
+
getChatModelClassSpy.mockImplementation(((provider: Providers) => {
|
|
371
|
+
if (provider === Providers.OPENAI) {
|
|
372
|
+
return class extends FakeChatModel {
|
|
373
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
374
|
+
constructor(_options: any) {
|
|
375
|
+
super({
|
|
376
|
+
responses: ['Calculating.', 'All done.'],
|
|
377
|
+
sleep: 1,
|
|
378
|
+
toolCalls: [createCalculatorToolCall()],
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
bindTools(tools: unknown): ReturnType<FakeChatModel['withConfig']> {
|
|
382
|
+
const config = {
|
|
383
|
+
tools,
|
|
384
|
+
} as Parameters<FakeChatModel['withConfig']>[0];
|
|
385
|
+
return this.withConfig(config);
|
|
386
|
+
}
|
|
387
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
388
|
+
} as any;
|
|
389
|
+
}
|
|
390
|
+
return originalGetChatModelClass(provider);
|
|
391
|
+
}) as typeof providers.getChatModelClass);
|
|
392
|
+
|
|
393
|
+
const registry = new HookRegistry();
|
|
394
|
+
const scopeEvents: string[] = [];
|
|
395
|
+
|
|
396
|
+
const preHook: HookCallback<'PreToolUse'> = async (
|
|
397
|
+
input
|
|
398
|
+
): Promise<PreToolUseHookOutput> => {
|
|
399
|
+
scopeEvents.push(
|
|
400
|
+
`pre:${input.agentId ?? '-'}:${input.executingAgentId ?? '-'}`
|
|
401
|
+
);
|
|
402
|
+
return { decision: 'allow' };
|
|
403
|
+
};
|
|
404
|
+
registry.register('PreToolUse', { hooks: [preHook] });
|
|
405
|
+
|
|
406
|
+
const postHook: HookCallback<'PostToolUse'> = async (
|
|
407
|
+
input
|
|
408
|
+
): Promise<PostToolUseHookOutput> => {
|
|
409
|
+
scopeEvents.push(
|
|
410
|
+
`post:${input.agentId ?? '-'}:${input.executingAgentId ?? '-'}`
|
|
411
|
+
);
|
|
412
|
+
return {};
|
|
413
|
+
};
|
|
414
|
+
registry.register('PostToolUse', { hooks: [postHook] });
|
|
415
|
+
|
|
416
|
+
const batchHook: HookCallback<'PostToolBatch'> = async (
|
|
417
|
+
input
|
|
418
|
+
): Promise<PostToolBatchHookOutput> => {
|
|
419
|
+
scopeEvents.push(
|
|
420
|
+
`batch:${input.agentId ?? '-'}:${input.executingAgentId ?? '-'}`
|
|
421
|
+
);
|
|
422
|
+
return {};
|
|
423
|
+
};
|
|
424
|
+
registry.register('PostToolBatch', { hooks: [batchHook] });
|
|
425
|
+
|
|
426
|
+
const dispatchAgentIds: Array<string | undefined> = [];
|
|
427
|
+
const customHandlers: Record<string, t.EventHandler> = {
|
|
428
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
429
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
430
|
+
[GraphEvents.ON_TOOL_EXECUTE]: {
|
|
431
|
+
handle: (_event, rawData): void => {
|
|
432
|
+
const request = rawData as t.ToolExecuteBatchRequest;
|
|
433
|
+
dispatchAgentIds.push(request.agentId);
|
|
434
|
+
const results: t.ToolExecuteResult[] = request.toolCalls.map(
|
|
435
|
+
(call) => ({
|
|
436
|
+
toolCallId: call.id,
|
|
437
|
+
status: 'success',
|
|
438
|
+
content: '42',
|
|
439
|
+
})
|
|
440
|
+
);
|
|
441
|
+
request.resolve(results);
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* The LibreChat shape: a TOP-LEVEL agent whose tools ride
|
|
448
|
+
* `toolDefinitions` (event-driven ToolNode, host executes via
|
|
449
|
+
* ON_TOOL_EXECUTE). Hook inputs here must NOT carry the subagent-scope
|
|
450
|
+
* marker — a host hook keying on `agentId != null` (e.g. a steering
|
|
451
|
+
* drain that must never inject into child state) would otherwise skip
|
|
452
|
+
* every top-level batch. The DISPATCH payload is the opposite: hosts
|
|
453
|
+
* key tool/credential lookup on `request.agentId`, so it must keep
|
|
454
|
+
* identifying the owning agent.
|
|
455
|
+
*/
|
|
456
|
+
const run = await Run.create<t.IState>({
|
|
457
|
+
runId: `toplevel-event-hook-${Date.now()}`,
|
|
458
|
+
graphConfig: {
|
|
459
|
+
type: 'standard',
|
|
460
|
+
agents: [
|
|
461
|
+
{
|
|
462
|
+
agentId: 'hook-parent',
|
|
463
|
+
provider: Providers.OPENAI,
|
|
464
|
+
clientOptions: { modelName: 'gpt-4o-mini', apiKey: 'test-key' },
|
|
465
|
+
instructions: 'Use the calculator.',
|
|
466
|
+
maxContextTokens: 8000,
|
|
467
|
+
toolDefinitions: [calculatorDef],
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
},
|
|
471
|
+
returnContent: true,
|
|
472
|
+
skipCleanup: true,
|
|
473
|
+
customHandlers,
|
|
474
|
+
hooks: registry,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
run.Graph!.overrideTestModel(['Calculating.', 'All done.'], 5, [
|
|
478
|
+
createCalculatorToolCall(),
|
|
479
|
+
]);
|
|
480
|
+
|
|
481
|
+
await run.processStream(
|
|
482
|
+
{ messages: [new HumanMessage('what is 21 * 2?')] },
|
|
483
|
+
callerConfig
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
expect(scopeEvents).toContain('pre:-:hook-parent');
|
|
487
|
+
expect(scopeEvents).toContain('post:-:hook-parent');
|
|
488
|
+
expect(scopeEvents).toContain('batch:-:hook-parent');
|
|
489
|
+
expect(dispatchAgentIds).toEqual(['hook-parent']);
|
|
490
|
+
});
|
|
491
|
+
|
|
368
492
|
it('child subagent tool ask hooks fail closed instead of starting unsupported nested HITL', async () => {
|
|
369
493
|
getChatModelClassSpy.mockImplementation(((provider: Providers) => {
|
|
370
494
|
if (provider === Providers.OPENAI) {
|
|
@@ -373,6 +373,7 @@ export class SubagentExecutor {
|
|
|
373
373
|
agents: [childInputs],
|
|
374
374
|
langfuse: this.langfuse,
|
|
375
375
|
tokenCounter: this.tokenCounter,
|
|
376
|
+
subagentScope: true,
|
|
376
377
|
/**
|
|
377
378
|
* Forwarded so the child graph's own `SubagentExecutor` (created in
|
|
378
379
|
* its `createAgentNode` when `allowNested` keeps subagentConfigs)
|
package/src/types/graph.ts
CHANGED
|
@@ -336,6 +336,16 @@ export type StandardGraphInput = {
|
|
|
336
336
|
* they already flow through the registry's `CHAT_MODEL_END` handler.
|
|
337
337
|
*/
|
|
338
338
|
subagentUsageSink?: SubagentUsageSink;
|
|
339
|
+
/**
|
|
340
|
+
* True when this graph IS a subagent child run (set by `SubagentExecutor`
|
|
341
|
+
* when it constructs the child graph). Drives the hook-input `agentId`
|
|
342
|
+
* subagent-scope marker: hook dispatches from this graph's tool nodes
|
|
343
|
+
* carry `agentId` so run-scoped host hooks — which fire for child scopes
|
|
344
|
+
* too, because children inherit the parent's `run_id` — can tell child
|
|
345
|
+
* scope from the top level. Top-level graphs leave this unset and their
|
|
346
|
+
* hook inputs carry only `executingAgentId`.
|
|
347
|
+
*/
|
|
348
|
+
subagentScope?: boolean;
|
|
339
349
|
};
|
|
340
350
|
|
|
341
351
|
export type GraphEdge = {
|