illuma-agents 1.0.27 → 1.0.28

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 (42) 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.map +1 -1
  6. package/dist/cjs/stream.cjs +27 -0
  7. package/dist/cjs/stream.cjs.map +1 -1
  8. package/dist/cjs/tools/BrowserTools.cjs +53 -32
  9. package/dist/cjs/tools/BrowserTools.cjs.map +1 -1
  10. package/dist/esm/graphs/Graph.mjs +28 -2
  11. package/dist/esm/graphs/Graph.mjs.map +1 -1
  12. package/dist/esm/graphs/MultiAgentGraph.mjs +108 -0
  13. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  14. package/dist/esm/messages/format.mjs.map +1 -1
  15. package/dist/esm/stream.mjs +27 -0
  16. package/dist/esm/stream.mjs.map +1 -1
  17. package/dist/esm/tools/BrowserTools.mjs +53 -32
  18. package/dist/esm/tools/BrowserTools.mjs.map +1 -1
  19. package/dist/types/graphs/Graph.d.ts +14 -0
  20. package/dist/types/graphs/MultiAgentGraph.d.ts +41 -0
  21. package/dist/types/messages/format.d.ts +1 -1
  22. package/dist/types/tools/BrowserTools.d.ts +1 -1
  23. package/dist/types/types/stream.d.ts +13 -0
  24. package/package.json +4 -2
  25. package/src/graphs/Graph.ts +30 -2
  26. package/src/graphs/MultiAgentGraph.ts +119 -0
  27. package/src/messages/format.ts +2 -2
  28. package/src/scripts/multi-agent-chain.ts +59 -6
  29. package/src/scripts/multi-agent-parallel-start.ts +265 -0
  30. package/src/scripts/multi-agent-parallel.ts +61 -10
  31. package/src/scripts/multi-agent-sequence.ts +6 -1
  32. package/src/scripts/parallel-asymmetric-tools-test.ts +274 -0
  33. package/src/scripts/parallel-full-metadata-test.ts +240 -0
  34. package/src/scripts/parallel-tools-test.ts +340 -0
  35. package/src/scripts/sequential-full-metadata-test.ts +197 -0
  36. package/src/scripts/single-agent-metadata-test.ts +198 -0
  37. package/src/scripts/test-thinking-handoff.ts +8 -0
  38. package/src/scripts/tools.ts +31 -11
  39. package/src/stream.ts +32 -0
  40. package/src/tools/BrowserTools.ts +356 -350
  41. package/src/tools/__tests__/BrowserTools.test.ts +263 -257
  42. package/src/types/stream.ts +15 -0
@@ -1246,6 +1246,26 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
1246
1246
  return workflow;
1247
1247
  }
1248
1248
 
1249
+ /**
1250
+ * Indicates if this is a multi-agent graph.
1251
+ * Override in MultiAgentGraph to return true.
1252
+ * Used to conditionally include agentId in RunStep for frontend rendering.
1253
+ */
1254
+ protected isMultiAgentGraph(): boolean {
1255
+ return false;
1256
+ }
1257
+
1258
+ /**
1259
+ * Get the parallel group ID for an agent, if any.
1260
+ * Override in MultiAgentGraph to provide actual group IDs.
1261
+ * Group IDs are incrementing numbers (1, 2, 3...) reflecting execution order.
1262
+ * @param _agentId - The agent ID to look up
1263
+ * @returns undefined for StandardGraph (no parallel groups), or group number for MultiAgentGraph
1264
+ */
1265
+ protected getParallelGroupIdForAgent(_agentId: string): number | undefined {
1266
+ return undefined;
1267
+ }
1268
+
1249
1269
  /* Dispatchers */
1250
1270
 
1251
1271
  /**
@@ -1286,13 +1306,21 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
1286
1306
  }
1287
1307
 
1288
1308
  /**
1289
- * Extract and store agentId from metadata
1309
+ * Extract agentId and parallelGroupId from metadata
1310
+ * Only set agentId for MultiAgentGraph (so frontend knows when to show agent labels)
1290
1311
  */
