@librechat/agents 3.0.17 → 3.0.19

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 (36) hide show
  1. package/dist/cjs/graphs/Graph.cjs +80 -1
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/main.cjs +2 -0
  4. package/dist/cjs/main.cjs.map +1 -1
  5. package/dist/cjs/messages/format.cjs +242 -6
  6. package/dist/cjs/messages/format.cjs.map +1 -1
  7. package/dist/cjs/stream.cjs +3 -2
  8. package/dist/cjs/stream.cjs.map +1 -1
  9. package/dist/cjs/tools/handlers.cjs +5 -5
  10. package/dist/cjs/tools/handlers.cjs.map +1 -1
  11. package/dist/esm/graphs/Graph.mjs +80 -1
  12. package/dist/esm/graphs/Graph.mjs.map +1 -1
  13. package/dist/esm/main.mjs +1 -1
  14. package/dist/esm/messages/format.mjs +242 -8
  15. package/dist/esm/messages/format.mjs.map +1 -1
  16. package/dist/esm/stream.mjs +3 -2
  17. package/dist/esm/stream.mjs.map +1 -1
  18. package/dist/esm/tools/handlers.mjs +5 -5
  19. package/dist/esm/tools/handlers.mjs.map +1 -1
  20. package/dist/types/graphs/Graph.d.ts +19 -2
  21. package/dist/types/messages/format.d.ts +25 -1
  22. package/dist/types/tools/handlers.d.ts +2 -1
  23. package/dist/types/types/stream.d.ts +1 -0
  24. package/package.json +9 -8
  25. package/src/graphs/Graph.ts +99 -2
  26. package/src/messages/ensureThinkingBlock.test.ts +393 -0
  27. package/src/messages/format.ts +312 -6
  28. package/src/messages/labelContentByAgent.test.ts +887 -0
  29. package/src/scripts/test-multi-agent-list-handoff.ts +169 -13
  30. package/src/scripts/test-parallel-agent-labeling.ts +325 -0
  31. package/src/scripts/test-thinking-handoff-bedrock.ts +153 -0
  32. package/src/scripts/test-thinking-handoff.ts +147 -0
  33. package/src/specs/thinking-handoff.test.ts +620 -0
  34. package/src/stream.ts +19 -10
  35. package/src/tools/handlers.ts +36 -18
  36. package/src/types/stream.ts +1 -0
@@ -4,11 +4,12 @@ import { config } from 'dotenv';
4
4
  config();
5
5
 
6
6
  import { HumanMessage, BaseMessage } from '@langchain/core/messages';
7
- import { Run } from '@/run';
7
+ import type * as t from '@/types';
8
+ import { labelContentByAgent, formatAgentMessages } from '@/messages/format';
8
9
  import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
9
- import { Providers, GraphEvents, Constants } from '@/common';
10
+ import { Providers, GraphEvents, Constants, StepTypes } from '@/common';
10
11
  import { ToolEndHandler, ModelEndHandler } from '@/events';
11
- import type * as t from '@/types';
12
+ import { Run } from '@/run';
12
13
 
13
14
  const conversationHistory: BaseMessage[] = [];
14
15
 
