@librechat/agents 3.0.53 → 3.0.55

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 (38) hide show
  1. package/dist/cjs/graphs/Graph.cjs +28 -2
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/graphs/MultiAgentGraph.cjs +108 -0
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  5. package/dist/cjs/messages/format.cjs +22 -1
  6. package/dist/cjs/messages/format.cjs.map +1 -1
  7. package/dist/cjs/stream.cjs +22 -11
  8. package/dist/cjs/stream.cjs.map +1 -1
  9. package/dist/esm/graphs/Graph.mjs +28 -2
  10. package/dist/esm/graphs/Graph.mjs.map +1 -1
  11. package/dist/esm/graphs/MultiAgentGraph.mjs +108 -0
  12. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  13. package/dist/esm/messages/format.mjs +22 -1
  14. package/dist/esm/messages/format.mjs.map +1 -1
  15. package/dist/esm/stream.mjs +22 -11
  16. package/dist/esm/stream.mjs.map +1 -1
  17. package/dist/types/graphs/Graph.d.ts +14 -0
  18. package/dist/types/graphs/MultiAgentGraph.d.ts +41 -0
  19. package/dist/types/messages/format.d.ts +8 -2
  20. package/dist/types/types/stream.d.ts +22 -2
  21. package/package.json +3 -2
  22. package/src/graphs/Graph.ts +30 -2
  23. package/src/graphs/MultiAgentGraph.ts +119 -0
  24. package/src/messages/format.ts +33 -3
  25. package/src/messages/formatAgentMessages.test.ts +168 -0
  26. package/src/scripts/multi-agent-chain.ts +59 -6
  27. package/src/scripts/multi-agent-parallel-start.ts +39 -6
  28. package/src/scripts/multi-agent-parallel.ts +61 -10
  29. package/src/scripts/multi-agent-sequence.ts +6 -1
  30. package/src/scripts/parallel-asymmetric-tools-test.ts +274 -0
  31. package/src/scripts/parallel-full-metadata-test.ts +240 -0
  32. package/src/scripts/parallel-tools-test.ts +340 -0
  33. package/src/scripts/sequential-full-metadata-test.ts +197 -0
  34. package/src/scripts/single-agent-metadata-test.ts +198 -0
  35. package/src/scripts/test-thinking-handoff.ts +8 -0
  36. package/src/scripts/tools.ts +31 -11
  37. package/src/stream.ts +25 -14
  38. package/src/types/stream.ts +23 -4
