@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.
Files changed (96) hide show
  1. package/dist/cjs/graphs/Graph.cjs +25 -1
  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/stream.cjs +115 -8
  12. package/dist/cjs/stream.cjs.map +1 -1
  13. package/dist/cjs/tools/BashExecutor.cjs +10 -9
  14. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  15. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +12 -8
  16. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  17. package/dist/cjs/tools/CodeExecutor.cjs +35 -11
  18. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  19. package/dist/cjs/tools/CodeSessionFileSummary.cjs +63 -0
  20. package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -0
  21. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -12
  22. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  23. package/dist/cjs/tools/ToolNode.cjs +32 -12
  24. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  25. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +319 -29
  26. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  27. package/dist/cjs/tools/toolOutputReferences.cjs +8 -0
  28. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  29. package/dist/cjs/utils/events.cjs +3 -1
  30. package/dist/cjs/utils/events.cjs.map +1 -1
  31. package/dist/esm/graphs/Graph.mjs +25 -1
  32. package/dist/esm/graphs/Graph.mjs.map +1 -1
  33. package/dist/esm/hooks/executeHooks.mjs +14 -7
  34. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  35. package/dist/esm/llm/anthropic/index.mjs +9 -3
  36. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  37. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +33 -1
  38. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  39. package/dist/esm/main.mjs +2 -1
  40. package/dist/esm/main.mjs.map +1 -1
  41. package/dist/esm/stream.mjs +115 -8
  42. package/dist/esm/stream.mjs.map +1 -1
  43. package/dist/esm/tools/BashExecutor.mjs +11 -10
  44. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  45. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +13 -9
  46. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  47. package/dist/esm/tools/CodeExecutor.mjs +29 -12
  48. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  49. package/dist/esm/tools/CodeSessionFileSummary.mjs +60 -0
  50. package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -0
  51. package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -13
  52. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  53. package/dist/esm/tools/ToolNode.mjs +32 -12
  54. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  55. package/dist/esm/tools/subagent/SubagentExecutor.mjs +320 -31
  56. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  57. package/dist/esm/tools/toolOutputReferences.mjs +8 -1
  58. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  59. package/dist/esm/utils/events.mjs +3 -1
  60. package/dist/esm/utils/events.mjs.map +1 -1
  61. package/dist/types/graphs/Graph.d.ts +8 -0
  62. package/dist/types/llm/anthropic/index.d.ts +3 -1
  63. package/dist/types/llm/anthropic/utils/message_inputs.d.ts +4 -0
  64. package/dist/types/tools/BashExecutor.d.ts +3 -3
  65. package/dist/types/tools/CodeExecutor.d.ts +10 -3
  66. package/dist/types/tools/CodeSessionFileSummary.d.ts +3 -0
  67. package/dist/types/tools/ProgrammaticToolCalling.d.ts +4 -4
  68. package/dist/types/tools/subagent/SubagentExecutor.d.ts +8 -5
  69. package/dist/types/types/tools.d.ts +11 -3
  70. package/dist/types/utils/events.d.ts +1 -1
  71. package/package.json +1 -1
  72. package/src/__tests__/stream.eagerEventExecution.test.ts +1073 -221
  73. package/src/graphs/Graph.ts +27 -5
  74. package/src/hooks/__tests__/executeHooks.test.ts +38 -0
  75. package/src/hooks/executeHooks.ts +27 -7
  76. package/src/llm/anthropic/index.ts +27 -3
  77. package/src/llm/anthropic/llm.spec.ts +60 -1
  78. package/src/llm/anthropic/utils/message_inputs.ts +46 -0
  79. package/src/specs/subagent.test.ts +87 -1
  80. package/src/stream.ts +163 -12
  81. package/src/tools/BashExecutor.ts +21 -10
  82. package/src/tools/BashProgrammaticToolCalling.ts +21 -9
  83. package/src/tools/CodeExecutor.ts +55 -12
  84. package/src/tools/CodeSessionFileSummary.ts +80 -0
  85. package/src/tools/ProgrammaticToolCalling.ts +25 -12
  86. package/src/tools/ToolNode.ts +142 -116
  87. package/src/tools/__tests__/BashExecutor.test.ts +9 -0
  88. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +43 -0
  89. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +100 -16
  90. package/src/tools/__tests__/SubagentExecutor.test.ts +540 -6
  91. package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +278 -14
  92. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +52 -0
  93. package/src/tools/__tests__/subagentHooks.test.ts +237 -0
  94. package/src/tools/subagent/SubagentExecutor.ts +514 -36
  95. package/src/types/tools.ts +11 -3
  96. 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 the
45
- * spawn-tool call site. Inherited verbatim into the child workflow's
46
- * `configurable` so host-set fields (`requestBody`, `user`,
47
- * `userMCPAuthMap`, etc.) propagate — fixing MCP body-placeholder
48
- * substitution and per-user lookups for subagent tool calls.
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 forwarder = forwardingEnabled
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` verbatim — host-set fields
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
- }): BaseCallbackHandler {
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
- await wrap(eventName, 'run_step', data);
677
+ scheduleWrap(eventName, 'run_step', data);
502
678
  return;
503
679
  }
504
680
 
505
681
  if (eventName === GraphEvents.ON_RUN_STEP) {
506
- await wrap(eventName, 'run_step', data);
682
+ scheduleWrap(eventName, 'run_step', data);
507
683
  return;
508
684
  }
509
685
  if (eventName === GraphEvents.ON_RUN_STEP_DELTA) {
510
- await wrap(eventName, 'run_step_delta', data);
686
+ scheduleWrap(eventName, 'run_step_delta', data);
511
687
  return;
512
688
  }
513
689
  if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {
514
- await wrap(eventName, 'run_step_completed', data);
690
+ scheduleWrap(eventName, 'run_step_completed', data);
515
691
  return;
516
692
  }
517
693
  if (eventName === GraphEvents.ON_MESSAGE_DELTA) {
518
- await wrap(eventName, 'message_delta', data);
694
+ scheduleWrap(eventName, 'message_delta', data);
519
695
  return;
520
696
  }
521
697
  if (eventName === GraphEvents.ON_REASONING_DELTA) {
522
- await wrap(eventName, 'reasoning_delta', data);
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. The same flag applies to observational events
531
- * (message_delta, run_step, …), which means a slow
532
- * `ON_SUBAGENT_UPDATE` handler on the host serializes the child
533
- * stream. If host-side latency becomes a concern, a future
534
- * refinement could split operational and observational events into
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
 
@@ -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 tool-result formatter renders these as
298
- * "Available files" rather than "Generated files" so the LLM doesn't
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
  };