1291
1312
  if (metadata) {
1292
1313
  try {
1293
1314
  const agentContext = this.getAgentContext(metadata);
1294
- if (agentContext.agentId) {
1315
+ if (this.isMultiAgentGraph() && agentContext.agentId) {
1316
+ // Only include agentId for MultiAgentGraph - enables frontend to show agent labels
1295
1317
  runStep.agentId = agentContext.agentId;
1318
+ // Set group ID if this agent is part of a parallel group
1319
+ // Group IDs are incrementing numbers (1, 2, 3...) reflecting execution order
1320
+ const groupId = this.getParallelGroupIdForAgent(agentContext.agentId);
1321
+ if (groupId != null) {
1322
+ runStep.groupId = groupId;
1323
+ }
1296
1324
  }
1297
1325
  } catch (_e) {
1298
1326
  /** If we can't get agent context, that's okay - agentId remains undefined */
@@ -40,6 +40,17 @@ export class MultiAgentGraph extends StandardGraph {
40
40
  private startingNodes: Set<string> = new Set();
41
41
  private directEdges: t.GraphEdge[] = [];
42
42
  private handoffEdges: t.GraphEdge[] = [];
43
+ /**
44
+ * Map of agentId to parallel group info.
45
+ * Contains groupId (incrementing number reflecting execution order) for agents in parallel groups.
46
+ * Sequential agents (not in any parallel group) have undefined entry.
47
+ *
48
+ * Example for: researcher -> [analyst1, analyst2, analyst3] -> summarizer
49
+ * - researcher: undefined (sequential, order 0)
50
+ * - analyst1, analyst2, analyst3: { groupId: 1 } (parallel group, order 1)
51
+ * - summarizer: undefined (sequential, order 2)
52
+ */
53
+ private agentParallelGroups: Map<string, number> = new Map();
43
54
 
44
55
  constructor(input: t.MultiAgentGraphInput) {
45
56
  super(input);
@@ -99,6 +110,114 @@ export class MultiAgentGraph extends StandardGraph {
99
110
  if (this.startingNodes.size === 0 && this.agentContexts.size > 0) {
100
111
  this.startingNodes.add(this.agentContexts.keys().next().value!);
101
112
  }
113
+
114
+ // Determine if graph has parallel execution capability
115
+ this.computeParallelCapability();
116
+ }
117
+
118
+ /**
119
+ * Compute parallel groups by traversing the graph in execution order.
120
+ * Assigns incrementing group IDs that reflect the sequential order of execution.
121
+ *
122
+ * For: researcher -> [analyst1, analyst2, analyst3] -> summarizer
123
+ * - researcher: no group (first sequential node)
124
+ * - analyst1, analyst2, analyst3: groupId 1 (first parallel group)
125
+ * - summarizer: no group (next sequential node)
126
+ *
127
+ * This allows frontend to render in order:
128
+ * Row 0: researcher
129
+ * Row 1: [analyst1, analyst2, analyst3] (grouped)
130
+ * Row 2: summarizer
131
+ */
132
+ private computeParallelCapability(): void {
133
+ let groupCounter = 1; // Start at 1, 0 reserved for "no group"
134
+
135
+ // Check 1: Multiple starting nodes means parallel from the start (group 1)
136
+ if (this.startingNodes.size > 1) {
137
+ for (const agentId of this.startingNodes) {
138
+ this.agentParallelGroups.set(agentId, groupCounter);
139
+ }
140
+ groupCounter++;
141
+ }
142
+
143
+ // Check 2: Traverse direct edges in order to find fan-out patterns
144
+ // Build a simple execution order by following edges from starting nodes
145
+ const visited = new Set<string>();
146
+ const queue: string[] = [...this.startingNodes];
147
+
148
+ while (queue.length > 0) {
149
+ const current = queue.shift()!;
150
+ if (visited.has(current)) continue;
151
+ visited.add(current);
152
+
153
+ // Find direct edges from this node
154
+ for (const edge of this.directEdges) {
155
+ const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
156
+ if (!sources.includes(current)) continue;
157
+
158
+ const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
159
+
160
+ // Fan-out: multiple destinations = parallel group
161
+ if (destinations.length > 1) {
162
+ for (const dest of destinations) {
163
+ // Only set if not already in a group (first group wins)
164
+ if (!this.agentParallelGroups.has(dest)) {
165
+ this.agentParallelGroups.set(dest, groupCounter);
166
+ }
167
+ if (!visited.has(dest)) {
168
+ queue.push(dest);
169
+ }
170
+ }
171
+ groupCounter++;
172
+ } else {
173
+ // Single destination - add to queue for traversal
174
+ for (const dest of destinations) {
175
+ if (!visited.has(dest)) {
176
+ queue.push(dest);
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ // Also follow handoff edges for traversal (but they don't create parallel groups)
183
+ for (const edge of this.handoffEdges) {
184
+ const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
185
+ if (!sources.includes(current)) continue;
186
+
187
+ const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
188
+ for (const dest of destinations) {
189
+ if (!visited.has(dest)) {
190
+ queue.push(dest);
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Get the parallel group ID for an agent, if any.
199
+ * Returns undefined if the agent is not part of a parallel group.
200
+ * Group IDs are incrementing numbers reflecting execution order.
201
+ */
202
+ getParallelGroupId(agentId: string): number | undefined {
203
+ return this.agentParallelGroups.get(agentId);
204
+ }
205
+
206
+ /**
207
+ * Override to indicate this is a multi-agent graph.
208
+ * Enables agentId to be included in RunStep for frontend agent labeling.
209
+ */
210
+ protected override isMultiAgentGraph(): boolean {
211
+ return true;
212
+ }
213
+
214
+ /**
215
+ * Override base class method to provide parallel group IDs for run steps.
216
+ */
217
+ protected override getParallelGroupIdForAgent(
218
+ agentId: string
219
+ ): number | undefined {
220
+ return this.agentParallelGroups.get(agentId);
102
221
  }
103
222
 
104
223
  /**
@@ -636,7 +636,7 @@ export const labelContentByAgent = (
636
636
  */
637
637
  export const formatAgentMessages = (
638
638
  payload: TPayload,
639
- indexTokenCountMap?: Record<number, number>,
639
+ indexTokenCountMap?: Record<number, number | undefined>,
640
640
  tools?: Set<string>
641
641
  ): {
642
642
  messages: Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>;
@@ -648,7 +648,7 @@ export const formatAgentMessages = (
648
648
  // If indexTokenCountMap is provided, create a new map to track the updated indices
649
649
  const updatedIndexTokenCountMap: Record<number, number> = {};
650
650
  // Keep track of the mapping from original payload indices to result indices
651
- const indexMapping: Record<number, number[]> = {};
651
+ const indexMapping: Record<number, number[] | undefined> = {};
652
652
 
653
653
  // Process messages with tool conversion if tools set is provided
654
654
  for (let i = 0; i < payload.length; i++) {
@@ -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
  },
@@ -0,0 +1,265 @@
1
+ import { config } from 'dotenv';
2
+ config();
3
+
4
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
5
+ import type * as t from '@/types';
6
+ import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
7
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
8
+ import { Providers, GraphEvents } from '@/common';
9
+ import { sleep } from '@/utils/run';
10
+ import { Run } from '@/run';
11
+
12
+ const conversationHistory: BaseMessage[] = [];
13
+
14
+ /**
15
+ * Example of parallel multi-agent system that starts with parallel execution immediately
16
+ *
17
+ * Graph structure:
18
+ * START -> [analyst1, analyst2] -> END (parallel from start, both run simultaneously)
19
+ *
20
+ * This demonstrates getting a parallel stream from the very beginning,
21
+ * with two agents running simultaneously. Useful for testing how different
22
+ * models respond to the same input.
23
+ */
24
+ async function testParallelFromStart() {
25
+ console.log('Testing Parallel From Start Multi-Agent System...\n');
26
+
27
+ // Set up content aggregator
28
+ const { contentParts, aggregateContent, contentMetadataMap } =
29
+ createContentAggregator();
30
+
31
+ // Define two agents - both have NO incoming edges, so they run in parallel from the start
32
+ const agents: t.AgentInputs[] = [
33
+ {
34
+ agentId: 'analyst1',
35
+ provider: Providers.ANTHROPIC,
36
+ clientOptions: {
37
+ modelName: 'claude-haiku-4-5',
38
+ apiKey: process.env.ANTHROPIC_API_KEY,
39
+ },
40
+ instructions: `You are a CREATIVE ANALYST. Analyze the user's query from a creative and innovative perspective. Focus on novel ideas, unconventional approaches, and imaginative possibilities. Keep your response concise (100-150 words). Start with "🎨 CREATIVE:"`,
41
+ },
42
+ {
43
+ agentId: 'analyst2',
44
+ provider: Providers.ANTHROPIC,
45
+ clientOptions: {
46
+ modelName: 'claude-haiku-4-5',
47
+ apiKey: process.env.ANTHROPIC_API_KEY,
48
+ },
49
+ instructions: `You are a PRACTICAL ANALYST. Analyze the user's query from a logical and practical perspective. Focus on feasibility, metrics, and actionable steps. Keep your response concise (100-150 words). Start with "📊 PRACTICAL:"`,
50
+ },
51
+ ];
52
+
53
+ // No edges needed - both agents have no incoming edges, so both are start nodes
54
+ // They will run in parallel and end when both complete
55
+ const edges: t.GraphEdge[] = [];
56
+
57
+ // Track which agents are active and their timing
58
+ const activeAgents = new Set<string>();
59
+ const agentTimings: Record<string, { start?: number; end?: number }> = {};
60
+ const startTime = Date.now();
61
+
62
+ // Create custom handlers with extensive metadata logging
63
+ const customHandlers = {
64
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
65
+ [GraphEvents.CHAT_MODEL_END]: {
66
+ handle: (
67
+ _event: string,
68
+ _data: t.StreamEventData,
69
+ metadata?: Record<string, unknown>
70
+ ): void => {
71
+ console.log('\n====== CHAT_MODEL_END METADATA ======');
72
+ console.dir(metadata, { depth: null });
73
+ const nodeName = metadata?.langgraph_node as string;
74
+ if (nodeName) {
75
+ const elapsed = Date.now() - startTime;
76
+ agentTimings[nodeName] = agentTimings[nodeName] || {};
77
+ agentTimings[nodeName].end = elapsed;
78
+ console.log(`⏱️ [${nodeName}] COMPLETED at ${elapsed}ms`);
79
+ }
80
+ },
81
+ },
82
+ [GraphEvents.CHAT_MODEL_START]: {
83
+ handle: (
84
+ _event: string,
85
+ _data: t.StreamEventData,
86
+ metadata?: Record<string, unknown>
87
+ ): void => {
88
+ console.log('\n====== CHAT_MODEL_START METADATA ======');
89
+ console.dir(metadata, { depth: null });
90
+ const nodeName = metadata?.langgraph_node as string;
91
+ if (nodeName) {
92
+ const elapsed = Date.now() - startTime;
93
+ agentTimings[nodeName] = agentTimings[nodeName] || {};
94
+ agentTimings[nodeName].start = elapsed;
95
+ activeAgents.add(nodeName);
96
+ console.log(`⏱️ [${nodeName}] STARTED at ${elapsed}ms`);
97
+ }
98
+ },
99
+ },
100
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
101
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
102
+ handle: (
103
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
104
+ data: t.StreamEventData,
105
+ metadata?: Record<string, unknown>
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 });
112
+ aggregateContent({
113
+ event,
114
+ data: data as unknown as { result: t.ToolEndEvent },
115
+ });
116
+ },
117
+ },
118
+ [GraphEvents.ON_RUN_STEP]: {
119
+ handle: (
120
+ event: GraphEvents.ON_RUN_STEP,
121
+ data: t.StreamEventData,
122
+ metadata?: Record<string, unknown>
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 });
129
+ aggregateContent({ event, data: data as t.RunStep });
130
+ },
131
+ },
132
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
133
+ handle: (
134
+ event: GraphEvents.ON_RUN_STEP_DELTA,
135
+ data: t.StreamEventData,
136
+ metadata?: Record<string, unknown>
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 });
143
+ aggregateContent({ event, data: data as t.RunStepDeltaEvent });
144
+ },
145
+ },
146
+ [GraphEvents.ON_MESSAGE_DELTA]: {
147
+ handle: (
148
+ event: GraphEvents.ON_MESSAGE_DELTA,
149
+ data: t.StreamEventData,
150
+ metadata?: Record<string, unknown>
151
+ ): void => {
152
+ // Only log first delta per agent to avoid spam
153
+ console.log('\n====== ON_MESSAGE_DELTA ======');
154
+ console.log('DATA:');
155
+ console.dir(data, { depth: null });
156
+ console.log('METADATA:');
157
+ console.dir(metadata, { depth: null });
158
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
159
+ },
160
+ },
161
+ };
162
+
163
+ // Create multi-agent run configuration
164
+ const runConfig: t.RunConfig = {
165
+ runId: `parallel-start-${Date.now()}`,
166
+ graphConfig: {
167
+ type: 'multi-agent',
168
+ agents,
169
+ edges,
170
+ },
171
+ customHandlers,
172
+ returnContent: true,
173
+ };
174
+
175
+ try {
176
+ // Create and execute the run
177
+ const run = await Run.create(runConfig);
178
+
179
+ // Debug: Log the graph structure
180
+ console.log('=== DEBUG: Graph Structure ===');
181
+ const graph = (run as any).Graph;
182
+ console.log('Graph exists:', !!graph);
183
+ if (graph) {
184
+ console.log('Graph type:', graph.constructor.name);
185
+ console.log('AgentContexts exists:', !!graph.agentContexts);
186
+ if (graph.agentContexts) {
187
+ console.log('AgentContexts size:', graph.agentContexts.size);
188
+ for (const [agentId, context] of graph.agentContexts) {
189
+ console.log(`\nAgent: ${agentId}`);
190
+ console.log(
191
+ `Tools: ${context.tools?.map((t: any) => t.name || 'unnamed').join(', ') || 'none'}`
192
+ );
193
+ }
194
+ }
195
+ }
196
+ console.log('=== END DEBUG ===\n');
197
+
198
+ const userMessage = `What are the best approaches to learning a new programming language?`;
199
+ conversationHistory.push(new HumanMessage(userMessage));
200
+
201
+ console.log('Invoking parallel-from-start multi-agent graph...\n');
202
+ console.log('Both analyst1 and analyst2 should start simultaneously!\n');
203
+
204
+ const config = {
205
+ configurable: {
206
+ thread_id: 'parallel-start-conversation-1',
207
+ },
208
+ streamMode: 'values',
209
+ version: 'v2' as const,
210
+ };
211
+
212
+ // Process with streaming
213
+ const inputs = {
214
+ messages: conversationHistory,
215
+ };
216
+
217
+ const finalContentParts = await run.processStream(inputs, config);
218
+ const finalMessages = run.getRunMessages();
219
+
220
+ if (finalMessages) {
221
+ conversationHistory.push(...finalMessages);
222
+ }
223
+
224
+ console.log('\n\n========== TIMING SUMMARY ==========');
225
+ for (const [agent, timing] of Object.entries(agentTimings)) {
226
+ const duration =
227
+ timing.end && timing.start ? timing.end - timing.start : 'N/A';
228
+ console.log(
229
+ `${agent}: started=${timing.start}ms, ended=${timing.end}ms, duration=${duration}ms`
230
+ );
231
+ }
232
+
233
+ // Check if parallel
234
+ const agents = Object.keys(agentTimings);
235
+ if (agents.length >= 2) {
236
+ const [a1, a2] = agents;
237
+ const t1 = agentTimings[a1];
238
+ const t2 = agentTimings[a2];
239
+ if (t1.start && t2.start && t1.end && t2.end) {
240
+ const overlap = Math.min(t1.end, t2.end) - Math.max(t1.start, t2.start);
241
+ if (overlap > 0) {
242
+ console.log(
243
+ `\n✅ PARALLEL EXECUTION CONFIRMED: ${overlap}ms overlap`
244
+ );
245
+ } else {
246
+ console.log(`\n❌ SEQUENTIAL EXECUTION: no overlap`);
247
+ }
248
+ }
249
+ }
250
+ console.log('====================================\n');
251
+
252
+ console.log('Final content parts:', contentParts.length, 'parts');
253
+ console.log('\n=== Content Parts (clean, no metadata) ===');
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
+
258
+ await sleep(3000);
259
+ } catch (error) {
260
+ console.error('Error in parallel-from-start multi-agent test:', error);
261
+ }
262
+ }
263
+
264
+ // Run the test
265
+ testParallelFromStart();
@@ -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
  },