@librechat/agents 3.1.67-dev.0 → 3.1.67-dev.4
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/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +19 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +1 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +267 -17
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +19 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +267 -18
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +56 -2
- package/dist/types/tools/subagent/index.d.ts +1 -1
- package/dist/types/types/graph.d.ts +36 -2
- package/package.json +4 -1
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +21 -0
- package/src/scripts/subagent-event-driven-debug.ts +190 -0
- package/src/scripts/subagent-tools-debug.ts +160 -0
- package/src/tools/__tests__/SubagentExecutor.test.ts +534 -1
- package/src/tools/subagent/SubagentExecutor.ts +349 -17
- package/src/tools/subagent/index.ts +1 -0
- package/src/types/graph.ts +53 -1
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
|
+
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
2
3
|
import { HumanMessage } from '@langchain/core/messages';
|
|
3
4
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
5
|
+
import type { Callbacks } from '@langchain/core/callbacks/manager';
|
|
4
6
|
import type {
|
|
5
7
|
AgentInputs,
|
|
6
8
|
StandardGraphInput,
|
|
7
9
|
ResolvedSubagentConfig,
|
|
8
10
|
SubagentConfig,
|
|
11
|
+
SubagentUpdateEvent,
|
|
12
|
+
SubagentUpdatePhase,
|
|
13
|
+
ToolExecuteBatchRequest,
|
|
9
14
|
TokenCounter,
|
|
10
15
|
} from '@/types';
|
|
11
16
|
import type { AggregatedHookResult, HookRegistry } from '@/hooks';
|
|
12
17
|
import type { AgentContext } from '@/agents/AgentContext';
|
|
13
18
|
import type { StandardGraph } from '@/graphs/Graph';
|
|
19
|
+
import { GraphEvents, Callback } from '@/common';
|
|
20
|
+
import type { HandlerRegistry } from '@/events';
|
|
14
21
|
import { executeHooks } from '@/hooks';
|
|
15
22
|
|
|
16
23
|
const DEFAULT_MAX_TURNS = 25;
|
|
@@ -26,6 +33,13 @@ export type SubagentExecuteParams = {
|
|
|
26
33
|
description: string;
|
|
27
34
|
subagentType: string;
|
|
28
35
|
threadId?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Parent-side `tool_call_id` of the `subagent` tool invocation that
|
|
38
|
+
* triggered this execution. Surfaced on {@link SubagentUpdateEvent} so
|
|
39
|
+
* hosts can correlate child updates back to the originating tool call
|
|
40
|
+
* without relying on event ordering heuristics.
|
|
41
|
+
*/
|
|
42
|
+
parentToolCallId?: string;
|
|
29
43
|
};
|
|
30
44
|
|
|
31
45
|
export type SubagentExecuteResult = {
|
|
@@ -57,6 +71,19 @@ export type SubagentExecutorOptions = {
|
|
|
57
71
|
* module dependency.
|
|
58
72
|
*/
|
|
59
73
|
createChildGraph: ChildGraphFactory;
|
|
74
|
+
/**
|
|
75
|
+
* Parent's event handler registry. When provided, child-graph events are
|
|
76
|
+
* forwarded through this registry so hosts can:
|
|
77
|
+
* (a) execute event-driven tools (`ON_TOOL_EXECUTE` routed to parent's handler),
|
|
78
|
+
* (b) surface child activity to a UI via wrapped {@link GraphEvents.ON_SUBAGENT_UPDATE}.
|
|
79
|
+
* When omitted, the child runs fully isolated (legacy behavior).
|
|
80
|
+
*
|
|
81
|
+
* Can be a direct `HandlerRegistry` or a zero-arg getter — use the getter
|
|
82
|
+
* form when the registry is assigned to the graph AFTER the executor is
|
|
83
|
+
* constructed (the current `Run.create` flow sets `handlerRegistry`
|
|
84
|
+
* post-`createWorkflow`, so `createAgentNode` must capture lazily).
|
|
85
|
+
*/
|
|
86
|
+
parentHandlerRegistry?: HandlerRegistry | (() => HandlerRegistry | undefined);
|
|
60
87
|
};
|
|
61
88
|
|
|
62
89
|
export class SubagentExecutor {
|
|
@@ -68,6 +95,9 @@ export class SubagentExecutor {
|
|
|
68
95
|
private readonly tokenCounter?: TokenCounter;
|
|
69
96
|
private readonly maxDepth: number;
|
|
70
97
|
private readonly createChildGraph: ChildGraphFactory;
|
|
98
|
+
private readonly resolveParentHandlerRegistry?: () =>
|
|
99
|
+
| HandlerRegistry
|
|
100
|
+
| undefined;
|
|
71
101
|
|
|
72
102
|
constructor(options: SubagentExecutorOptions) {
|
|
73
103
|
this.configs = options.configs;
|
|
@@ -78,10 +108,21 @@ export class SubagentExecutor {
|
|
|
78
108
|
this.tokenCounter = options.tokenCounter;
|
|
79
109
|
this.maxDepth = options.maxDepth ?? 1;
|
|
80
110
|
this.createChildGraph = options.createChildGraph;
|
|
111
|
+
const rawRegistry = options.parentHandlerRegistry;
|
|
112
|
+
if (typeof rawRegistry === 'function') {
|
|
113
|
+
this.resolveParentHandlerRegistry = rawRegistry;
|
|
114
|
+
} else if (rawRegistry != null) {
|
|
115
|
+
this.resolveParentHandlerRegistry = (): HandlerRegistry => rawRegistry;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Snapshot of the parent's registry at the moment a subagent is dispatched. */
|
|
120
|
+
private getParentHandlerRegistry(): HandlerRegistry | undefined {
|
|
121
|
+
return this.resolveParentHandlerRegistry?.();
|
|
81
122
|
}
|
|
82
123
|
|
|
83
124
|
async execute(params: SubagentExecuteParams): Promise<SubagentExecuteResult> {
|
|
84
|
-
const { description, subagentType, threadId } = params;
|
|
125
|
+
const { description, subagentType, threadId, parentToolCallId } = params;
|
|
85
126
|
const config = this.configs.get(subagentType);
|
|
86
127
|
|
|
87
128
|
if (!config) {
|
|
@@ -134,7 +175,25 @@ export class SubagentExecutor {
|
|
|
134
175
|
}
|
|
135
176
|
}
|
|
136
177
|
|
|
137
|
-
const
|
|
178
|
+
const parentRegistry = this.getParentHandlerRegistry();
|
|
179
|
+
const forwardingEnabled = parentRegistry != null;
|
|
180
|
+
/**
|
|
181
|
+
* Keep `toolDefinitions` only when the host has actually wired an
|
|
182
|
+
* `ON_TOOL_EXECUTE` handler. `Run` always constructs a `HandlerRegistry`,
|
|
183
|
+
* so treating any registry as "forwarding enabled" would leak
|
|
184
|
+
* `toolDefinitions` into children whose hosts cannot execute them — the
|
|
185
|
+
* child's `ToolNode` batch promise would hang forever with no handler to
|
|
186
|
+
* resolve/reject. Gating on the tool-execute handler preserves the
|
|
187
|
+
* recoverable "no tools" path for registry-but-no-handler configs.
|
|
188
|
+
*/
|
|
189
|
+
const hasToolExecuteHandler =
|
|
190
|
+
parentRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) != null;
|
|
191
|
+
const childInputs = buildChildInputs(
|
|
192
|
+
config,
|
|
193
|
+
childAgentId,
|
|
194
|
+
this.maxDepth,
|
|
195
|
+
/* keepToolDefinitions */ hasToolExecuteHandler
|
|
196
|
+
);
|
|
138
197
|
const childRunId = `${this.parentRunId}_sub_${nanoid(8)}`;
|
|
139
198
|
const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
140
199
|
|
|
@@ -145,30 +204,54 @@ export class SubagentExecutor {
|
|
|
145
204
|
tokenCounter: this.tokenCounter,
|
|
146
205
|
});
|
|
147
206
|
|
|
207
|
+
const forwarder = forwardingEnabled
|
|
208
|
+
? this.createForwarderCallback({
|
|
209
|
+
parentRegistry: parentRegistry!,
|
|
210
|
+
subagentType,
|
|
211
|
+
subagentAgentId: childAgentId,
|
|
212
|
+
childRunId,
|
|
213
|
+
parentToolCallId,
|
|
214
|
+
})
|
|
215
|
+
: undefined;
|
|
216
|
+
|
|
217
|
+
if (forwarder) {
|
|
218
|
+
await this.emitSubagentUpdate(parentRegistry!, {
|
|
219
|
+
childRunId,
|
|
220
|
+
subagentType,
|
|
221
|
+
subagentAgentId: childAgentId,
|
|
222
|
+
parentToolCallId,
|
|
223
|
+
phase: 'start',
|
|
224
|
+
label: `Subagent "${subagentType}" started`,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
148
228
|
let result: { messages: BaseMessage[] };
|
|
149
229
|
try {
|
|
150
230
|
const workflow = childGraph.createWorkflow();
|
|
151
231
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
232
|
+
* When `parentHandlerRegistry` is provided (forwarding mode), attach a
|
|
233
|
+
* lightweight callback that intercepts the child's `on_custom_event`
|
|
234
|
+
* dispatches and routes them to the parent's registry — either as
|
|
235
|
+
* operational events (ON_TOOL_EXECUTE) or wrapped ON_SUBAGENT_UPDATE
|
|
236
|
+
* envelopes. Native LangChain streaming events (on_chat_model_stream,
|
|
237
|
+
* etc.) still do NOT propagate to the parent's outer streamEvents
|
|
238
|
+
* iterator — the `callbacks` array REPLACES the inherited chain, so
|
|
239
|
+
* parent handlers won't receive child stream chunks and raise "No
|
|
240
|
+
* agent context found" lookups on the parent's agentContexts map.
|
|
241
|
+
*
|
|
242
|
+
* When no registry is provided (legacy isolation), `callbacks: []`
|
|
243
|
+
* fully detaches the child.
|
|
162
244
|
*
|
|
163
245
|
* `runName` gives the child a distinct LangSmith trace root (avoids
|
|
164
246
|
* nested trace pollution).
|
|
165
247
|
*/
|
|
248
|
+
const callbacks: Callbacks = forwarder ? [forwarder] : [];
|
|
166
249
|
result = await workflow.invoke(
|
|
167
250
|
{ messages: [new HumanMessage(description)] },
|
|
168
251
|
{
|
|
169
252
|
recursionLimit: maxTurns * RECURSION_MULTIPLIER,
|
|
170
253
|
signal: this.parentSignal,
|
|
171
|
-
callbacks
|
|
254
|
+
callbacks,
|
|
172
255
|
runName: `subagent:${subagentType}`,
|
|
173
256
|
configurable: {
|
|
174
257
|
thread_id: childRunId,
|
|
@@ -176,9 +259,21 @@ export class SubagentExecutor {
|
|
|
176
259
|
}
|
|
177
260
|
);
|
|
178
261
|
} catch (error) {
|
|
262
|
+
const errorMessage = truncateErrorMessage(error);
|
|
263
|
+
if (forwarder) {
|
|
264
|
+
await this.emitSubagentUpdate(parentRegistry!, {
|
|
265
|
+
childRunId,
|
|
266
|
+
subagentType,
|
|
267
|
+
subagentAgentId: childAgentId,
|
|
268
|
+
parentToolCallId,
|
|
269
|
+
phase: 'error',
|
|
270
|
+
label: `Subagent "${subagentType}" errored: ${errorMessage}`,
|
|
271
|
+
data: { message: errorMessage },
|
|
272
|
+
});
|
|
273
|
+
}
|
|
179
274
|
childGraph.clearHeavyState();
|
|
180
275
|
return {
|
|
181
|
-
content: `Subagent error: ${
|
|
276
|
+
content: `Subagent error: ${errorMessage}`,
|
|
182
277
|
messages: [],
|
|
183
278
|
};
|
|
184
279
|
}
|
|
@@ -211,10 +306,228 @@ export class SubagentExecutor {
|
|
|
211
306
|
});
|
|
212
307
|
}
|
|
213
308
|
|
|
309
|
+
if (forwarder) {
|
|
310
|
+
await this.emitSubagentUpdate(parentRegistry!, {
|
|
311
|
+
childRunId,
|
|
312
|
+
subagentType,
|
|
313
|
+
subagentAgentId: childAgentId,
|
|
314
|
+
parentToolCallId,
|
|
315
|
+
phase: 'stop',
|
|
316
|
+
label: `Subagent "${subagentType}" finished`,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
214
320
|
childGraph.clearHeavyState();
|
|
215
321
|
|
|
216
322
|
return { content: filteredContent, messages: result.messages };
|
|
217
323
|
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Emits a single {@link GraphEvents.ON_SUBAGENT_UPDATE} envelope through the
|
|
327
|
+
* parent's handler registry. Silent no-op when no parent registry is set.
|
|
328
|
+
* Errors are swallowed — update events are observational.
|
|
329
|
+
*/
|
|
330
|
+
private async emitSubagentUpdate(
|
|
331
|
+
parentRegistry: HandlerRegistry,
|
|
332
|
+
args: {
|
|
333
|
+
childRunId: string;
|
|
334
|
+
subagentType: string;
|
|
335
|
+
subagentAgentId: string;
|
|
336
|
+
parentToolCallId?: string;
|
|
337
|
+
phase: SubagentUpdatePhase;
|
|
338
|
+
data?: unknown;
|
|
339
|
+
label?: string;
|
|
340
|
+
}
|
|
341
|
+
): Promise<void> {
|
|
342
|
+
const handler = parentRegistry.getHandler(GraphEvents.ON_SUBAGENT_UPDATE);
|
|
343
|
+
if (!handler) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const event: SubagentUpdateEvent = {
|
|
347
|
+
runId: this.parentRunId,
|
|
348
|
+
subagentRunId: args.childRunId,
|
|
349
|
+
subagentType: args.subagentType,
|
|
350
|
+
subagentAgentId: args.subagentAgentId,
|
|
351
|
+
parentAgentId: this.parentAgentId,
|
|
352
|
+
parentToolCallId: args.parentToolCallId,
|
|
353
|
+
phase: args.phase,
|
|
354
|
+
data: args.data,
|
|
355
|
+
label: args.label,
|
|
356
|
+
timestamp: new Date().toISOString(),
|
|
357
|
+
};
|
|
358
|
+
try {
|
|
359
|
+
await handler.handle(GraphEvents.ON_SUBAGENT_UPDATE, event);
|
|
360
|
+
} catch {
|
|
361
|
+
/* observational — swallow */
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Builds a BaseCallbackHandler that intercepts the child graph's custom
|
|
367
|
+
* events. Routing rules:
|
|
368
|
+
* - `ON_TOOL_EXECUTE` → forwarded as-is to the parent's ON_TOOL_EXECUTE
|
|
369
|
+
* handler (so event-driven tools work identically for child and parent).
|
|
370
|
+
* - `ON_RUN_STEP` / `ON_RUN_STEP_DELTA` / `ON_RUN_STEP_COMPLETED` /
|
|
371
|
+
* `ON_MESSAGE_DELTA` / `ON_REASONING_DELTA` → wrapped in a
|
|
372
|
+
* {@link GraphEvents.ON_SUBAGENT_UPDATE} envelope with a human-readable
|
|
373
|
+
* label, delivered to the parent's subagent-update handler.
|
|
374
|
+
* - Everything else → ignored (keeps parent's UI scoped to the events it
|
|
375
|
+
* cares about; host apps can extend by registering more phases).
|
|
376
|
+
*/
|
|
377
|
+
private createForwarderCallback(args: {
|
|
378
|
+
parentRegistry: HandlerRegistry;
|
|
379
|
+
subagentType: string;
|
|
380
|
+
subagentAgentId: string;
|
|
381
|
+
childRunId: string;
|
|
382
|
+
parentToolCallId?: string;
|
|
383
|
+
}): BaseCallbackHandler {
|
|
384
|
+
const {
|
|
385
|
+
parentRegistry,
|
|
386
|
+
subagentType,
|
|
387
|
+
subagentAgentId,
|
|
388
|
+
childRunId,
|
|
389
|
+
parentToolCallId,
|
|
390
|
+
} = args;
|
|
391
|
+
const parentRunId = this.parentRunId;
|
|
392
|
+
const parentAgentId = this.parentAgentId;
|
|
393
|
+
|
|
394
|
+
const wrap = async (
|
|
395
|
+
eventName: string,
|
|
396
|
+
phase: SubagentUpdatePhase,
|
|
397
|
+
data: unknown
|
|
398
|
+
): Promise<void> => {
|
|
399
|
+
const handler = parentRegistry.getHandler(GraphEvents.ON_SUBAGENT_UPDATE);
|
|
400
|
+
if (!handler) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const event: SubagentUpdateEvent = {
|
|
404
|
+
runId: parentRunId,
|
|
405
|
+
subagentRunId: childRunId,
|
|
406
|
+
subagentType,
|
|
407
|
+
subagentAgentId,
|
|
408
|
+
parentAgentId,
|
|
409
|
+
parentToolCallId,
|
|
410
|
+
phase,
|
|
411
|
+
data,
|
|
412
|
+
label: summarizeEvent(eventName, data),
|
|
413
|
+
timestamp: new Date().toISOString(),
|
|
414
|
+
};
|
|
415
|
+
try {
|
|
416
|
+
await handler.handle(GraphEvents.ON_SUBAGENT_UPDATE, event);
|
|
417
|
+
} catch {
|
|
418
|
+
/* observational — swallow */
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const handler = BaseCallbackHandler.fromMethods({
|
|
423
|
+
[Callback.CUSTOM_EVENT]: async (
|
|
424
|
+
eventName: string,
|
|
425
|
+
data: unknown
|
|
426
|
+
): Promise<void> => {
|
|
427
|
+
if (eventName === GraphEvents.ON_TOOL_EXECUTE) {
|
|
428
|
+
const toolHandler = parentRegistry.getHandler(
|
|
429
|
+
GraphEvents.ON_TOOL_EXECUTE
|
|
430
|
+
);
|
|
431
|
+
if (toolHandler) {
|
|
432
|
+
await toolHandler.handle(
|
|
433
|
+
GraphEvents.ON_TOOL_EXECUTE,
|
|
434
|
+
data as ToolExecuteBatchRequest
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* We also surface a short notice in the subagent-update stream so
|
|
439
|
+
* the UI can show "calling <tool>" for each tool the child spawns.
|
|
440
|
+
*/
|
|
441
|
+
await wrap(eventName, 'run_step', data);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (eventName === GraphEvents.ON_RUN_STEP) {
|
|
446
|
+
await wrap(eventName, 'run_step', data);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (eventName === GraphEvents.ON_RUN_STEP_DELTA) {
|
|
450
|
+
await wrap(eventName, 'run_step_delta', data);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
454
|
+
await wrap(eventName, 'run_step_completed', data);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (eventName === GraphEvents.ON_MESSAGE_DELTA) {
|
|
458
|
+
await wrap(eventName, 'message_delta', data);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (eventName === GraphEvents.ON_REASONING_DELTA) {
|
|
462
|
+
await wrap(eventName, 'reasoning_delta', data);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
/**
|
|
468
|
+
* `awaitHandlers = true` is required so the child's `ToolNode` actually
|
|
469
|
+
* blocks on the parent's `ON_TOOL_EXECUTE` handler until it resolves
|
|
470
|
+
* the batch request. The same flag applies to observational events
|
|
471
|
+
* (message_delta, run_step, …), which means a slow
|
|
472
|
+
* `ON_SUBAGENT_UPDATE` handler on the host serializes the child
|
|
473
|
+
* stream. If host-side latency becomes a concern, a future
|
|
474
|
+
* refinement could split operational and observational events into
|
|
475
|
+
* separate callback handlers with distinct await semantics.
|
|
476
|
+
*/
|
|
477
|
+
handler.awaitHandlers = true;
|
|
478
|
+
return handler;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Produces a short single-line label for an arbitrary forwarded child event.
|
|
484
|
+
* Used to populate {@link SubagentUpdateEvent.label} so the host UI can show
|
|
485
|
+
* a compact status ticker without parsing the raw payload.
|
|
486
|
+
*/
|
|
487
|
+
export function summarizeEvent(eventName: string, data: unknown): string {
|
|
488
|
+
if (eventName === GraphEvents.ON_TOOL_EXECUTE) {
|
|
489
|
+
const req = data as { toolCalls?: Array<{ name?: string }> };
|
|
490
|
+
const names = (req.toolCalls ?? [])
|
|
491
|
+
.map((c) => c.name)
|
|
492
|
+
.filter((n): n is string => typeof n === 'string');
|
|
493
|
+
return names.length > 0 ? `Calling ${names.join(', ')}` : 'Calling tool';
|
|
494
|
+
}
|
|
495
|
+
if (eventName === GraphEvents.ON_RUN_STEP) {
|
|
496
|
+
const step = data as {
|
|
497
|
+
type?: string;
|
|
498
|
+
stepDetails?: { type?: string; tool_calls?: Array<{ name?: string }> };
|
|
499
|
+
};
|
|
500
|
+
const detailType = step.stepDetails?.type ?? step.type ?? 'step';
|
|
501
|
+
if (detailType === 'tool_calls') {
|
|
502
|
+
const names = (step.stepDetails?.tool_calls ?? [])
|
|
503
|
+
.map((c) => c.name)
|
|
504
|
+
.filter((n): n is string => typeof n === 'string');
|
|
505
|
+
return names.length > 0
|
|
506
|
+
? `Using tool: ${names.join(', ')}`
|
|
507
|
+
: 'Planning tool call';
|
|
508
|
+
}
|
|
509
|
+
if (detailType === 'message_creation') {
|
|
510
|
+
return 'Thinking…';
|
|
511
|
+
}
|
|
512
|
+
return `Step: ${detailType}`;
|
|
513
|
+
}
|
|
514
|
+
if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
515
|
+
const step = data as {
|
|
516
|
+
result?: {
|
|
517
|
+
type?: string;
|
|
518
|
+
tool_call?: { name?: string; output?: string };
|
|
519
|
+
};
|
|
520
|
+
};
|
|
521
|
+
const tool = step.result?.tool_call;
|
|
522
|
+
if (tool?.name != null && tool.name !== '') {
|
|
523
|
+
return `Tool ${tool.name} complete`;
|
|
524
|
+
}
|
|
525
|
+
return 'Step complete';
|
|
526
|
+
}
|
|
527
|
+
if (eventName === GraphEvents.ON_MESSAGE_DELTA) {
|
|
528
|
+
return 'Streaming…';
|
|
529
|
+
}
|
|
530
|
+
return eventName;
|
|
218
531
|
}
|
|
219
532
|
|
|
220
533
|
/**
|
|
@@ -300,11 +613,16 @@ export function resolveSubagentConfigs(
|
|
|
300
613
|
|
|
301
614
|
/**
|
|
302
615
|
* Build child AgentInputs from a resolved config, stripping nesting and
|
|
303
|
-
* event-driven fields. When `allowNested: true`, the child's
|
|
616
|
+
* (optionally) event-driven fields. When `allowNested: true`, the child's
|
|
304
617
|
* `maxSubagentDepth` is decremented so that depth is consumed as the call
|
|
305
618
|
* chain deepens across graph boundaries — the parent's executor-level check
|
|
306
619
|
* alone cannot see into the child graph's separate executor.
|
|
307
620
|
*
|
|
621
|
+
* When `keepToolDefinitions` is `true`, the child retains the parent's
|
|
622
|
+
* `toolDefinitions` so event-driven tools remain usable. This is only safe
|
|
623
|
+
* when the caller has wired a forwarder for `ON_TOOL_EXECUTE` to a
|
|
624
|
+
* registered handler — otherwise the child will hang on tool dispatch.
|
|
625
|
+
*
|
|
308
626
|
* @remarks Advanced utility: exported primarily for testing and by
|
|
309
627
|
* {@link SubagentExecutor}. Host applications configuring subagents should
|
|
310
628
|
* not need to call this directly — it is invoked internally when a subagent
|
|
@@ -316,13 +634,27 @@ export function resolveSubagentConfigs(
|
|
|
316
634
|
export function buildChildInputs(
|
|
317
635
|
config: ResolvedSubagentConfig,
|
|
318
636
|
childAgentId: string,
|
|
319
|
-
parentMaxDepth: number
|
|
637
|
+
parentMaxDepth: number,
|
|
638
|
+
keepToolDefinitions: boolean = false
|
|
320
639
|
): AgentInputs {
|
|
321
640
|
const { agentInputs } = config;
|
|
322
641
|
const childInputs: AgentInputs = {
|
|
323
642
|
...agentInputs,
|
|
324
643
|
agentId: childAgentId,
|
|
325
|
-
toolDefinitions:
|
|
644
|
+
toolDefinitions: keepToolDefinitions
|
|
645
|
+
? agentInputs.toolDefinitions
|
|
646
|
+
: undefined,
|
|
647
|
+
/**
|
|
648
|
+
* Subagents run in an isolated context by contract. Parent-run-scoped
|
|
649
|
+
* fields that would otherwise survive the shallow-spread clone — the
|
|
650
|
+
* cross-run conversation summary and the prior-turn tool-discovery
|
|
651
|
+
* set — are cleared here so the child starts fresh. Host applications
|
|
652
|
+
* that want a subagent to see parent context must thread it in
|
|
653
|
+
* explicitly (e.g. via the `description` argument to the subagent
|
|
654
|
+
* tool), not via inherited state.
|
|
655
|
+
*/
|
|
656
|
+
initialSummary: undefined,
|
|
657
|
+
discoveredTools: undefined,
|
|
326
658
|
};
|
|
327
659
|
|
|
328
660
|
if (config.allowNested === true) {
|
package/src/types/graph.ts
CHANGED
|
@@ -18,7 +18,13 @@ import type {
|
|
|
18
18
|
import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
|
|
19
19
|
import type { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
20
20
|
import type { GoogleAIToolType } from '@langchain/google-common';
|
|
21
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
ToolMap,
|
|
23
|
+
ToolEndEvent,
|
|
24
|
+
GenericTool,
|
|
25
|
+
LCTool,
|
|
26
|
+
ToolExecuteBatchRequest,
|
|
27
|
+
} from '@/types/tools';
|
|
22
28
|
import type { Providers, Callback, GraphNodeKeys } from '@/common';
|
|
23
29
|
import type { StandardGraph, MultiAgentGraph } from '@/graphs';
|
|
24
30
|
import type { ClientOptions } from '@/types/llm';
|
|
@@ -105,7 +111,9 @@ export interface EventHandler {
|
|
|
105
111
|
| SummarizeStartEvent
|
|
106
112
|
| SummarizeDeltaEvent
|
|
107
113
|
| SummarizeCompleteEvent
|
|
114
|
+
| SubagentUpdateEvent
|
|
108
115
|
| AgentLogEvent
|
|
116
|
+
| ToolExecuteBatchRequest
|
|
109
117
|
| { result: ToolEndEvent },
|
|
110
118
|
metadata?: Record<string, unknown>,
|
|
111
119
|
graph?: StandardGraph | MultiAgentGraph
|
|
@@ -411,6 +419,50 @@ export type ResolvedSubagentConfig = SubagentConfig & {
|
|
|
411
419
|
agentInputs: AgentInputs;
|
|
412
420
|
};
|
|
413
421
|
|
|
422
|
+
/** Lifecycle phase carried on {@link SubagentUpdateEvent}. */
|
|
423
|
+
export type SubagentUpdatePhase =
|
|
424
|
+
| 'start'
|
|
425
|
+
| 'run_step'
|
|
426
|
+
| 'run_step_delta'
|
|
427
|
+
| 'run_step_completed'
|
|
428
|
+
| 'message_delta'
|
|
429
|
+
| 'reasoning_delta'
|
|
430
|
+
| 'stop'
|
|
431
|
+
| 'error';
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Wrapper event emitted when a subagent's child graph dispatches activity.
|
|
435
|
+
* Lets hosts show subagent progress in a UI surface separate from the parent
|
|
436
|
+
* conversation without having to untangle events by agent ID.
|
|
437
|
+
*/
|
|
438
|
+
export interface SubagentUpdateEvent {
|
|
439
|
+
/** Parent run ID. */
|
|
440
|
+
runId: string;
|
|
441
|
+
/** Child run ID (unique per subagent execution). */
|
|
442
|
+
subagentRunId: string;
|
|
443
|
+
/**
|
|
444
|
+
* Parent-side `tool_call_id` for the `subagent` tool invocation that
|
|
445
|
+
* triggered this run. Stable for the duration of the child; lets hosts
|
|
446
|
+
* correlate updates deterministically instead of inferring by ordering.
|
|
447
|
+
* Omitted when the executor was invoked outside of a tool-call context.
|
|
448
|
+
*/
|
|
449
|
+
parentToolCallId?: string;
|
|
450
|
+
/** Subagent `type` identifier from the SubagentConfig. */
|
|
451
|
+
subagentType: string;
|
|
452
|
+
/** Child agent ID assigned to this subagent execution. */
|
|
453
|
+
subagentAgentId: string;
|
|
454
|
+
/** Parent agent ID that spawned this subagent. */
|
|
455
|
+
parentAgentId?: string;
|
|
456
|
+
/** Lifecycle phase carried by this update. */
|
|
457
|
+
phase: SubagentUpdatePhase;
|
|
458
|
+
/** Underlying event payload (shape depends on phase). */
|
|
459
|
+
data?: unknown;
|
|
460
|
+
/** Short human-readable description. Hosts can render this directly. */
|
|
461
|
+
label?: string;
|
|
462
|
+
/** ISO timestamp for ordering / display. */
|
|
463
|
+
timestamp: string;
|
|
464
|
+
}
|
|
465
|
+
|
|
414
466
|
export interface AgentInputs {
|
|
415
467
|
agentId: string;
|
|
416
468
|
/** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */
|