@@ -14,6 +14,7 @@ import type {
14
14
  ExtendedMessageContent,
15
15
  MessageContentComplex,
16
16
  ReasoningContentText,
17
+ ContentMetadata,
17
18
  ToolCallContent,
18
19
  ToolCallPart,
19
20
  TPayload,
@@ -626,23 +627,31 @@ export const labelContentByAgent = (
626
627
  * @param payload - The array of messages to format.
627
628
  * @param indexTokenCountMap - Optional map of message indices to token counts.
628
629
  * @param tools - Optional set of tool names that are allowed in the request.
630
+ * @param options - Optional configuration for agent filtering.
631
+ * @param options.targetAgentId - If provided, only content parts from this agent will be included.
632
+ * @param options.contentMetadataMap - Map of content index to metadata (required when targetAgentId is provided).
629
633
  * @returns - Object containing formatted messages and updated indexTokenCountMap if provided.
630
634
  */
631
635
  export const formatAgentMessages = (
632
636
  payload: TPayload,
633
- indexTokenCountMap?: Record<number, number>,
634
- tools?: Set<string>
637
+ indexTokenCountMap?: Record<number, number | undefined>,
638
+ tools?: Set<string>,
639
+ options?: {
640
+ targetAgentId?: string;
641
+ contentMetadataMap?: Map<number, ContentMetadata>;
642
+ }
635
643
  ): {
636
644
  messages: Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>;
637
645
  indexTokenCountMap?: Record<number, number>;
638
646
  } => {
647
+ const { targetAgentId, contentMetadataMap } = options ?? {};
639
648
  const messages: Array<
640
649
  HumanMessage | AIMessage | SystemMessage | ToolMessage
641
650
  > = [];
642
651
  // If indexTokenCountMap is provided, create a new map to track the updated indices
643
652
  const updatedIndexTokenCountMap: Record<number, number> = {};
644
653
  // Keep track of the mapping from original payload indices to result indices
645
- const indexMapping: Record<number, number[]> = {};
654
+ const indexMapping: Record<number, number[] | undefined> = {};
646
655
 
647
656
  // Process messages with tool conversion if tools set is provided
648
657
  for (let i = 0; i < payload.length; i++) {
@@ -654,6 +663,27 @@ export const formatAgentMessages = (
654
663
  { type: ContentTypes.TEXT, [ContentTypes.TEXT]: message.content },
655
664
  ];
656
665
  }
666
+
667
+ // Filter content parts by targetAgentId if provided (only for assistant messages with array content)
668
+ if (
669
+ targetAgentId != null &&
670
+ targetAgentId !== '' &&
671
+ contentMetadataMap != null &&
672
+ message.role === 'assistant' &&
673
+ Array.isArray(message.content)
674
+ ) {
675
+ const filteredContent = message.content.filter((_, partIndex) => {
676
+ const metadata = contentMetadataMap.get(partIndex);
677
+ return metadata?.agentId === targetAgentId;
678
+ });
679
+ // Skip this message entirely if no content parts match the target agent
680
+ if (filteredContent.length === 0) {
681
+ indexMapping[i] = [];
682
+ continue;
683
+ }
684
+ message.content = filteredContent;
685
+ }
686
+
657
687
  if (message.role !== 'assistant') {
658
688
  messages.push(
659
689
  formatMessage({
@@ -1141,4 +1141,172 @@ describe('formatAgentMessages', () => {
1141
1141
  expect(result.messages[1].name).toBe('search');
1142
1142
  expect(result.messages[1].content).toBe('');
1143
1143
  });
1144
+
1145
+ describe('targetAgentId filtering', () => {
1146
+ it('should filter content parts to only include those from targetAgentId', () => {
1147
+ const payload: TPayload = [
1148
+ { role: 'user', content: 'Hello' },
1149
+ {
1150
+ role: 'assistant',
1151
+ content: [
1152
+ { type: ContentTypes.TEXT, text: 'Response from agent_a' },
1153
+ { type: ContentTypes.TEXT, text: 'Response from agent_b' },
1154
+ { type: ContentTypes.TEXT, text: 'Another from agent_a' },
1155
+ ],
1156
+ },
1157
+ ];
1158
+
1159
+ const contentMetadataMap = new Map([
1160
+ [0, { agentId: 'agent_a' }],
1161
+ [1, { agentId: 'agent_b' }],
1162
+ [2, { agentId: 'agent_a' }],
1163
+ ]);
1164
+
1165
+ const result = formatAgentMessages(payload, undefined, undefined, {
1166
+ targetAgentId: 'agent_a',
1167
+ contentMetadataMap,
1168
+ });
1169
+
1170
+ // Should have user message + filtered assistant message
1171
+ expect(result.messages).toHaveLength(2);
1172
+ expect(result.messages[0]).toBeInstanceOf(HumanMessage);
1173
+ expect(result.messages[1]).toBeInstanceOf(AIMessage);
1174
+
1175
+ // The AIMessage should only have agent_a's content parts
1176
+ const aiMessage = result.messages[1] as AIMessage;
1177
+ expect(Array.isArray(aiMessage.content)).toBe(true);
1178
+ expect(aiMessage.content as Array<{ text: string }>).toHaveLength(2);
1179
+ expect((aiMessage.content as Array<{ text: string }>)[0].text).toBe(
1180
+ 'Response from agent_a'
1181
+ );
1182
+ expect((aiMessage.content as Array<{ text: string }>)[1].text).toBe(
1183
+ 'Another from agent_a'
1184
+ );
1185
+ });
1186
+
1187
+ it('should skip assistant message entirely if no content parts match targetAgentId', () => {
1188
+ const payload: TPayload = [
1189
+ { role: 'user', content: 'Hello' },
1190
+ {
1191
+ role: 'assistant',
1192
+ content: [{ type: ContentTypes.TEXT, text: 'Response from agent_b' }],
1193
+ },
1194
+ ];
1195
+
1196
+ const contentMetadataMap = new Map([[0, { agentId: 'agent_b' }]]);
1197
+
1198
+ const result = formatAgentMessages(payload, undefined, undefined, {
1199
+ targetAgentId: 'agent_a',
1200
+ contentMetadataMap,
1201
+ });
1202
+
1203
+ // Should only have the user message, assistant message skipped
1204
+ expect(result.messages).toHaveLength(1);
1205
+ expect(result.messages[0]).toBeInstanceOf(HumanMessage);
1206
+ });
1207
+
1208
+ it('should not filter when targetAgentId is not provided', () => {
1209
+ const payload: TPayload = [
1210
+ {
1211
+ role: 'assistant',
1212
+ content: [
1213
+ { type: ContentTypes.TEXT, text: 'Response from agent_a' },
1214
+ { type: ContentTypes.TEXT, text: 'Response from agent_b' },
1215
+ ],
1216
+ },
1217
+ ];
1218
+
1219
+ const contentMetadataMap = new Map([
1220
+ [0, { agentId: 'agent_a' }],
1221
+ [1, { agentId: 'agent_b' }],
1222
+ ]);
1223
+
1224
+ // No targetAgentId provided - should include all content
1225
+ const result = formatAgentMessages(payload, undefined, undefined, {
1226
+ contentMetadataMap,
1227
+ });
1228
+
1229
+ expect(result.messages).toHaveLength(1);
1230
+ const aiMessage = result.messages[0] as AIMessage;
1231
+ expect(Array.isArray(aiMessage.content)).toBe(true);
1232
+ expect(aiMessage.content as Array<{ text: string }>).toHaveLength(2);
1233
+ });
1234
+
1235
+ it('should not filter when contentMetadataMap is not provided', () => {
1236
+ const payload: TPayload = [
1237
+ {
1238
+ role: 'assistant',
1239
+ content: [
1240
+ { type: ContentTypes.TEXT, text: 'Response 1' },
1241
+ { type: ContentTypes.TEXT, text: 'Response 2' },
1242
+ ],
1243
+ },
1244
+ ];
1245
+
1246
+ // targetAgentId provided but no contentMetadataMap - should include all content
1247
+ const result = formatAgentMessages(payload, undefined, undefined, {
1248
+ targetAgentId: 'agent_a',
1249
+ });
1250
+
1251
+ expect(result.messages).toHaveLength(1);
1252
+ const aiMessage = result.messages[0] as AIMessage;
1253
+ expect(Array.isArray(aiMessage.content)).toBe(true);
1254
+ expect(aiMessage.content as Array<{ text: string }>).toHaveLength(2);
1255
+ });
1256
+
1257
+ it('should filter content with groupId metadata (parallel execution)', () => {
1258
+ const payload: TPayload = [
1259
+ { role: 'user', content: 'Analyze this' },
1260
+ {
1261
+ role: 'assistant',
1262
+ content: [
1263
+ { type: ContentTypes.TEXT, text: 'Creative analysis' },
1264
+ { type: ContentTypes.TEXT, text: 'Practical analysis' },
1265
+ ],
1266
+ },
1267
+ ];
1268
+
1269
+ const contentMetadataMap = new Map([
1270
+ [0, { agentId: 'creative_analyst', groupId: 1 }],
1271
+ [1, { agentId: 'practical_analyst', groupId: 1 }],
1272
+ ]);
1273
+
1274
+ const result = formatAgentMessages(payload, undefined, undefined, {
1275
+ targetAgentId: 'creative_analyst',
1276
+ contentMetadataMap,
1277
+ });
1278
+
1279
+ expect(result.messages).toHaveLength(2);
1280
+ const aiMessage = result.messages[1] as AIMessage;
1281
+ expect(Array.isArray(aiMessage.content)).toBe(true);
1282
+ expect(aiMessage.content as Array<{ text: string }>).toHaveLength(1);
1283
+ expect((aiMessage.content as Array<{ text: string }>)[0].text).toBe(
1284
+ 'Creative analysis'
1285
+ );
1286
+ });
1287
+
1288
+ it('should not affect non-assistant messages when filtering', () => {
1289
+ const payload: TPayload = [
1290
+ { role: 'user', content: 'Hello from user' },
1291
+ { role: 'system', content: 'System message' },
1292
+ {
1293
+ role: 'assistant',
1294
+ content: [{ type: ContentTypes.TEXT, text: 'From agent_a' }],
1295
+ },
1296
+ ];
1297
+
1298
+ const contentMetadataMap = new Map([[0, { agentId: 'agent_a' }]]);
1299
+
1300
+ const result = formatAgentMessages(payload, undefined, undefined, {
1301
+ targetAgentId: 'agent_a',
1302
+ contentMetadataMap,
1303
+ });
1304
+
1305
+ // All three messages should be present
1306
+ expect(result.messages).toHaveLength(3);
1307
+ expect(result.messages[0]).toBeInstanceOf(HumanMessage);
1308
+ expect(result.messages[1]).toBeInstanceOf(SystemMessage);
1309
+ expect(result.messages[2]).toBeInstanceOf(AIMessage);
1310
+ });
1311
+ });
1144
1312
  });
@@ -143,18 +143,49 @@ async function testSequentialAgentChain() {
143
143
 
144
144
  // Track agent progression
145
145
  let currentAgent = '';
146
+ const startTime = Date.now();
147
+ let messageCount = 0;
146
148
 
147
- // Create custom handlers
149
+ // Create custom handlers with extensive metadata logging
148
150
  const customHandlers = {
149
151
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
150
- [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
152
+ [GraphEvents.CHAT_MODEL_END]: {
153
+ handle: (
154
+ _event: string,
155
+ _data: t.StreamEventData,
156
+ metadata?: Record<string, unknown>
157
+ ): void => {
158
+ console.log('\n====== CHAT_MODEL_END METADATA ======');
159
+ console.dir(metadata, { depth: null });
160
+ const elapsed = Date.now() - startTime;
161
+ console.log(`⏱️ COMPLETED at ${elapsed}ms`);
162
+ },
163
+ },
164
+ [GraphEvents.CHAT_MODEL_START]: {
165
+ handle: (
166
+ _event: string,
167
+ _data: t.StreamEventData,
168
+ metadata?: Record<string, unknown>
169
+ ): void => {
170
+ console.log('\n====== CHAT_MODEL_START METADATA ======');
171
+ console.dir(metadata, { depth: null });
172
+ const elapsed = Date.now() - startTime;
173
+ console.log(`⏱️ STARTED at ${elapsed}ms`);
174
+ },
175
+ },
151
176
  [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
152
177
  [GraphEvents.ON_RUN_STEP]: {
153
178
  handle: (
154
179
  event: GraphEvents.ON_RUN_STEP,
155
- data: t.StreamEventData
180
+ data: t.StreamEventData,
181
+ metadata?: Record<string, unknown>
156
182
  ): void => {
157
183
  const runStepData = data as any;
184
+ console.log('\n====== ON_RUN_STEP ======');
185
+ console.log('DATA:');
186
+ console.dir(data, { depth: null });
187
+ console.log('METADATA:');
188
+ console.dir(metadata, { depth: null });
158
189
  if (runStepData?.name) {
159
190
  currentAgent = runStepData.name;
160
191
  console.log(`\n→ ${currentAgent} is processing...`);
@@ -165,9 +196,15 @@ async function testSequentialAgentChain() {
165
196
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
166
197
  handle: (
167
198
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
168
- data: t.StreamEventData
199
+ data: t.StreamEventData,
200
+ metadata?: Record<string, unknown>
169
201
  ): void => {
170
202
  const runStepData = data as any;
203
+ console.log('\n====== ON_RUN_STEP_COMPLETED ======');
204
+ console.log('DATA:');
205
+ console.dir(data, { depth: null });
206
+ console.log('METADATA:');
207
+ console.dir(metadata, { depth: null });
171
208
  if (runStepData?.name) {
172
209
  console.log(`✓ ${runStepData.name} completed`);
173
210
  }
@@ -180,16 +217,32 @@ async function testSequentialAgentChain() {
180
217
  [GraphEvents.ON_RUN_STEP_DELTA]: {
181
218
  handle: (
182
219
  event: GraphEvents.ON_RUN_STEP_DELTA,
183
- data: t.StreamEventData
220
+ data: t.StreamEventData,
221
+ metadata?: Record<string, unknown>
184
222
  ): void => {
223
+ console.log('\n====== ON_RUN_STEP_DELTA ======');
224
+ console.log('DATA:');
225
+ console.dir(data, { depth: null });
226
+ console.log('METADATA:');
227
+ console.dir(metadata, { depth: null });
185
228
  aggregateContent({ event, data: data as t.RunStepDeltaEvent });
186
229
  },
187
230
  },
188
231
  [GraphEvents.ON_MESSAGE_DELTA]: {
189
232
  handle: (
190
233
  event: GraphEvents.ON_MESSAGE_DELTA,
191
- data: t.StreamEventData
234
+ data: t.StreamEventData,
235
+ metadata?: Record<string, unknown>
192
236
  ): void => {
237
+ messageCount++;
238
+ // Only log first few message deltas to avoid spam
239
+ if (messageCount <= 3) {
240
+ console.log('\n====== ON_MESSAGE_DELTA ======');
241
+ console.log('DATA:');
242
+ console.dir(data, { depth: null });
243
+ console.log('METADATA:');
244
+ console.dir(metadata, { depth: null });
245
+ }
193
246
  aggregateContent({ event, data: data as t.MessageDeltaEvent });
194
247
  },
195
248
  },
@@ -25,7 +25,8 @@ async function testParallelFromStart() {
25
25
  console.log('Testing Parallel From Start Multi-Agent System...\n');
26
26
 
27
27
  // Set up content aggregator
28
- const { contentParts, aggregateContent } = createContentAggregator();
28
+ const { contentParts, aggregateContent, contentMetadataMap } =
29
+ createContentAggregator();
29
30
 
30
31
  // Define two agents - both have NO incoming edges, so they run in parallel from the start
31
32
  const agents: t.AgentInputs[] = [
@@ -58,7 +59,7 @@ async function testParallelFromStart() {
58
59
  const agentTimings: Record<string, { start?: number; end?: number }> = {};
59
60
  const startTime = Date.now();
60
61
 
61
- // Create custom handlers
62
+ // Create custom handlers with extensive metadata logging
62
63
  const customHandlers = {
63
64
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
64
65
  [GraphEvents.CHAT_MODEL_END]: {
@@ -67,6 +68,8 @@ async function testParallelFromStart() {
67
68
  _data: t.StreamEventData,
68
69
  metadata?: Record<string, unknown>
69
70
  ): void => {
71
+ console.log('\n====== CHAT_MODEL_END METADATA ======');
72
+ console.dir(metadata, { depth: null });
70
73
  const nodeName = metadata?.langgraph_node as string;
71
74
  if (nodeName) {
72
75
  const elapsed = Date.now() - startTime;
@@ -82,6 +85,8 @@ async function testParallelFromStart() {
82
85
  _data: t.StreamEventData,
83
86
  metadata?: Record<string, unknown>
84
87
  ): void => {
88
+ console.log('\n====== CHAT_MODEL_START METADATA ======');
89
+ console.dir(metadata, { depth: null });
85
90
  const nodeName = metadata?.langgraph_node as string;
86
91
  if (nodeName) {
87
92
  const elapsed = Date.now() - startTime;
@@ -96,8 +101,14 @@ async function testParallelFromStart() {
96
101
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
97
102
  handle: (
98
103
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
99
- data: t.StreamEventData
104
+ data: t.StreamEventData,
105
+ metadata?: Record<string, unknown>
100
106
  ): void => {
107
+ console.log('\n====== ON_RUN_STEP_COMPLETED ======');
108
+ console.log('DATA:');
109
+ console.dir(data, { depth: null });
110
+ console.log('METADATA:');
111
+ console.dir(metadata, { depth: null });
101
112
  aggregateContent({
102
113
  event,
103
114
  data: data as unknown as { result: t.ToolEndEvent },
@@ -107,25 +118,43 @@ async function testParallelFromStart() {
107
118
  [GraphEvents.ON_RUN_STEP]: {
108
119
  handle: (
109
120
  event: GraphEvents.ON_RUN_STEP,
110
- data: t.StreamEventData
121
+ data: t.StreamEventData,
122
+ metadata?: Record<string, unknown>
111
123
  ): void => {
124
+ console.log('\n====== ON_RUN_STEP ======');
125
+ console.log('DATA:');
126
+ console.dir(data, { depth: null });
127
+ console.log('METADATA:');
128
+ console.dir(metadata, { depth: null });
112
129
  aggregateContent({ event, data: data as t.RunStep });
113
130
  },
114
131
  },
115
132
  [GraphEvents.ON_RUN_STEP_DELTA]: {
116
133
  handle: (
117
134
  event: GraphEvents.ON_RUN_STEP_DELTA,
118
- data: t.StreamEventData
135
+ data: t.StreamEventData,
136
+ metadata?: Record<string, unknown>
119
137
  ): void => {
138
+ console.log('\n====== ON_RUN_STEP_DELTA ======');
139
+ console.log('DATA:');
140
+ console.dir(data, { depth: null });
141
+ console.log('METADATA:');
142
+ console.dir(metadata, { depth: null });
120
143
  aggregateContent({ event, data: data as t.RunStepDeltaEvent });
121
144
  },
122
145
  },
123
146
  [GraphEvents.ON_MESSAGE_DELTA]: {
124
147
  handle: (
125
148
  event: GraphEvents.ON_MESSAGE_DELTA,
126
- data: t.StreamEventData
149
+ data: t.StreamEventData,
150
+ metadata?: Record<string, unknown>
127
151
  ): void => {
152
+ // Only log first delta per agent to avoid spam
153
+ console.log('\n====== ON_MESSAGE_DELTA ======');
154
+ console.log('DATA:');
128
155
  console.dir(data, { depth: null });
156
+ console.log('METADATA:');
157
+ console.dir(metadata, { depth: null });
129
158
  aggregateContent({ event, data: data as t.MessageDeltaEvent });
130
159
  },
131
160
  },
@@ -221,7 +250,11 @@ async function testParallelFromStart() {
221
250
  console.log('====================================\n');
222
251
 
223
252
  console.log('Final content parts:', contentParts.length, 'parts');
253
+ console.log('\n=== Content Parts (clean, no metadata) ===');
224
254
  console.dir(contentParts, { depth: null });
255
+ console.log('\n=== Content Metadata Map (separate from content) ===');
256
+ console.dir(Object.fromEntries(contentMetadataMap), { depth: null });
257
+
225
258
  await sleep(3000);
226
259
  } catch (error) {
227
260
  console.error('Error in parallel-from-start multi-agent test:', error);
@@ -200,18 +200,50 @@ async function testParallelMultiAgent() {
200
200
 
201
201
  // Track which agents are active
202
202
  const activeAgents = new Set<string>();
203
+ const startTime = Date.now();
204
+ let messageCount = 0;
203
205
 
204
- // Create custom handlers
206
+ // Create custom handlers with extensive metadata logging
205
207
  const customHandlers = {
206
208
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
207
- [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
209
+ [GraphEvents.CHAT_MODEL_END]: {
210
+ handle: (
211
+ _event: string,
212
+ _data: t.StreamEventData,
213
+ metadata?: Record<string, unknown>
214
+ ): void => {
215
+ console.log('\n====== CHAT_MODEL_END METADATA ======');
216
+ console.dir(metadata, { depth: null });
217
+ const elapsed = Date.now() - startTime;
218
+ const nodeName = metadata?.langgraph_node as string;
219
+ console.log(`⏱️ [${nodeName || 'unknown'}] COMPLETED at ${elapsed}ms`);
220
+ },
221
+ },
222
+ [GraphEvents.CHAT_MODEL_START]: {
223
+ handle: (
224
+ _event: string,
225
+ _data: t.StreamEventData,
226
+ metadata?: Record<string, unknown>
227
+ ): void => {
228
+ console.log('\n====== CHAT_MODEL_START METADATA ======');
229
+ console.dir(metadata, { depth: null });
230
+ const elapsed = Date.now() - startTime;
231
+ const nodeName = metadata?.langgraph_node as string;
232
+ console.log(`⏱️ [${nodeName || 'unknown'}] STARTED at ${elapsed}ms`);
233
+ },
234
+ },
208
235
  [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
209
236
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
210
237
  handle: (
211
238
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
212
- data: t.StreamEventData
239
+ data: t.StreamEventData,
240
+ metadata?: Record<string, unknown>
213
241
  ): void => {
214
- console.log('====== ON_RUN_STEP_COMPLETED ======');
242
+ console.log('\n====== ON_RUN_STEP_COMPLETED ======');
243
+ console.log('DATA:');
244
+ console.dir(data, { depth: null });
245
+ console.log('METADATA:');
246
+ console.dir(metadata, { depth: null });
215
247
  const runStepData = data as any;
216
248
  if (runStepData?.name) {
217
249
  activeAgents.delete(runStepData.name);
@@ -226,9 +258,14 @@ async function testParallelMultiAgent() {
226
258
  [GraphEvents.ON_RUN_STEP]: {
227
259
  handle: (
228
260
  event: GraphEvents.ON_RUN_STEP,
229
- data: t.StreamEventData
261
+ data: t.StreamEventData,
262
+ metadata?: Record<string, unknown>
230
263
  ): void => {
231
- console.log('====== ON_RUN_STEP ======');
264
+ console.log('\n====== ON_RUN_STEP ======');
265
+ console.log('DATA:');
266
+ console.dir(data, { depth: null });
267
+ console.log('METADATA:');
268
+ console.dir(metadata, { depth: null });
232
269
  const runStepData = data as any;
233
270
  if (runStepData?.name) {
234
271
  activeAgents.add(runStepData.name);
@@ -240,18 +277,32 @@ async function testParallelMultiAgent() {
240
277
  [GraphEvents.ON_RUN_STEP_DELTA]: {
241
278
  handle: (
242
279
  event: GraphEvents.ON_RUN_STEP_DELTA,
243
- data: t.StreamEventData
280
+ data: t.StreamEventData,
281
+ metadata?: Record<string, unknown>
244
282
  ): void => {
283
+ console.log('\n====== ON_RUN_STEP_DELTA ======');
284
+ console.log('DATA:');
285
+ console.dir(data, { depth: null });
286
+ console.log('METADATA:');
287
+ console.dir(metadata, { depth: null });
245
288
  aggregateContent({ event, data: data as t.RunStepDeltaEvent });
246
289
  },
247
290
  },
248
291
  [GraphEvents.ON_MESSAGE_DELTA]: {
249
292
  handle: (
250
293
  event: GraphEvents.ON_MESSAGE_DELTA,
251
- data: t.StreamEventData
294
+ data: t.StreamEventData,
295
+ metadata?: Record<string, unknown>
252
296
  ): void => {
253
- console.log('====== ON_MESSAGE_DELTA ======');
254
- console.dir(data, { depth: null });
297
+ messageCount++;
298
+ // Only log first few message deltas per agent to avoid spam
299
+ if (messageCount <= 5) {
300
+ console.log('\n====== ON_MESSAGE_DELTA ======');
301
+ console.log('DATA:');
302
+ console.dir(data, { depth: null });
303
+ console.log('METADATA:');
304
+ console.dir(metadata, { depth: null });
305
+ }
255
306
  aggregateContent({ event, data: data as t.MessageDeltaEvent });
256
307
  },
257
308
  },
@@ -22,7 +22,8 @@ async function testSequentialMultiAgent() {
22
22
  console.log('Testing Sequential Multi-Agent System (A → B → C)...\n');
23
23
 
24
24
  // Set up content aggregator
25
- const { contentParts, aggregateContent } = createContentAggregator();
25
+ const { contentParts, aggregateContent, contentMetadataMap } =
26
+ createContentAggregator();
26
27
 
27
28
  // Define three simple agents
28
29
  const agents: t.AgentInputs[] = [
@@ -194,6 +195,10 @@ async function testSequentialMultiAgent() {
194
195
  console.log('\n\n=== Final Output ===');
195
196
  console.log('Sequential flow completed successfully!');
196
197
  console.log(`Total content parts: ${contentParts.length}`);
198
+ console.log('\n=== Content Parts (clean, no metadata) ===');
199
+ console.dir(contentParts, { depth: null });
200
+ console.log('\n=== Content Metadata Map (separate from content) ===');
201
+ console.dir(Object.fromEntries(contentMetadataMap), { depth: null });
197
202
 
198
203
  // Display the sequential responses
199
204
  const aiMessages = conversationHistory.filter(