@librechat/agents 3.1.88 → 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 +25 -1
- 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/stream.cjs +115 -8
- package/dist/cjs/stream.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 +32 -12
- 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/cjs/tools/toolOutputReferences.cjs +8 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/events.cjs +3 -1
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +25 -1
- 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/stream.mjs +115 -8
- package/dist/esm/stream.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 +32 -12
- 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/esm/tools/toolOutputReferences.mjs +8 -1
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/events.mjs +3 -1
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +8 -0
- 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 +11 -3
- package/dist/types/utils/events.d.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/stream.eagerEventExecution.test.ts +1073 -221
- package/src/graphs/Graph.ts +27 -5
- 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/specs/subagent.test.ts +87 -1
- package/src/stream.ts +163 -12
- 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 +142 -116
- 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.eagerEventExecution.test.ts +278 -14
- 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 +11 -3
- package/src/utils/events.ts +4 -2
|
@@ -5,30 +5,150 @@ import type { BaseMessage } from '@langchain/core/messages';
|
|
|
5
5
|
import type { Callbacks } from '@langchain/core/callbacks/manager';
|
|
6
6
|
import type {
|
|
7
7
|
AgentInputs,
|
|
8
|
+
MessageDeltaEvent,
|
|
9
|
+
ProcessedToolCall,
|
|
10
|
+
ReasoningDeltaEvent,
|
|
11
|
+
RunStep,
|
|
12
|
+
RunStepDeltaEvent,
|
|
8
13
|
StandardGraphInput,
|
|
9
14
|
ResolvedSubagentConfig,
|
|
15
|
+
StepCompleted,
|
|
10
16
|
SubagentConfig,
|
|
11
17
|
SubagentUpdateEvent,
|
|
12
18
|
SubagentUpdatePhase,
|
|
13
19
|
ToolExecuteBatchRequest,
|
|
20
|
+
ToolCallDelta,
|
|
14
21
|
TokenCounter,
|
|
15
22
|
} from '@/types';
|
|
16
23
|
import type { AggregatedHookResult, HookRegistry } from '@/hooks';
|
|
17
24
|
import type { AgentContext } from '@/agents/AgentContext';
|
|
18
25
|
import type { StandardGraph } from '@/graphs/Graph';
|
|
19
|
-
import { GraphEvents, Callback } from '@/common';
|
|
26
|
+
import { GraphEvents, Callback, StepTypes } from '@/common';
|
|
20
27
|
import type { HandlerRegistry } from '@/events';
|
|
21
28
|
import { executeHooks } from '@/hooks';
|
|
22
29
|
|
|
23
30
|
const DEFAULT_MAX_TURNS = 25;
|
|
24
31
|
const RECURSION_MULTIPLIER = 3;
|
|
25
32
|
const ERROR_MESSAGE_MAX_CHARS = 200;
|
|
33
|
+
const MAX_PENDING_SUBAGENT_UPDATES = 64;
|
|
26
34
|
|
|
27
35
|
const HOOK_FALLBACK: AggregatedHookResult = Object.freeze({
|
|
28
36
|
additionalContexts: [] as string[],
|
|
29
37
|
errors: [] as string[],
|
|
30
38
|
});
|
|
31
39
|
|
|
40
|
+
type SanitizedSubagentToolCall = {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
args?: ToolExecuteBatchRequest['toolCalls'][number]['args'];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type SanitizedSubagentToolExecuteData = {
|
|
47
|
+
toolCalls: SanitizedSubagentToolCall[];
|
|
48
|
+
agentId?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type SanitizedRunStep = Partial<
|
|
52
|
+
Pick<
|
|
53
|
+
RunStep,
|
|
54
|
+
| 'agentId'
|
|
55
|
+
| 'groupId'
|
|
56
|
+
| 'id'
|
|
57
|
+
| 'index'
|
|
58
|
+
| 'runId'
|
|
59
|
+
| 'stepIndex'
|
|
60
|
+
| 'summary'
|
|
61
|
+
| 'type'
|
|
62
|
+
| 'usage'
|
|
63
|
+
>
|
|
64
|
+
> & {
|
|
65
|
+
stepDetails?: SanitizedStepDetails;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type SanitizedStepDetails =
|
|
69
|
+
| {
|
|
70
|
+
type: StepTypes.MESSAGE_CREATION;
|
|
71
|
+
message_creation?: {
|
|
72
|
+
message_id?: string;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
| {
|
|
76
|
+
type: StepTypes.TOOL_CALLS;
|
|
77
|
+
tool_calls?: SanitizedAgentToolCall[];
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
type SanitizedAgentToolCall = {
|
|
81
|
+
id?: string;
|
|
82
|
+
name?: string;
|
|
83
|
+
args?: string | object;
|
|
84
|
+
type?: string;
|
|
85
|
+
function?: {
|
|
86
|
+
name?: string;
|
|
87
|
+
arguments?: string | object;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
type SanitizedRunStepDelta = Partial<Pick<RunStepDeltaEvent, 'id'>> & {
|
|
92
|
+
delta?: SanitizedToolCallDelta;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
type SanitizedToolCallDelta = Partial<
|
|
96
|
+
Pick<ToolCallDelta, 'auth' | 'expires_at' | 'summary' | 'type'>
|
|
97
|
+
> & {
|
|
98
|
+
tool_calls?: SanitizedAgentToolCall[];
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
type SanitizedStepCompleted =
|
|
102
|
+
| {
|
|
103
|
+
id?: string;
|
|
104
|
+
index?: number;
|
|
105
|
+
type: 'tool_call';
|
|
106
|
+
tool_call?: SanitizedProcessedToolCall;
|
|
107
|
+
}
|
|
108
|
+
| {
|
|
109
|
+
type: 'summary';
|
|
110
|
+
summary?: Extract<StepCompleted, { type: 'summary' }>['summary'];
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
type SanitizedProcessedToolCall = Partial<
|
|
114
|
+
Pick<ProcessedToolCall, 'args' | 'id' | 'name' | 'output' | 'progress'>
|
|
115
|
+
>;
|
|
116
|
+
|
|
117
|
+
type SanitizedRunStepCompleted = {
|
|
118
|
+
result?: SanitizedStepCompleted;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
type SanitizedMessageDelta = Partial<Pick<MessageDeltaEvent, 'id'>> & {
|
|
122
|
+
delta?: {
|
|
123
|
+
content?: MessageDeltaEvent['delta']['content'];
|
|
124
|
+
tool_call_ids?: MessageDeltaEvent['delta']['tool_call_ids'];
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
type SanitizedReasoningDelta = Partial<Pick<ReasoningDeltaEvent, 'id'>> & {
|
|
129
|
+
delta?: {
|
|
130
|
+
content?: ReasoningDeltaEvent['delta']['content'];
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
type QueuedSubagentUpdate = {
|
|
135
|
+
eventName: string;
|
|
136
|
+
phase: SubagentUpdatePhase;
|
|
137
|
+
data: unknown;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
type ForwarderCallback = {
|
|
141
|
+
handler: BaseCallbackHandler;
|
|
142
|
+
drain: () => Promise<void>;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const LANGGRAPH_RUNTIME_CONFIG_PREFIX = '__pregel_';
|
|
146
|
+
const LANGGRAPH_CHECKPOINT_CONFIG_KEYS = new Set([
|
|
147
|
+
'checkpoint_id',
|
|
148
|
+
'checkpoint_map',
|
|
149
|
+
'checkpoint_ns',
|
|
150
|
+
]);
|
|
151
|
+
|
|
32
152
|
export type SubagentExecuteParams = {
|
|
33
153
|
description: string;
|
|
34
154
|
subagentType: string;
|
|
@@ -41,11 +161,13 @@ export type SubagentExecuteParams = {
|
|
|
41
161
|
*/
|
|
42
162
|
parentToolCallId?: string;
|
|
43
163
|
/**
|
|
44
|
-
* Snapshot of the parent invocation's `config.configurable` at
|
|
45
|
-
* spawn-tool call site.
|
|
46
|
-
* `
|
|
47
|
-
* `
|
|
48
|
-
*
|
|
164
|
+
* Snapshot of the parent invocation's host `config.configurable` at
|
|
165
|
+
* the spawn-tool call site. Host-set fields (`requestBody`, `user`,
|
|
166
|
+
* `userMCPAuthMap`, etc.) propagate into the child workflow's
|
|
167
|
+
* `configurable` — fixing MCP body-placeholder substitution and
|
|
168
|
+
* per-user lookups for subagent tool calls. LangGraph runtime keys
|
|
169
|
+
* (`__pregel_*`, checkpoint bookkeeping) are intentionally not
|
|
170
|
+
* inherited; the child graph recreates its own runtime config.
|
|
49
171
|
*
|
|
50
172
|
* Inheritance details (verified empirically against LangGraph):
|
|
51
173
|
* - host-set keys propagate as-is into the child's tool dispatches;
|
|
@@ -233,7 +355,7 @@ export class SubagentExecutor {
|
|
|
233
355
|
tokenCounter: this.tokenCounter,
|
|
234
356
|
});
|
|
235
357
|
|
|
236
|
-
const
|
|
358
|
+
const forwarding = forwardingEnabled
|
|
237
359
|
? this.createForwarderCallback({
|
|
238
360
|
parentRegistry: parentRegistry!,
|
|
239
361
|
subagentType,
|
|
@@ -242,6 +364,7 @@ export class SubagentExecutor {
|
|
|
242
364
|
parentToolCallId,
|
|
243
365
|
})
|
|
244
366
|
: undefined;
|
|
367
|
+
const forwarder = forwarding?.handler;
|
|
245
368
|
|
|
246
369
|
if (forwarder) {
|
|
247
370
|
await this.emitSubagentUpdate(parentRegistry!, {
|
|
@@ -276,10 +399,11 @@ export class SubagentExecutor {
|
|
|
276
399
|
*/
|
|
277
400
|
const callbacks: Callbacks = forwarder ? [forwarder] : [];
|
|
278
401
|
/**
|
|
279
|
-
* Inherit the parent's `configurable`
|
|
402
|
+
* Inherit the parent's host `configurable` — host-set fields
|
|
280
403
|
* (`requestBody`, `user`, `userMCPAuthMap`, etc.) AND the run-
|
|
281
404
|
* identity fields (`run_id`, `parent_run_id`, `thread_id`) all
|
|
282
|
-
* propagate.
|
|
405
|
+
* propagate. LangGraph's own runtime keys are excluded because the
|
|
406
|
+
* child graph creates its own scratchpad/checkpoint/abort plumbing.
|
|
283
407
|
*
|
|
284
408
|
* Run-identity propagation is intentional and matches the
|
|
285
409
|
* convention this executor itself already uses for `SubagentStart`
|
|
@@ -304,7 +428,7 @@ export class SubagentExecutor {
|
|
|
304
428
|
* case (synchronous subagents within a single user turn).
|
|
305
429
|
*/
|
|
306
430
|
const inheritedConfigurable: Record<string, unknown> =
|
|
307
|
-
params.parentConfigurable
|
|
431
|
+
sanitizeChildConfigurable(params.parentConfigurable);
|
|
308
432
|
result = await workflow.invoke(
|
|
309
433
|
{ messages: [new HumanMessage(description)] },
|
|
310
434
|
{
|
|
@@ -321,6 +445,7 @@ export class SubagentExecutor {
|
|
|
321
445
|
} catch (error) {
|
|
322
446
|
const errorMessage = truncateErrorMessage(error);
|
|
323
447
|
if (forwarder) {
|
|
448
|
+
await forwarding.drain();
|
|
324
449
|
await this.emitSubagentUpdate(parentRegistry!, {
|
|
325
450
|
childRunId,
|
|
326
451
|
subagentType,
|
|
@@ -367,6 +492,7 @@ export class SubagentExecutor {
|
|
|
367
492
|
}
|
|
368
493
|
|
|
369
494
|
if (forwarder) {
|
|
495
|
+
await forwarding.drain();
|
|
370
496
|
await this.emitSubagentUpdate(parentRegistry!, {
|
|
371
497
|
childRunId,
|
|
372
498
|
subagentType,
|
|
@@ -440,7 +566,7 @@ export class SubagentExecutor {
|
|
|
440
566
|
subagentAgentId: string;
|
|
441
567
|
childRunId: string;
|
|
442
568
|
parentToolCallId?: string;
|
|
443
|
-
}):
|
|
569
|
+
}): ForwarderCallback {
|
|
444
570
|
const {
|
|
445
571
|
parentRegistry,
|
|
446
572
|
subagentType,
|
|
@@ -460,25 +586,75 @@ export class SubagentExecutor {
|
|
|
460
586
|
if (!handler) {
|
|
461
587
|
return;
|
|
462
588
|
}
|
|
463
|
-
const event: SubagentUpdateEvent = {
|
|
464
|
-
runId: parentRunId,
|
|
465
|
-
subagentRunId: childRunId,
|
|
466
|
-
subagentType,
|
|
467
|
-
subagentAgentId,
|
|
468
|
-
parentAgentId,
|
|
469
|
-
parentToolCallId,
|
|
470
|
-
phase,
|
|
471
|
-
data,
|
|
472
|
-
label: summarizeEvent(eventName, data),
|
|
473
|
-
timestamp: new Date().toISOString(),
|
|
474
|
-
};
|
|
475
589
|
try {
|
|
590
|
+
const event: SubagentUpdateEvent = {
|
|
591
|
+
runId: parentRunId,
|
|
592
|
+
subagentRunId: childRunId,
|
|
593
|
+
subagentType,
|
|
594
|
+
subagentAgentId,
|
|
595
|
+
parentAgentId,
|
|
596
|
+
parentToolCallId,
|
|
597
|
+
phase,
|
|
598
|
+
data: sanitizeForwardedSubagentUpdateData(eventName, data),
|
|
599
|
+
label: summarizeEvent(eventName, data),
|
|
600
|
+
timestamp: new Date().toISOString(),
|
|
601
|
+
};
|
|
476
602
|
await handler.handle(GraphEvents.ON_SUBAGENT_UPDATE, event);
|
|
477
603
|
} catch {
|
|
478
604
|
/* observational — swallow */
|
|
479
605
|
}
|
|
480
606
|
};
|
|
481
607
|
|
|
608
|
+
const queuedUpdates: QueuedSubagentUpdate[] = [];
|
|
609
|
+
let drainPromise: Promise<void> | undefined;
|
|
610
|
+
|
|
611
|
+
const enqueue = (update: QueuedSubagentUpdate): void => {
|
|
612
|
+
if (queuedUpdates.length >= MAX_PENDING_SUBAGENT_UPDATES) {
|
|
613
|
+
const dropIndex = queuedUpdates.findIndex((queued) =>
|
|
614
|
+
isDroppableSubagentUpdatePhase(queued.phase)
|
|
615
|
+
);
|
|
616
|
+
if (dropIndex >= 0) {
|
|
617
|
+
queuedUpdates.splice(dropIndex, 1);
|
|
618
|
+
} else if (isDroppableSubagentUpdatePhase(update.phase)) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
queuedUpdates.push(update);
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
const drain = async (): Promise<void> => {
|
|
626
|
+
if (drainPromise != null) {
|
|
627
|
+
await drainPromise;
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
drainPromise = (async (): Promise<void> => {
|
|
631
|
+
while (queuedUpdates.length > 0) {
|
|
632
|
+
const update = queuedUpdates.shift();
|
|
633
|
+
if (update == null) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
await wrap(update.eventName, update.phase, update.data);
|
|
637
|
+
}
|
|
638
|
+
})();
|
|
639
|
+
try {
|
|
640
|
+
await drainPromise;
|
|
641
|
+
} finally {
|
|
642
|
+
drainPromise = undefined;
|
|
643
|
+
if (queuedUpdates.length > 0) {
|
|
644
|
+
await drain();
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
const scheduleWrap = (
|
|
650
|
+
eventName: string,
|
|
651
|
+
phase: SubagentUpdatePhase,
|
|
652
|
+
data: unknown
|
|
653
|
+
): void => {
|
|
654
|
+
enqueue({ eventName, phase, data });
|
|
655
|
+
void drain();
|
|
656
|
+
};
|
|
657
|
+
|
|
482
658
|
const handler = BaseCallbackHandler.fromMethods({
|
|
483
659
|
[Callback.CUSTOM_EVENT]: async (
|
|
484
660
|
eventName: string,
|
|
@@ -498,28 +674,28 @@ export class SubagentExecutor {
|
|
|
498
674
|
* We also surface a short notice in the subagent-update stream so
|
|
499
675
|
* the UI can show "calling <tool>" for each tool the child spawns.
|
|
500
676
|
*/
|
|
501
|
-
|
|
677
|
+
scheduleWrap(eventName, 'run_step', data);
|
|
502
678
|
return;
|
|
503
679
|
}
|
|
504
680
|
|
|
505
681
|
if (eventName === GraphEvents.ON_RUN_STEP) {
|
|
506
|
-
|
|
682
|
+
scheduleWrap(eventName, 'run_step', data);
|
|
507
683
|
return;
|
|
508
684
|
}
|
|
509
685
|
if (eventName === GraphEvents.ON_RUN_STEP_DELTA) {
|
|
510
|
-
|
|
686
|
+
scheduleWrap(eventName, 'run_step_delta', data);
|
|
511
687
|
return;
|
|
512
688
|
}
|
|
513
689
|
if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
514
|
-
|
|
690
|
+
scheduleWrap(eventName, 'run_step_completed', data);
|
|
515
691
|
return;
|
|
516
692
|
}
|
|
517
693
|
if (eventName === GraphEvents.ON_MESSAGE_DELTA) {
|
|
518
|
-
|
|
694
|
+
scheduleWrap(eventName, 'message_delta', data);
|
|
519
695
|
return;
|
|
520
696
|
}
|
|
521
697
|
if (eventName === GraphEvents.ON_REASONING_DELTA) {
|
|
522
|
-
|
|
698
|
+
scheduleWrap(eventName, 'reasoning_delta', data);
|
|
523
699
|
return;
|
|
524
700
|
}
|
|
525
701
|
},
|
|
@@ -527,15 +703,317 @@ export class SubagentExecutor {
|
|
|
527
703
|
/**
|
|
528
704
|
* `awaitHandlers = true` is required so the child's `ToolNode` actually
|
|
529
705
|
* blocks on the parent's `ON_TOOL_EXECUTE` handler until it resolves
|
|
530
|
-
* the batch request.
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
* separate callback handlers with distinct await semantics.
|
|
706
|
+
* the batch request. Observational `ON_SUBAGENT_UPDATE` calls are queued
|
|
707
|
+
* behind a bounded sequential dispatcher so host UI publication cannot
|
|
708
|
+
* backpressure each child emission or run unbounded concurrent publishes.
|
|
709
|
+
* The executor drains this queue before terminal stop/error envelopes to
|
|
710
|
+
* preserve phase ordering.
|
|
536
711
|
*/
|
|
537
712
|
handler.awaitHandlers = true;
|
|
538
|
-
return handler;
|
|
713
|
+
return { handler, drain };
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function sanitizeChildConfigurable(
|
|
718
|
+
parentConfigurable: Record<string, unknown> | undefined
|
|
719
|
+
): Record<string, unknown> {
|
|
720
|
+
if (parentConfigurable == null) {
|
|
721
|
+
return {};
|
|
722
|
+
}
|
|
723
|
+
return Object.fromEntries(
|
|
724
|
+
Object.entries(parentConfigurable).filter(
|
|
725
|
+
([key]) => !isLangGraphRuntimeConfigKey(key)
|
|
726
|
+
)
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function isLangGraphRuntimeConfigKey(key: string): boolean {
|
|
731
|
+
return (
|
|
732
|
+
key.startsWith(LANGGRAPH_RUNTIME_CONFIG_PREFIX) ||
|
|
733
|
+
LANGGRAPH_CHECKPOINT_CONFIG_KEYS.has(key)
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
export function sanitizeForwardedSubagentUpdateData(
|
|
738
|
+
eventName: string,
|
|
739
|
+
data: unknown
|
|
740
|
+
): unknown {
|
|
741
|
+
if (eventName === GraphEvents.ON_TOOL_EXECUTE) {
|
|
742
|
+
return sanitizeToolExecuteUpdateData(data);
|
|
743
|
+
}
|
|
744
|
+
if (eventName === GraphEvents.ON_RUN_STEP) {
|
|
745
|
+
return sanitizeRunStepUpdateData(data);
|
|
746
|
+
}
|
|
747
|
+
if (eventName === GraphEvents.ON_RUN_STEP_DELTA) {
|
|
748
|
+
return sanitizeRunStepDeltaUpdateData(data);
|
|
749
|
+
}
|
|
750
|
+
if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
751
|
+
return sanitizeRunStepCompletedUpdateData(data);
|
|
752
|
+
}
|
|
753
|
+
if (eventName === GraphEvents.ON_MESSAGE_DELTA) {
|
|
754
|
+
return sanitizeMessageDeltaUpdateData(data);
|
|
755
|
+
}
|
|
756
|
+
if (eventName === GraphEvents.ON_REASONING_DELTA) {
|
|
757
|
+
return sanitizeReasoningDeltaUpdateData(data);
|
|
758
|
+
}
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function isDroppableSubagentUpdatePhase(phase: SubagentUpdatePhase): boolean {
|
|
763
|
+
return (
|
|
764
|
+
phase === 'message_delta' ||
|
|
765
|
+
phase === 'reasoning_delta' ||
|
|
766
|
+
phase === 'run_step_delta'
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function sanitizeToolExecuteUpdateData(
|
|
771
|
+
data: unknown
|
|
772
|
+
): SanitizedSubagentToolExecuteData {
|
|
773
|
+
const request = data as Partial<ToolExecuteBatchRequest>;
|
|
774
|
+
const toolCalls = Array.isArray(request.toolCalls)
|
|
775
|
+
? request.toolCalls.map(sanitizeToolCallForUpdate)
|
|
776
|
+
: [];
|
|
777
|
+
const sanitized: SanitizedSubagentToolExecuteData = { toolCalls };
|
|
778
|
+
if (typeof request.agentId === 'string') {
|
|
779
|
+
sanitized.agentId = request.agentId;
|
|
780
|
+
}
|
|
781
|
+
return sanitized;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function sanitizeToolCallForUpdate(
|
|
785
|
+
call: ToolExecuteBatchRequest['toolCalls'][number]
|
|
786
|
+
): SanitizedSubagentToolCall {
|
|
787
|
+
const sanitized: SanitizedSubagentToolCall = {
|
|
788
|
+
id: call.id,
|
|
789
|
+
name: call.name,
|
|
790
|
+
args: call.args,
|
|
791
|
+
};
|
|
792
|
+
return sanitized;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function sanitizeRunStepUpdateData(data: unknown): SanitizedRunStep | undefined {
|
|
796
|
+
if (!isObjectLike(data)) {
|
|
797
|
+
return undefined;
|
|
798
|
+
}
|
|
799
|
+
const step = data as Partial<RunStep>;
|
|
800
|
+
const sanitized: SanitizedRunStep = {};
|
|
801
|
+
assignString(sanitized, 'agentId', step.agentId);
|
|
802
|
+
assignNumber(sanitized, 'groupId', step.groupId);
|
|
803
|
+
assignString(sanitized, 'id', step.id);
|
|
804
|
+
assignNumber(sanitized, 'index', step.index);
|
|
805
|
+
assignString(sanitized, 'runId', step.runId);
|
|
806
|
+
assignNumber(sanitized, 'stepIndex', step.stepIndex);
|
|
807
|
+
assignString(sanitized, 'type', step.type);
|
|
808
|
+
if (step.summary !== undefined) {
|
|
809
|
+
sanitized.summary = step.summary;
|
|
810
|
+
}
|
|
811
|
+
if (step.usage !== undefined) {
|
|
812
|
+
sanitized.usage = step.usage;
|
|
813
|
+
}
|
|
814
|
+
sanitized.stepDetails = sanitizeStepDetails(step.stepDetails);
|
|
815
|
+
return sanitized;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function sanitizeRunStepDeltaUpdateData(
|
|
819
|
+
data: unknown
|
|
820
|
+
): SanitizedRunStepDelta | undefined {
|
|
821
|
+
if (!isObjectLike(data)) {
|
|
822
|
+
return undefined;
|
|
823
|
+
}
|
|
824
|
+
const event = data as Partial<RunStepDeltaEvent>;
|
|
825
|
+
const sanitized: SanitizedRunStepDelta = {};
|
|
826
|
+
assignString(sanitized, 'id', event.id);
|
|
827
|
+
sanitized.delta = sanitizeToolCallDelta(event.delta);
|
|
828
|
+
return sanitized;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function sanitizeRunStepCompletedUpdateData(
|
|
832
|
+
data: unknown
|
|
833
|
+
): SanitizedRunStepCompleted | undefined {
|
|
834
|
+
if (!isObjectLike(data)) {
|
|
835
|
+
return undefined;
|
|
836
|
+
}
|
|
837
|
+
const event = data as { result?: unknown };
|
|
838
|
+
return { result: sanitizeStepCompleted(event.result) };
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function sanitizeMessageDeltaUpdateData(
|
|
842
|
+
data: unknown
|
|
843
|
+
): SanitizedMessageDelta | undefined {
|
|
844
|
+
if (!isObjectLike(data)) {
|
|
845
|
+
return undefined;
|
|
846
|
+
}
|
|
847
|
+
const event = data as Partial<MessageDeltaEvent>;
|
|
848
|
+
const sanitized: SanitizedMessageDelta = {};
|
|
849
|
+
assignString(sanitized, 'id', event.id);
|
|
850
|
+
if (event.delta != null) {
|
|
851
|
+
sanitized.delta = {};
|
|
852
|
+
if (event.delta.content !== undefined) {
|
|
853
|
+
sanitized.delta.content = event.delta.content;
|
|
854
|
+
}
|
|
855
|
+
if (event.delta.tool_call_ids !== undefined) {
|
|
856
|
+
sanitized.delta.tool_call_ids = event.delta.tool_call_ids;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return sanitized;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function sanitizeReasoningDeltaUpdateData(
|
|
863
|
+
data: unknown
|
|
864
|
+
): SanitizedReasoningDelta | undefined {
|
|
865
|
+
if (!isObjectLike(data)) {
|
|
866
|
+
return undefined;
|
|
867
|
+
}
|
|
868
|
+
const event = data as Partial<ReasoningDeltaEvent>;
|
|
869
|
+
const sanitized: SanitizedReasoningDelta = {};
|
|
870
|
+
assignString(sanitized, 'id', event.id);
|
|
871
|
+
if (event.delta?.content !== undefined) {
|
|
872
|
+
sanitized.delta = { content: event.delta.content };
|
|
873
|
+
}
|
|
874
|
+
return sanitized;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function sanitizeStepDetails(stepDetails: unknown): SanitizedStepDetails | undefined {
|
|
878
|
+
if (!isObjectLike(stepDetails)) {
|
|
879
|
+
return undefined;
|
|
880
|
+
}
|
|
881
|
+
const rawDetails = stepDetails as {
|
|
882
|
+
message_creation?: { message_id?: unknown };
|
|
883
|
+
tool_calls?: unknown[];
|
|
884
|
+
type?: unknown;
|
|
885
|
+
};
|
|
886
|
+
if (rawDetails.type === StepTypes.MESSAGE_CREATION) {
|
|
887
|
+
const sanitized: SanitizedStepDetails = {
|
|
888
|
+
type: StepTypes.MESSAGE_CREATION,
|
|
889
|
+
};
|
|
890
|
+
const messageId = rawDetails.message_creation?.message_id;
|
|
891
|
+
if (typeof messageId === 'string') {
|
|
892
|
+
sanitized.message_creation = { message_id: messageId };
|
|
893
|
+
}
|
|
894
|
+
return sanitized;
|
|
895
|
+
}
|
|
896
|
+
if (rawDetails.type === StepTypes.TOOL_CALLS) {
|
|
897
|
+
const sanitized: SanitizedStepDetails = {
|
|
898
|
+
type: StepTypes.TOOL_CALLS,
|
|
899
|
+
};
|
|
900
|
+
if (Array.isArray(rawDetails.tool_calls)) {
|
|
901
|
+
sanitized.tool_calls = rawDetails.tool_calls.map(sanitizeAgentToolCall);
|
|
902
|
+
}
|
|
903
|
+
return sanitized;
|
|
904
|
+
}
|
|
905
|
+
return undefined;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function sanitizeToolCallDelta(
|
|
909
|
+
delta: ToolCallDelta | undefined
|
|
910
|
+
): SanitizedToolCallDelta | undefined {
|
|
911
|
+
if (!isObjectLike(delta)) {
|
|
912
|
+
return undefined;
|
|
913
|
+
}
|
|
914
|
+
const sanitized: SanitizedToolCallDelta = {};
|
|
915
|
+
assignString(sanitized, 'auth', delta.auth);
|
|
916
|
+
assignNumber(sanitized, 'expires_at', delta.expires_at);
|
|
917
|
+
assignString(sanitized, 'type', delta.type);
|
|
918
|
+
if (delta.summary !== undefined) {
|
|
919
|
+
sanitized.summary = delta.summary;
|
|
920
|
+
}
|
|
921
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
922
|
+
sanitized.tool_calls = delta.tool_calls.map(sanitizeAgentToolCall);
|
|
923
|
+
}
|
|
924
|
+
return sanitized;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function sanitizeStepCompleted(data: unknown): SanitizedStepCompleted | undefined {
|
|
928
|
+
if (!isObjectLike(data)) {
|
|
929
|
+
return undefined;
|
|
930
|
+
}
|
|
931
|
+
const completed = data as Partial<StepCompleted> & {
|
|
932
|
+
id?: unknown;
|
|
933
|
+
index?: unknown;
|
|
934
|
+
tool_call?: unknown;
|
|
935
|
+
};
|
|
936
|
+
if (completed.type === 'summary') {
|
|
937
|
+
return {
|
|
938
|
+
type: 'summary',
|
|
939
|
+
summary: completed.summary,
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
if (completed.type !== 'tool_call') {
|
|
943
|
+
return undefined;
|
|
944
|
+
}
|
|
945
|
+
const sanitized: SanitizedStepCompleted = { type: 'tool_call' };
|
|
946
|
+
assignString(sanitized, 'id', completed.id);
|
|
947
|
+
assignNumber(sanitized, 'index', completed.index);
|
|
948
|
+
sanitized.tool_call = sanitizeProcessedToolCall(completed.tool_call);
|
|
949
|
+
return sanitized;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function sanitizeProcessedToolCall(
|
|
953
|
+
toolCall: unknown
|
|
954
|
+
): SanitizedProcessedToolCall | undefined {
|
|
955
|
+
if (!isObjectLike(toolCall)) {
|
|
956
|
+
return undefined;
|
|
957
|
+
}
|
|
958
|
+
const call = toolCall as Partial<ProcessedToolCall>;
|
|
959
|
+
const sanitized: SanitizedProcessedToolCall = {};
|
|
960
|
+
assignString(sanitized, 'id', call.id);
|
|
961
|
+
assignString(sanitized, 'name', call.name);
|
|
962
|
+
if (call.args !== undefined) {
|
|
963
|
+
sanitized.args = call.args;
|
|
964
|
+
}
|
|
965
|
+
assignString(sanitized, 'output', call.output);
|
|
966
|
+
assignNumber(sanitized, 'progress', call.progress);
|
|
967
|
+
return sanitized;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function sanitizeAgentToolCall(toolCall: unknown): SanitizedAgentToolCall {
|
|
971
|
+
if (!isObjectLike(toolCall)) {
|
|
972
|
+
return {};
|
|
973
|
+
}
|
|
974
|
+
const call = toolCall as SanitizedAgentToolCall;
|
|
975
|
+
const sanitized: SanitizedAgentToolCall = {};
|
|
976
|
+
assignString(sanitized, 'id', call.id);
|
|
977
|
+
assignString(sanitized, 'name', call.name);
|
|
978
|
+
assignString(sanitized, 'type', call.type);
|
|
979
|
+
if (call.args !== undefined) {
|
|
980
|
+
sanitized.args = call.args;
|
|
981
|
+
}
|
|
982
|
+
if (isObjectLike(call.function)) {
|
|
983
|
+
const fn: SanitizedAgentToolCall['function'] = {};
|
|
984
|
+
assignString(fn, 'name', call.function.name);
|
|
985
|
+
if (
|
|
986
|
+
typeof call.function.arguments === 'string' ||
|
|
987
|
+
isObjectLike(call.function.arguments)
|
|
988
|
+
) {
|
|
989
|
+
fn.arguments = call.function.arguments;
|
|
990
|
+
}
|
|
991
|
+
sanitized.function = fn;
|
|
992
|
+
}
|
|
993
|
+
return sanitized;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function isObjectLike(value: unknown): value is object {
|
|
997
|
+
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function assignString<T extends object, K extends keyof T>(
|
|
1001
|
+
target: T,
|
|
1002
|
+
key: K,
|
|
1003
|
+
value: unknown
|
|
1004
|
+
): void {
|
|
1005
|
+
if (typeof value === 'string') {
|
|
1006
|
+
target[key] = value as T[K];
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function assignNumber<T extends object, K extends keyof T>(
|
|
1011
|
+
target: T,
|
|
1012
|
+
key: K,
|
|
1013
|
+
value: unknown
|
|
1014
|
+
): void {
|
|
1015
|
+
if (typeof value === 'number') {
|
|
1016
|
+
target[key] = value as T[K];
|
|
539
1017
|
}
|
|
540
1018
|
}
|
|
541
1019
|
|
package/src/types/tools.ts
CHANGED
|
@@ -49,6 +49,13 @@ export type EagerEventToolExecution = {
|
|
|
49
49
|
args: Record<string, unknown>;
|
|
50
50
|
request: ToolCallRequest;
|
|
51
51
|
promise: Promise<EagerEventToolExecutionOutcome>;
|
|
52
|
+
/**
|
|
53
|
+
* True when the streaming eager path already emitted the user-visible
|
|
54
|
+
* ON_RUN_STEP_COMPLETED event for this call. ToolNode still consumes the
|
|
55
|
+
* result later to mutate graph state in provider-safe order, but skips
|
|
56
|
+
* duplicate completion emission.
|
|
57
|
+
*/
|
|
58
|
+
completionDispatched?: boolean;
|
|
52
59
|
};
|
|
53
60
|
|
|
54
61
|
export type EagerEventToolCallChunkState = {
|
|
@@ -172,6 +179,8 @@ export type ToolEndEvent = {
|
|
|
172
179
|
/** The content index of the tool call */
|
|
173
180
|
index: number;
|
|
174
181
|
type?: 'tool_call';
|
|
182
|
+
/** True when the stream eager path surfaced this completion before ToolNode finalized graph state. */
|
|
183
|
+
eager?: boolean;
|
|
175
184
|
};
|
|
176
185
|
|
|
177
186
|
/**
|
|
@@ -294,9 +303,8 @@ export type FileRef = {
|
|
|
294
303
|
* `true` when the codeapi sandbox echoed this entry as an unchanged
|
|
295
304
|
* passthrough of an input the caller already owns (skill files,
|
|
296
305
|
* downloaded inputs whose hash matched the baseline, inherited
|
|
297
|
-
* `.dirkeep` markers). The
|
|
298
|
-
*
|
|
299
|
-
* conflate infrastructure inputs with newly-produced outputs.
|
|
306
|
+
* `.dirkeep` markers). The host can use this flag to skip
|
|
307
|
+
* post-processing work for files the caller already owns.
|
|
300
308
|
*/
|
|
301
309
|
inherited?: true;
|
|
302
310
|
};
|