@@ -188,10 +189,6 @@ async function testSupervisorListHandoff() {
188
189
  try {
189
190
  // Test with different queries
190
191
  const testQueries = [
191
- // 'How can we analyze user engagement metrics to improve our product?',
192
- // 'What security measures should we implement for our new API?',
193
- // 'Can you help design a better onboarding flow for our mobile app?',
194
- // 'We need to set up a CI/CD pipeline for our microservices.',
195
192
  'What are the legal implications of using GPL-licensed code in our product?',
196
193
  ];
197
194
 
@@ -204,9 +201,9 @@ async function testSupervisorListHandoff() {
204
201
  };
205
202
 
206
203
  for (const query of testQueries) {
207
- console.log(`\n${'='.repeat(60)}`);
208
- console.log(`USER QUERY: "${query}"`);
209
- console.log('='.repeat(60));
204
+ console.log(`\n${'='.repeat(80)}`);
205
+ console.log(`FIRST RUN - USER QUERY: "${query}"`);
206
+ console.log('='.repeat(80));
210
207
 
211
208
  // Reset conversation
212
209
  conversationHistory.length = 0;
@@ -216,7 +213,7 @@ async function testSupervisorListHandoff() {
216
213
  const runConfig = createSupervisorGraphWithListEdge();
217
214
  const run = await Run.create(runConfig);
218
215
 
219
- console.log('Processing request...');
216
+ console.log('Processing first request...');
220
217
 
221
218
  // Process with streaming
222
219
  const inputs = {
@@ -230,9 +227,59 @@ async function testSupervisorListHandoff() {
230
227
  conversationHistory.push(...finalMessages);
231
228
  }
232
229
 
233
- // Show summary
230
+ // Demo: Map contentParts to agentIds
231
+ console.log(`\n${'─'.repeat(60)}`);
232
+ console.log('CONTENT PARTS TO AGENT MAPPING:');
233
+ console.log('─'.repeat(60));
234
+
235
+ if (run.Graph) {
236
+ // Get the mapping of contentPart index to agentId
237
+ const contentPartAgentMap = run.Graph.getContentPartAgentMap();
238
+
239
+ console.log(`\nTotal content parts: ${contentParts.length}`);
240
+ console.log(`\nContent Part → Agent Mapping:`);
241
+
242
+ contentPartAgentMap.forEach((agentId, index) => {
243
+ const contentPart = contentParts[index];
244
+ const contentType = contentPart?.type || 'unknown';
245
+ const preview =
246
+ contentType === 'text'
247
+ ? (contentPart as any).text?.slice(0, 50) || ''
248
+ : contentType === 'tool_call'
249
+ ? `Tool: ${(contentPart as any).tool_call?.name || 'unknown'}`
250
+ : contentType;
251
+
252
+ console.log(
253
+ ` [${index}] ${agentId} → ${contentType}: ${preview}${preview.length >= 50 ? '...' : ''}`
254
+ );
255
+ });
256
+
257
+ // Show agent participation summary
258
+ console.log(`\n${'─'.repeat(60)}`);
259
+ console.log('AGENT PARTICIPATION SUMMARY:');
260
+ console.log('─'.repeat(60));
261
+
262
+ const activeAgents = run.Graph.getActiveAgentIds();
263
+ console.log(`\nActive agents (${activeAgents.length}):`, activeAgents);
264
+
265
+ const stepsByAgent = run.Graph.getRunStepsByAgent();
266
+ stepsByAgent.forEach((steps, agentId) => {
267
+ const toolCallSteps = steps.filter(
268
+ (s) => s.type === StepTypes.TOOL_CALLS
269
+ ).length;
270
+ const messageSteps = steps.filter(
271
+ (s) => s.type === StepTypes.MESSAGE_CREATION
272
+ ).length;
273
+ console.log(`\n ${agentId}:`);
274
+ console.log(` - Total steps: ${steps.length}`);
275
+ console.log(` - Message steps: ${messageSteps}`);
276
+ console.log(` - Tool call steps: ${toolCallSteps}`);
277
+ });
278
+ }
279
+
280
+ // Show graph structure summary
234
281
  console.log(`\n${'─'.repeat(60)}`);
235
- console.log(`Graph structure:`);
282
+ console.log(`GRAPH STRUCTURE:`);
236
283
  console.log(`- Agents: 6 total (supervisor + 5 specialists)`);
237
284
  console.log(`- Edges: 1 edge with multiple destinations`);
238
285
  console.log(
@@ -242,6 +289,115 @@ async function testSupervisorListHandoff() {
242
289
  `- Result: Supervisor has 5 handoff tools from a single edge`
243
290
  );
244
291
  console.log('─'.repeat(60));
292
+
293
+ // =============================================================
294
+ // SECOND RUN: Demonstrate agent-labeled history
295
+ // =============================================================
296
+ console.log(`\n${'='.repeat(80)}`);
297
+ console.log(`SECOND RUN - Simulating DB Load with Agent-Labeled History`);
298
+ console.log('='.repeat(80));
299
+
300
+ // Simulate what happens in the main app:
301
+ // 1. Store contentParts + agentIdMap to "DB" (in-memory here)
302
+ const dbStoredContentParts = [...contentParts];
303
+ const dbStoredAgentIdMap = Object.fromEntries(
304
+ run.Graph!.getContentPartAgentMap()
305
+ );
306
+
307
+ console.log('\n📦 Simulating DB storage:');
308
+ console.log(` - Stored ${dbStoredContentParts.length} content parts`);
309
+ console.log(
310
+ ` - Stored agent mappings for ${Object.keys(dbStoredAgentIdMap).length} parts`
311
+ );
312
+
313
+ // 2. On next run, load from "DB" and label by agent
314
+ console.log('\n📥 Loading from DB and labeling by agent...');
315
+
316
+ const agentNames = {
317
+ supervisor: 'Supervisor',
318
+ legal_advisor: 'Legal Advisor',
319
+ data_analyst: 'Data Analyst',
320
+ security_expert: 'Security Expert',
321
+ product_designer: 'Product Designer',
322
+ devops_engineer: 'DevOps Engineer',
323
+ };
324
+
325
+ const labeledContentParts = labelContentByAgent(
326
+ dbStoredContentParts.filter(
327
+ (p): p is t.MessageContentComplex => p != null
328
+ ),
329
+ dbStoredAgentIdMap,
330
+ agentNames
331
+ );
332
+
333
+ console.log(
334
+ ` - Labeled ${labeledContentParts.length} content parts by agent`
335
+ );
336
+
337
+ // 3. Convert labeled content parts to payload format
338
+ const payload: t.TPayload = [
339
+ {
340
+ role: 'user',
341
+ content: query,
342
+ },
343
+ {
344
+ role: 'assistant',
345
+ content: labeledContentParts,
346
+ },
347
+ ];
348
+
349
+ // 4. Format using formatAgentMessages (simulates what main app does)
350
+ console.log('\n🔧 Calling formatAgentMessages...');
351
+ const { messages: formattedMessages } = formatAgentMessages(payload);
352
+
353
+ console.log(
354
+ ` - Formatted into ${formattedMessages.length} BaseMessages`
355
+ );
356
+
357
+ // Show a preview of what the supervisor will see
358
+ console.log('\n👁️ Preview of formatted history for supervisor:');
359
+ console.log('─'.repeat(80));
360
+ for (let i = 0; i < formattedMessages.length; i++) {
361
+ const msg = formattedMessages[i];
362
+ const role = msg._getType();
363
+ const preview =
364
+ typeof msg.content === 'string'
365
+ ? msg.content.slice(0, 200)
366
+ : JSON.stringify(msg.content).slice(0, 200);
367
+ console.log(
368
+ `[${i}] ${role}: ${preview}${preview.length >= 200 ? '...' : ''}`
369
+ );
370
+ }
371
+ console.log('─'.repeat(80));
372
+
373
+ // 5. Create a new run with the formatted history + a followup question
374
+ console.log(
375
+ '\n🚀 Starting second run with agent-labeled history + followup question...'
376
+ );
377
+ const followupQuery =
378
+ 'Can you summarize the key legal points from your previous response?';
379
+ console.log(` Followup: "${followupQuery}"`);
380
+
381
+ // Reset for second run
382
+ const secondRunHistory: BaseMessage[] = [
383
+ ...formattedMessages,
384
+ new HumanMessage(followupQuery),
385
+ ];
386
+
387
+ const runConfig2 = createSupervisorGraphWithListEdge();
388
+ const run2 = await Run.create(runConfig2);
389
+
390
+ const inputs2 = {
391
+ messages: secondRunHistory,
392
+ };
393
+
394
+ await run2.processStream(inputs2, config);
395
+
396
+ console.log('\n✅ Second run completed successfully!');
397
+ console.log(
398
+ ' The supervisor correctly understood that the legal_advisor handled'
399
+ );
400
+ console.log(' the previous query, avoiding identity confusion.');
245
401
  }
246
402
 
247
403
  // Final summary
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { config } from 'dotenv';
4
+ config();
5
+
6
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
7
+ import type * as t from '@/types';
8
+ import { labelContentByAgent, formatAgentMessages } from '@/messages/format';
9
+ import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
10
+ import { Providers, GraphEvents, StepTypes } from '@/common';
11
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
12
+ import { Run } from '@/run';
13
+
14
+ const conversationHistory: BaseMessage[] = [];
15
+
16
+ /**
17
+ * Test parallel multi-agent system with agent labeling on subsequent runs
18
+ *
19
+ * Graph structure:
20
+ * START -> researcher
21
+ * researcher -> [analyst1, analyst2, analyst3] (fan-out)
22
+ * [analyst1, analyst2, analyst3] -> summarizer (fan-in)
23
+ * summarizer -> END
24
+ */
25
+ async function testParallelWithAgentLabeling() {
26
+ console.log('Testing Parallel Multi-Agent with Agent Labeling...\n');
27
+
28
+ // Set up content aggregator
29
+ const { contentParts, aggregateContent } = createContentAggregator();
30
+
31
+ // Define specialized agents
32
+ const agents: t.AgentInputs[] = [
33
+ {
34
+ agentId: 'researcher',
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 research coordinator. Analyze the request and provide 2-3 sentence coordination brief.`,
41
+ },
42
+ {
43
+ agentId: 'analyst1',
44
+ provider: Providers.ANTHROPIC,
45
+ clientOptions: {
46
+ modelName: 'claude-haiku-4-5',
47
+ apiKey: process.env.ANTHROPIC_API_KEY,
48
+ },
49
+ instructions: `You are FINANCIAL ANALYST. Provide 2-3 sentence financial analysis. Start with "FINANCIAL ANALYSIS:"`,
50
+ },
51
+ {
52
+ agentId: 'analyst2',
53
+ provider: Providers.ANTHROPIC,
54
+ clientOptions: {
55
+ modelName: 'claude-haiku-4-5',
56
+ apiKey: process.env.ANTHROPIC_API_KEY,
57
+ },
58
+ instructions: `You are TECHNICAL ANALYST. Provide 2-3 sentence technical analysis. Start with "TECHNICAL ANALYSIS:"`,
59
+ },
60
+ {
61
+ agentId: 'analyst3',
62
+ provider: Providers.ANTHROPIC,
63
+ clientOptions: {
64
+ modelName: 'claude-haiku-4-5',
65
+ apiKey: process.env.ANTHROPIC_API_KEY,
66
+ },
67
+ instructions: `You are MARKET ANALYST. Provide 2-3 sentence market analysis. Start with "MARKET ANALYSIS:"`,
68
+ },
69
+ {
70
+ agentId: 'summarizer',
71
+ provider: Providers.ANTHROPIC,
72
+ clientOptions: {
73
+ modelName: 'claude-haiku-4-5',
74
+ apiKey: process.env.ANTHROPIC_API_KEY,
75
+ },
76
+ instructions: `You are SYNTHESIS EXPERT. Review all analyses and provide 2-3 sentence integrated summary.`,
77
+ },
78
+ ];
79
+
80
+ // Define direct edges (fan-out and fan-in)
81
+ const edges: t.GraphEdge[] = [
82
+ {
83
+ from: 'researcher',
84
+ to: ['analyst1', 'analyst2', 'analyst3'],
85
+ description: 'Distribute research to specialist analysts',
86
+ edgeType: 'direct',
87
+ },
88
+ {
89
+ from: ['analyst1', 'analyst2', 'analyst3'],
90
+ to: 'summarizer',
91
+ description: 'Aggregate analysis results',
92
+ edgeType: 'direct',
93
+ prompt:
94
+ 'Based on the analyses below, provide an integrated summary:\n\n{results}',
95
+ },
96
+ ];
97
+
98
+ // Create custom handlers
99
+ const customHandlers = {
100
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
101
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
102
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
103
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
104
+ handle: (
105
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
106
+ data: t.StreamEventData
107
+ ): void => {
108
+ aggregateContent({
109
+ event,
110
+ data: data as unknown as { result: t.ToolEndEvent },
111
+ });
112
+ },
113
+ },
114
+ [GraphEvents.ON_RUN_STEP]: {
115
+ handle: (
116
+ event: GraphEvents.ON_RUN_STEP,
117
+ data: t.StreamEventData
118
+ ): void => {
119
+ aggregateContent({ event, data: data as t.RunStep });
120
+ },
121
+ },
122
+ [GraphEvents.ON_MESSAGE_DELTA]: {
123
+ handle: (
124
+ event: GraphEvents.ON_MESSAGE_DELTA,
125
+ data: t.StreamEventData
126
+ ): void => {
127
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
128
+ },
129
+ },
130
+ };
131
+
132
+ try {
133
+ const query = 'What are the implications of widespread AI adoption?';
134
+
135
+ console.log(`${'='.repeat(80)}`);
136
+ console.log(`FIRST RUN - USER QUERY: "${query}"`);
137
+ console.log('='.repeat(80));
138
+
139
+ // Reset conversation
140
+ conversationHistory.length = 0;
141
+ conversationHistory.push(new HumanMessage(query));
142
+
143
+ // Create graph
144
+ const runConfig: t.RunConfig = {
145
+ runId: `parallel-test-${Date.now()}`,
146
+ graphConfig: {
147
+ type: 'multi-agent',
148
+ agents,
149
+ edges,
150
+ },
151
+ customHandlers,
152
+ returnContent: true,
153
+ };
154
+
155
+ const run = await Run.create(runConfig);
156
+
157
+ console.log('\nProcessing first run with parallel agents...\n');
158
+
159
+ const config = {
160
+ configurable: {
161
+ thread_id: 'parallel-agent-labeling-1',
162
+ },
163
+ streamMode: 'values',
164
+ version: 'v2' as const,
165
+ };
166
+
167
+ const inputs = {
168
+ messages: conversationHistory,
169
+ };
170
+
171
+ await run.processStream(inputs, config);
172
+ const finalMessages = run.getRunMessages();
173
+
174
+ if (finalMessages) {
175
+ conversationHistory.push(...finalMessages);
176
+ }
177
+
178
+ // Show agent participation
179
+ console.log(`\n${'─'.repeat(80)}`);
180
+ console.log('FIRST RUN - AGENT PARTICIPATION:');
181
+ console.log('─'.repeat(80));
182
+
183
+ if (run.Graph) {
184
+ const activeAgents = run.Graph.getActiveAgentIds();
185
+ console.log(`\nActive agents (${activeAgents.length}):`, activeAgents);
186
+
187
+ const stepsByAgent = run.Graph.getRunStepsByAgent();
188
+ stepsByAgent.forEach((steps, agentId) => {
189
+ console.log(` ${agentId}: ${steps.length} steps`);
190
+ });
191
+
192
+ console.log(`\nTotal content parts: ${contentParts.length}`);
193
+ }
194
+
195
+ // =============================================================
196
+ // SECOND RUN: Test with agent-labeled history
197
+ // =============================================================
198
+ console.log(`\n${'='.repeat(80)}`);
199
+ console.log(`SECOND RUN - Simulating DB Load with Parallel Agent Labeling`);
200
+ console.log('='.repeat(80));
201
+
202
+ // Simulate DB storage
203
+ const dbStoredContentParts = [...contentParts];
204
+ const dbStoredAgentIdMap = Object.fromEntries(
205
+ run.Graph!.getContentPartAgentMap()
206
+ );
207
+
208
+ console.log('\n📦 Simulating DB storage:');
209
+ console.log(` - Stored ${dbStoredContentParts.length} content parts`);
210
+ console.log(
211
+ ` - Stored agent mappings for ${Object.keys(dbStoredAgentIdMap).length} parts`
212
+ );
213
+
214
+ // Load and label by agent with labelNonTransferContent option
215
+ console.log('\n📥 Loading from DB and labeling ALL agent content...');
216
+
217
+ const agentNames = {
218
+ researcher: 'Researcher',
219
+ analyst1: 'Financial Analyst',
220
+ analyst2: 'Technical Analyst',
221
+ analyst3: 'Market Analyst',
222
+ summarizer: 'Synthesizer',
223
+ };
224
+
225
+ const labeledContentParts = labelContentByAgent(
226
+ dbStoredContentParts.filter(
227
+ (p): p is t.MessageContentComplex => p != null
228
+ ),
229
+ dbStoredAgentIdMap,
230
+ agentNames,
231
+ { labelNonTransferContent: true } // NEW: Label all content
232
+ );
233
+
234
+ console.log(
235
+ ` - Labeled ${labeledContentParts.length} content groups by agent`
236
+ );
237
+
238
+ // Convert to payload
239
+ const payload: t.TPayload = [
240
+ {
241
+ role: 'user',
242
+ content: query,
243
+ },
244
+ {
245
+ role: 'assistant',
246
+ content: labeledContentParts,
247
+ },
248
+ ];
249
+
250
+ // Format using formatAgentMessages
251
+ console.log('\n🔧 Calling formatAgentMessages...');
252
+ const { messages: formattedMessages } = formatAgentMessages(payload);
253
+
254
+ console.log(` - Formatted into ${formattedMessages.length} BaseMessages`);
255
+
256
+ // Show preview
257
+ console.log('\n👁️ Preview of formatted history:');
258
+ console.log('─'.repeat(80));
259
+ for (let i = 0; i < formattedMessages.length; i++) {
260
+ const msg = formattedMessages[i];
261
+ const role = msg._getType();
262
+ const preview =
263
+ typeof msg.content === 'string'
264
+ ? msg.content.slice(0, 300)
265
+ : JSON.stringify(msg.content).slice(0, 300);
266
+ console.log(
267
+ `[${i}] ${role}: ${preview}${preview.length >= 300 ? '...' : ''}`
268
+ );
269
+ console.log('');
270
+ }
271
+ console.log('─'.repeat(80));
272
+
273
+ // Create a second run with labeled history
274
+ console.log(
275
+ '\n🚀 Starting second run with agent-labeled parallel history...'
276
+ );
277
+ const followupQuery = 'Which analyst identified the most significant risk?';
278
+ console.log(` Followup: "${followupQuery}"`);
279
+
280
+ const secondRunHistory: BaseMessage[] = [
281
+ ...formattedMessages,
282
+ new HumanMessage(followupQuery),
283
+ ];
284
+
285
+ const runConfig2: t.RunConfig = {
286
+ runId: `parallel-test-2-${Date.now()}`,
287
+ graphConfig: {
288
+ type: 'multi-agent',
289
+ agents,
290
+ edges,
291
+ },
292
+ customHandlers,
293
+ returnContent: true,
294
+ };
295
+
296
+ const run2 = await Run.create(runConfig2);
297
+
298
+ const inputs2 = {
299
+ messages: secondRunHistory,
300
+ };
301
+
302
+ await run2.processStream(inputs2, config);
303
+
304
+ console.log('\n✅ Second run completed successfully!');
305
+ console.log(
306
+ ' The researcher correctly understood that parallel analysts handled'
307
+ );
308
+ console.log(' the previous analysis, with clear attribution per agent.');
309
+
310
+ console.log(`\n${'='.repeat(80)}`);
311
+ console.log('TEST COMPLETE');
312
+ console.log('='.repeat(80));
313
+ console.log(
314
+ '\nThis demonstrates that parallel multi-agent patterns work correctly'
315
+ );
316
+ console.log(
317
+ 'with agent labeling, preventing confusion about who said what.'
318
+ );
319
+ } catch (error) {
320
+ console.error('Error in parallel agent labeling test:', error);
321
+ }
322
+ }
323
+
324
+ // Run the test
325
+ testParallelWithAgentLabeling();
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { config } from 'dotenv';
4
+ config();
5
+
6
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
7
+ import { Run } from '@/run';
8
+ import { ChatModelStreamHandler } from '@/stream';
9
+ import { Providers, GraphEvents } from '@/common';
10
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
11
+ import type * as t from '@/types';
12
+
13
+ const conversationHistory: BaseMessage[] = [];
14
+
15
+ /**
16
+ * Test edge case: switching from OpenAI supervisor (no thinking) to Bedrock specialist (with thinking enabled)
17
+ * This should not throw an error about missing thinking blocks
18
+ */
19
+ async function testBedrockThinkingHandoff() {
20
+ console.log('Testing OpenAI → Bedrock (with thinking) handoff...\n');
21
+
22
+ // Create custom handlers
23
+ const customHandlers = {
24
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
25
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
26
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
27
+ [GraphEvents.TOOL_START]: {
28
+ handle: (_event: string, data: t.StreamEventData): void => {
29
+ const toolData = data as any;
30
+ if (toolData?.name) {
31
+ console.log(`\n🔧 Tool called: ${toolData.name}`);
32
+ }
33
+ },
34
+ },
35
+ };
36
+
37
+ // Create the graph configuration
38
+ function createGraphConfig(): t.RunConfig {
39
+ console.log(
40
+ 'Creating graph with OpenAI supervisor and Bedrock specialist with thinking enabled.\n'
41
+ );
42
+
43
+ const agents: t.AgentInputs[] = [
44
+ {
45
+ agentId: 'supervisor',
46
+ provider: Providers.OPENAI,
47
+ clientOptions: {
48
+ modelName: 'gpt-4o-mini',
49
+ apiKey: process.env.OPENAI_API_KEY,
50
+ },
51
+ instructions: `You are a task supervisor. When the user asks about code review, use transfer_to_code_reviewer to hand off to the specialist.`,
52
+ maxContextTokens: 8000,
53
+ },
54
+ {
55
+ agentId: 'code_reviewer',
56
+ provider: Providers.BEDROCK,
57
+ clientOptions: {
58
+ region: process.env.BEDROCK_AWS_REGION || 'us-east-1',
59
+ model: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
60
+ credentials: {
61
+ accessKeyId: process.env.BEDROCK_AWS_ACCESS_KEY_ID!,
62
+ secretAccessKey: process.env.BEDROCK_AWS_SECRET_ACCESS_KEY!,
63
+ },
64
+ additionalModelRequestFields: {
65
+ thinking: {
66
+ type: 'enabled',
67
+ budget_tokens: 2000,
68
+ },
69
+ },
70
+ },
71
+ instructions: `You are a code review specialist using Bedrock with extended thinking. Think carefully about the code quality, best practices, and potential issues. Provide thoughtful feedback.`,
72
+ maxContextTokens: 8000,
73
+ },
74
+ ];
75
+
76
+ const edges: t.GraphEdge[] = [
77
+ {
78
+ from: 'supervisor',
79
+ to: ['code_reviewer'],
80
+ description: 'Transfer to code review specialist',
81
+ edgeType: 'handoff',
82
+ },
83
+ ];
84
+
85
+ return {
86
+ runId: `bedrock-thinking-handoff-test-${Date.now()}`,
87
+ graphConfig: {
88
+ type: 'multi-agent',
89
+ agents,
90
+ edges,
91
+ },
92
+ customHandlers,
93
+ returnContent: true,
94
+ };
95
+ }
96
+
97
+ try {
98
+ // Test query that should trigger a handoff
99
+ const query =
100
+ 'Can you review this function and tell me if there are any issues?\n\nfunction add(a, b) { return a + b; }';
101
+
102
+ console.log(`${'='.repeat(60)}`);
103
+ console.log(`USER QUERY: "${query}"`);
104
+ console.log('='.repeat(60));
105
+
106
+ // Initialize conversation
107
+ conversationHistory.push(new HumanMessage(query));
108
+
109
+ // Create and run the graph
110
+ const runConfig = createGraphConfig();
111
+ const run = await Run.create(runConfig);
112
+
113
+ const config = {
114
+ configurable: {
115
+ thread_id: 'bedrock-thinking-handoff-test-1',
116
+ },
117
+ streamMode: 'values',
118
+ version: 'v2' as const,
119
+ };
120
+
121
+ console.log('\nProcessing request...\n');
122
+
123
+ // Process with streaming
124
+ const inputs = {
125
+ messages: conversationHistory,
126
+ };
127
+
128
+ await run.processStream(inputs, config);
129
+ const finalMessages = run.getRunMessages();
130
+
131
+ if (finalMessages) {
132
+ conversationHistory.push(...finalMessages);
133
+ }
134
+
135
+ // Success!
136
+ console.log(`\n${'='.repeat(60)}`);
137
+ console.log('✅ TEST PASSED');
138
+ console.log('='.repeat(60));
139
+ console.log('\nSuccessfully handed off from OpenAI (no thinking) to');
140
+ console.log('Bedrock with thinking enabled without errors!');
141
+ console.log('\nThe ensureThinkingBlockInMessages() function correctly');
142
+ console.log('handled the transition by converting tool sequences to');
143
+ console.log('HumanMessages before calling the Bedrock API.');
144
+ } catch (error) {
145
+ console.error('\n❌ TEST FAILED');
146
+ console.error('='.repeat(60));
147
+ console.error('Error:', error);
148
+ process.exit(1);
149
+ }
150
+ }
151
+
152
+ // Run the test
153
+ testBedrockThinkingHandoff();