@librechat/agents 3.0.0-rc1 → 3.0.0-rc11

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 (187) hide show
  1. package/dist/cjs/common/enum.cjs +1 -0
  2. package/dist/cjs/common/enum.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +7 -2
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs +229 -44
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  7. package/dist/cjs/llm/anthropic/index.cjs +21 -2
  8. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  9. package/dist/cjs/llm/google/index.cjs +3 -0
  10. package/dist/cjs/llm/google/index.cjs.map +1 -1
  11. package/dist/cjs/llm/google/utils/common.cjs +13 -0
  12. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  13. package/dist/cjs/llm/ollama/index.cjs +3 -0
  14. package/dist/cjs/llm/ollama/index.cjs.map +1 -1
  15. package/dist/cjs/llm/openai/index.cjs +53 -1
  16. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  17. package/dist/cjs/llm/openai/utils/index.cjs +6 -1
  18. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  19. package/dist/cjs/llm/openrouter/index.cjs +5 -1
  20. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  21. package/dist/cjs/llm/vertexai/index.cjs +1 -1
  22. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  23. package/dist/cjs/main.cjs +3 -1
  24. package/dist/cjs/main.cjs.map +1 -1
  25. package/dist/cjs/messages/core.cjs +5 -1
  26. package/dist/cjs/messages/core.cjs.map +1 -1
  27. package/dist/cjs/messages/format.cjs +52 -34
  28. package/dist/cjs/messages/format.cjs.map +1 -1
  29. package/dist/cjs/messages/prune.cjs +28 -0
  30. package/dist/cjs/messages/prune.cjs.map +1 -1
  31. package/dist/cjs/run.cjs +28 -15
  32. package/dist/cjs/run.cjs.map +1 -1
  33. package/dist/cjs/stream.cjs +1 -1
  34. package/dist/cjs/stream.cjs.map +1 -1
  35. package/dist/cjs/tools/ToolNode.cjs +2 -0
  36. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  37. package/dist/cjs/tools/search/firecrawl.cjs +3 -1
  38. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  39. package/dist/cjs/tools/search/rerankers.cjs +8 -6
  40. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  41. package/dist/cjs/tools/search/search.cjs +5 -5
  42. package/dist/cjs/tools/search/search.cjs.map +1 -1
  43. package/dist/cjs/tools/search/serper-scraper.cjs +132 -0
  44. package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -0
  45. package/dist/cjs/tools/search/tool.cjs +46 -9
  46. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  47. package/dist/cjs/utils/handlers.cjs +70 -0
  48. package/dist/cjs/utils/handlers.cjs.map +1 -0
  49. package/dist/esm/common/enum.mjs +1 -0
  50. package/dist/esm/common/enum.mjs.map +1 -1
  51. package/dist/esm/graphs/Graph.mjs +7 -2
  52. package/dist/esm/graphs/Graph.mjs.map +1 -1
  53. package/dist/esm/graphs/MultiAgentGraph.mjs +230 -45
  54. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  55. package/dist/esm/llm/anthropic/index.mjs +21 -2
  56. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  57. package/dist/esm/llm/google/index.mjs +3 -0
  58. package/dist/esm/llm/google/index.mjs.map +1 -1
  59. package/dist/esm/llm/google/utils/common.mjs +13 -0
  60. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  61. package/dist/esm/llm/ollama/index.mjs +3 -0
  62. package/dist/esm/llm/ollama/index.mjs.map +1 -1
  63. package/dist/esm/llm/openai/index.mjs +53 -1
  64. package/dist/esm/llm/openai/index.mjs.map +1 -1
  65. package/dist/esm/llm/openai/utils/index.mjs +6 -1
  66. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  67. package/dist/esm/llm/openrouter/index.mjs +5 -1
  68. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  69. package/dist/esm/llm/vertexai/index.mjs +1 -1
  70. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  71. package/dist/esm/main.mjs +2 -1
  72. package/dist/esm/main.mjs.map +1 -1
  73. package/dist/esm/messages/core.mjs +5 -1
  74. package/dist/esm/messages/core.mjs.map +1 -1
  75. package/dist/esm/messages/format.mjs +52 -34
  76. package/dist/esm/messages/format.mjs.map +1 -1
  77. package/dist/esm/messages/prune.mjs +28 -0
  78. package/dist/esm/messages/prune.mjs.map +1 -1
  79. package/dist/esm/run.mjs +28 -15
  80. package/dist/esm/run.mjs.map +1 -1
  81. package/dist/esm/stream.mjs +1 -1
  82. package/dist/esm/stream.mjs.map +1 -1
  83. package/dist/esm/tools/ToolNode.mjs +2 -0
  84. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  85. package/dist/esm/tools/search/firecrawl.mjs +3 -1
  86. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  87. package/dist/esm/tools/search/rerankers.mjs +8 -6
  88. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  89. package/dist/esm/tools/search/search.mjs +5 -5
  90. package/dist/esm/tools/search/search.mjs.map +1 -1
  91. package/dist/esm/tools/search/serper-scraper.mjs +129 -0
  92. package/dist/esm/tools/search/serper-scraper.mjs.map +1 -0
  93. package/dist/esm/tools/search/tool.mjs +46 -9
  94. package/dist/esm/tools/search/tool.mjs.map +1 -1
  95. package/dist/esm/utils/handlers.mjs +68 -0
  96. package/dist/esm/utils/handlers.mjs.map +1 -0
  97. package/dist/types/common/enum.d.ts +2 -1
  98. package/dist/types/graphs/MultiAgentGraph.d.ts +12 -2
  99. package/dist/types/llm/anthropic/index.d.ts +3 -0
  100. package/dist/types/llm/google/index.d.ts +1 -0
  101. package/dist/types/llm/ollama/index.d.ts +1 -0
  102. package/dist/types/llm/openai/index.d.ts +14 -0
  103. package/dist/types/llm/openrouter/index.d.ts +4 -2
  104. package/dist/types/llm/vertexai/index.d.ts +1 -1
  105. package/dist/types/messages/format.d.ts +23 -20
  106. package/dist/types/run.d.ts +1 -1
  107. package/dist/types/tools/search/firecrawl.d.ts +2 -1
  108. package/dist/types/tools/search/rerankers.d.ts +4 -1
  109. package/dist/types/tools/search/search.d.ts +1 -2
  110. package/dist/types/tools/search/serper-scraper.d.ts +59 -0
  111. package/dist/types/tools/search/tool.d.ts +25 -4
  112. package/dist/types/tools/search/types.d.ts +31 -1
  113. package/dist/types/types/graph.d.ts +38 -4
  114. package/dist/types/types/llm.d.ts +1 -0
  115. package/dist/types/types/run.d.ts +5 -1
  116. package/dist/types/utils/handlers.d.ts +34 -0
  117. package/dist/types/utils/index.d.ts +1 -0
  118. package/package.json +11 -3
  119. package/src/common/enum.ts +1 -0
  120. package/src/graphs/Graph.ts +8 -2
  121. package/src/graphs/MultiAgentGraph.ts +267 -50
  122. package/src/llm/anthropic/index.ts +23 -2
  123. package/src/llm/google/index.ts +4 -0
  124. package/src/llm/google/utils/common.ts +14 -0
  125. package/src/llm/ollama/index.ts +3 -0
  126. package/src/llm/openai/index.ts +60 -1
  127. package/src/llm/openai/utils/index.ts +7 -1
  128. package/src/llm/openrouter/index.ts +15 -6
  129. package/src/llm/vertexai/index.ts +2 -2
  130. package/src/messages/core.ts +5 -2
  131. package/src/messages/format.ts +67 -39
  132. package/src/messages/formatMessage.test.ts +418 -2
  133. package/src/messages/prune.ts +51 -0
  134. package/src/run.ts +38 -27
  135. package/src/scripts/multi-agent-chain.ts +278 -0
  136. package/src/scripts/multi-agent-document-review-chain.ts +197 -0
  137. package/src/scripts/multi-agent-hybrid-flow.ts +310 -0
  138. package/src/scripts/multi-agent-parallel.ts +27 -23
  139. package/src/scripts/multi-agent-supervisor.ts +362 -0
  140. package/src/scripts/search.ts +5 -1
  141. package/src/scripts/test-custom-prompt-key.ts +145 -0
  142. package/src/scripts/test-handoff-input.ts +170 -0
  143. package/src/scripts/test-multi-agent-list-handoff.ts +261 -0
  144. package/src/scripts/test-tools-before-handoff.ts +233 -0
  145. package/src/scripts/tools.ts +4 -1
  146. package/src/stream.ts +4 -1
  147. package/src/tools/search/firecrawl.ts +5 -2
  148. package/src/tools/search/jina-reranker.test.ts +126 -0
  149. package/src/tools/search/rerankers.ts +11 -5
  150. package/src/tools/search/search.ts +6 -8
  151. package/src/tools/search/serper-scraper.ts +155 -0
  152. package/src/tools/search/tool.ts +49 -8
  153. package/src/tools/search/types.ts +46 -0
  154. package/src/types/graph.ts +51 -5
  155. package/src/types/llm.ts +1 -0
  156. package/src/types/run.ts +6 -1
  157. package/src/utils/handlers.ts +107 -0
  158. package/src/utils/index.ts +2 -1
  159. package/src/utils/llmConfig.ts +35 -1
  160. package/dist/types/scripts/abort.d.ts +0 -1
  161. package/dist/types/scripts/ant_web_search.d.ts +0 -1
  162. package/dist/types/scripts/args.d.ts +0 -7
  163. package/dist/types/scripts/caching.d.ts +0 -1
  164. package/dist/types/scripts/cli.d.ts +0 -1
  165. package/dist/types/scripts/cli2.d.ts +0 -1
  166. package/dist/types/scripts/cli3.d.ts +0 -1
  167. package/dist/types/scripts/cli4.d.ts +0 -1
  168. package/dist/types/scripts/cli5.d.ts +0 -1
  169. package/dist/types/scripts/code_exec.d.ts +0 -1
  170. package/dist/types/scripts/code_exec_files.d.ts +0 -1
  171. package/dist/types/scripts/code_exec_simple.d.ts +0 -1
  172. package/dist/types/scripts/content.d.ts +0 -1
  173. package/dist/types/scripts/empty_input.d.ts +0 -1
  174. package/dist/types/scripts/handoff-test.d.ts +0 -1
  175. package/dist/types/scripts/image.d.ts +0 -1
  176. package/dist/types/scripts/memory.d.ts +0 -1
  177. package/dist/types/scripts/multi-agent-conditional.d.ts +0 -1
  178. package/dist/types/scripts/multi-agent-parallel.d.ts +0 -1
  179. package/dist/types/scripts/multi-agent-sequence.d.ts +0 -1
  180. package/dist/types/scripts/multi-agent-test.d.ts +0 -1
  181. package/dist/types/scripts/search.d.ts +0 -1
  182. package/dist/types/scripts/simple.d.ts +0 -1
  183. package/dist/types/scripts/stream.d.ts +0 -1
  184. package/dist/types/scripts/thinking.d.ts +0 -1
  185. package/dist/types/scripts/tools.d.ts +0 -1
  186. package/dist/types/specs/spec.utils.d.ts +0 -1
  187. package/src/scripts/multi-agent-example-output.md +0 -110
@@ -1,8 +1,43 @@
1
- import { HumanMessage, AIMessage, SystemMessage } from '@langchain/core/messages';
2
- import { formatMessage, formatLangChainMessages, formatFromLangChain } from './format';
1
+ import {
2
+ HumanMessage,
3
+ AIMessage,
4
+ SystemMessage,
5
+ } from '@langchain/core/messages';
6
+ import type { MessageContentComplex } from '@/types';
7
+ import {
8
+ formatMessage,
9
+ formatLangChainMessages,
10
+ formatFromLangChain,
11
+ formatMediaMessage,
12
+ } from './format';
13
+ import { Providers } from '@/common';
3
14
 
4
15
  const NO_PARENT = '00000000-0000-0000-0000-000000000000';
5
16
 
17
+ /**
18
+ * Type for formatted message results with media content
19
+ */
20
+ interface FormattedMediaMessage {
21
+ role: string;
22
+ content: MessageContentComplex[];
23
+ name?: string;
24
+ }
25
+
26
+ /**
27
+ * Type guard to check if result is a FormattedMediaMessage
28
+ */
29
+ function isFormattedMediaMessage(
30
+ result: unknown
31
+ ): result is FormattedMediaMessage {
32
+ return (
33
+ typeof result === 'object' &&
34
+ result !== null &&
35
+ 'role' in result &&
36
+ 'content' in result &&
37
+ Array.isArray((result as FormattedMediaMessage).content)
38
+ );
39
+ }
40
+
6
41
  describe('formatMessage', () => {
7
42
  it('formats user message', () => {
8
43
  const input = {
@@ -187,6 +222,387 @@ describe('formatMessage', () => {
187
222
  });
188
223
  });
189
224
 
225
+ describe('formatMediaMessage', () => {
226
+ it('formats message with images for default provider', () => {
227
+ const message = {
228
+ role: 'user',
229
+ content: 'Check out this image',
230
+ name: 'John',
231
+ };
232
+ const mediaParts = [
233
+ {
234
+ type: 'image_url',
235
+ image_url: { url: 'https://example.com/image1.jpg' },
236
+ },
237
+ {
238
+ type: 'image_url',
239
+ image_url: { url: 'https://example.com/image2.jpg' },
240
+ },
241
+ ];
242
+
243
+ const result = formatMediaMessage({ message, mediaParts });
244
+
245
+ expect(result.role).toBe('user');
246
+ expect(result.name).toBe('John');
247
+ expect(Array.isArray(result.content)).toBe(true);
248
+ expect(result.content).toHaveLength(3);
249
+ expect(result.content[0]).toEqual({
250
+ type: 'text',
251
+ text: 'Check out this image',
252
+ });
253
+ expect(result.content[1]).toEqual(mediaParts[0]);
254
+ expect(result.content[2]).toEqual(mediaParts[1]);
255
+ });
256
+
257
+ it('formats message with images for Anthropic (media first)', () => {
258
+ const message = {
259
+ role: 'user',
260
+ content: 'Check out this image',
261
+ };
262
+ const mediaParts = [
263
+ {
264
+ type: 'image_url',
265
+ image_url: { url: 'https://example.com/image.jpg' },
266
+ },
267
+ ];
268
+
269
+ const result = formatMediaMessage({
270
+ message,
271
+ mediaParts,
272
+ endpoint: Providers.ANTHROPIC,
273
+ });
274
+
275
+ expect(result.content).toHaveLength(2);
276
+ expect(result.content[0]).toEqual(mediaParts[0]);
277
+ expect(result.content[1]).toEqual({
278
+ type: 'text',
279
+ text: 'Check out this image',
280
+ });
281
+ });
282
+
283
+ it('formats message with multiple media types', () => {
284
+ const message = {
285
+ role: 'user',
286
+ content: 'Check out these files',
287
+ };
288
+ const mediaParts = [
289
+ { type: 'document', document: { url: 'https://example.com/doc.pdf' } },
290
+ { type: 'video', video: { url: 'https://example.com/video.mp4' } },
291
+ { type: 'audio', audio: { url: 'https://example.com/audio.mp3' } },
292
+ {
293
+ type: 'image_url',
294
+ image_url: { url: 'https://example.com/image.jpg' },
295
+ },
296
+ ];
297
+
298
+ const result = formatMediaMessage({ message, mediaParts });
299
+
300
+ expect(result.content).toHaveLength(5);
301
+ expect(result.content[0]).toEqual({
302
+ type: 'text',
303
+ text: 'Check out these files',
304
+ });
305
+ expect(result.content[1]).toEqual(mediaParts[0]);
306
+ expect(result.content[2]).toEqual(mediaParts[1]);
307
+ expect(result.content[3]).toEqual(mediaParts[2]);
308
+ expect(result.content[4]).toEqual(mediaParts[3]);
309
+ });
310
+ });
311
+
312
+ describe('formatMessage with media', () => {
313
+ it('formats user message with image_urls (backward compatibility)', () => {
314
+ const input = {
315
+ message: {
316
+ sender: 'user',
317
+ text: 'Check out this image',
318
+ image_urls: [
319
+ {
320
+ type: 'image_url' as const,
321
+ image_url: { url: 'https://example.com/image.jpg' },
322
+ },
323
+ ],
324
+ },
325
+ userName: 'John',
326
+ };
327
+
328
+ const result = formatMessage(input);
329
+
330
+ expect(isFormattedMediaMessage(result)).toBe(true);
331
+ if (isFormattedMediaMessage(result)) {
332
+ expect(result.role).toBe('user');
333
+ expect(result.name).toBe('John');
334
+ expect(Array.isArray(result.content)).toBe(true);
335
+ expect(result.content).toHaveLength(2);
336
+ expect(result.content[0]).toEqual({
337
+ type: 'text',
338
+ text: 'Check out this image',
339
+ });
340
+ expect(result.content[1]).toEqual(input.message.image_urls[0]);
341
+ }
342
+ });
343
+
344
+ it('formats user message with documents', () => {
345
+ const input = {
346
+ message: {
347
+ role: 'user',
348
+ content: 'Review this document',
349
+ documents: [
350
+ {
351
+ type: 'document',
352
+ document: { url: 'https://example.com/report.pdf' },
353
+ },
354
+ ],
355
+ },
356
+ };
357
+
358
+ const result = formatMessage(input);
359
+
360
+ expect(isFormattedMediaMessage(result)).toBe(true);
361
+ if (isFormattedMediaMessage(result)) {
362
+ expect(result.role).toBe('user');
363
+ expect(Array.isArray(result.content)).toBe(true);
364
+ expect(result.content).toHaveLength(2);
365
+ expect(result.content[0]).toEqual({
366
+ type: 'text',
367
+ text: 'Review this document',
368
+ });
369
+ expect(result.content[1]).toEqual(input.message.documents[0]);
370
+ }
371
+ });
372
+
373
+ it('formats user message with videos', () => {
374
+ const input = {
375
+ message: {
376
+ role: 'user',
377
+ content: 'Watch this video',
378
+ videos: [
379
+ { type: 'video', video: { url: 'https://example.com/demo.mp4' } },
380
+ ],
381
+ },
382
+ };
383
+
384
+ const result = formatMessage(input);
385
+
386
+ expect(isFormattedMediaMessage(result)).toBe(true);
387
+ if (isFormattedMediaMessage(result)) {
388
+ expect(result.role).toBe('user');
389
+ expect(Array.isArray(result.content)).toBe(true);
390
+ expect(result.content).toHaveLength(2);
391
+ expect(result.content[0]).toEqual({
392
+ type: 'text',
393
+ text: 'Watch this video',
394
+ });
395
+ expect(result.content[1]).toEqual(input.message.videos[0]);
396
+ }
397
+ });
398
+
399
+ it('formats user message with audios', () => {
400
+ const input = {
401
+ message: {
402
+ role: 'user',
403
+ content: 'Listen to this',
404
+ audios: [
405
+ { type: 'audio', audio: { url: 'https://example.com/podcast.mp3' } },
406
+ ],
407
+ },
408
+ };
409
+
410
+ const result = formatMessage(input);
411
+
412
+ expect(isFormattedMediaMessage(result)).toBe(true);
413
+ if (isFormattedMediaMessage(result)) {
414
+ expect(result.role).toBe('user');
415
+ expect(Array.isArray(result.content)).toBe(true);
416
+ expect(result.content).toHaveLength(2);
417
+ expect(result.content[0]).toEqual({
418
+ type: 'text',
419
+ text: 'Listen to this',
420
+ });
421
+ expect(result.content[1]).toEqual(input.message.audios[0]);
422
+ }
423
+ });
424
+
425
+ it('formats user message with all media types in correct order', () => {
426
+ const input = {
427
+ message: {
428
+ role: 'user',
429
+ content: 'Check out all these files',
430
+ documents: [
431
+ {
432
+ type: 'document',
433
+ document: { url: 'https://example.com/doc.pdf' },
434
+ },
435
+ ],
436
+ videos: [
437
+ { type: 'video', video: { url: 'https://example.com/video.mp4' } },
438
+ ],
439
+ audios: [
440
+ { type: 'audio', audio: { url: 'https://example.com/audio.mp3' } },
441
+ ],
442
+ image_urls: [
443
+ {
444
+ type: 'image_url' as const,
445
+ image_url: { url: 'https://example.com/image.jpg' },
446
+ },
447
+ ],
448
+ },
449
+ };
450
+
451
+ const result = formatMessage(input);
452
+
453
+ expect(isFormattedMediaMessage(result)).toBe(true);
454
+ if (isFormattedMediaMessage(result)) {
455
+ expect(result.role).toBe('user');
456
+ expect(Array.isArray(result.content)).toBe(true);
457
+ expect(result.content).toHaveLength(5);
458
+ // Text first
459
+ expect(result.content[0]).toEqual({
460
+ type: 'text',
461
+ text: 'Check out all these files',
462
+ });
463
+ // Then documents, videos, audios, images
464
+ expect(result.content[1]).toEqual(input.message.documents[0]);
465
+ expect(result.content[2]).toEqual(input.message.videos[0]);
466
+ expect(result.content[3]).toEqual(input.message.audios[0]);
467
+ expect(result.content[4]).toEqual(input.message.image_urls[0]);
468
+ }
469
+ });
470
+
471
+ it('formats user message with multiple files of the same type', () => {
472
+ const input = {
473
+ message: {
474
+ role: 'user',
475
+ content: 'Review these documents',
476
+ documents: [
477
+ {
478
+ type: 'document',
479
+ document: { url: 'https://example.com/doc1.pdf' },
480
+ },
481
+ {
482
+ type: 'document',
483
+ document: { url: 'https://example.com/doc2.pdf' },
484
+ },
485
+ {
486
+ type: 'document',
487
+ document: { url: 'https://example.com/doc3.pdf' },
488
+ },
489
+ ],
490
+ },
491
+ };
492
+
493
+ const result = formatMessage(input);
494
+
495
+ expect(isFormattedMediaMessage(result)).toBe(true);
496
+ if (isFormattedMediaMessage(result)) {
497
+ expect(result.content).toHaveLength(4);
498
+ expect(result.content[0].type).toBe('text');
499
+ expect(result.content[1]).toEqual(input.message.documents[0]);
500
+ expect(result.content[2]).toEqual(input.message.documents[1]);
501
+ expect(result.content[3]).toEqual(input.message.documents[2]);
502
+ }
503
+ });
504
+
505
+ it('respects Anthropic provider ordering (media before text)', () => {
506
+ const input = {
507
+ message: {
508
+ role: 'user',
509
+ content: 'Check this out',
510
+ documents: [
511
+ {
512
+ type: 'document',
513
+ document: { url: 'https://example.com/doc.pdf' },
514
+ },
515
+ ],
516
+ image_urls: [
517
+ {
518
+ type: 'image_url' as const,
519
+ image_url: { url: 'https://example.com/image.jpg' },
520
+ },
521
+ ],
522
+ },
523
+ endpoint: Providers.ANTHROPIC,
524
+ };
525
+
526
+ const result = formatMessage(input);
527
+
528
+ expect(isFormattedMediaMessage(result)).toBe(true);
529
+ if (isFormattedMediaMessage(result)) {
530
+ expect(result.content).toHaveLength(3);
531
+ // Media first for Anthropic
532
+ expect(result.content[0]).toEqual(input.message.documents[0]);
533
+ expect(result.content[1]).toEqual(input.message.image_urls[0]);
534
+ expect(result.content[2]).toEqual({
535
+ type: 'text',
536
+ text: 'Check this out',
537
+ });
538
+ }
539
+ });
540
+
541
+ it('does not format media for assistant messages', () => {
542
+ const input = {
543
+ message: {
544
+ role: 'assistant',
545
+ content: 'Here is a response',
546
+ documents: [
547
+ {
548
+ type: 'document',
549
+ document: { url: 'https://example.com/doc.pdf' },
550
+ },
551
+ ],
552
+ },
553
+ };
554
+
555
+ const result = formatMessage(input);
556
+
557
+ expect(result).toMatchObject({
558
+ role: 'assistant',
559
+ content: 'Here is a response',
560
+ });
561
+ });
562
+
563
+ it('handles empty media arrays gracefully', () => {
564
+ const input = {
565
+ message: {
566
+ role: 'user',
567
+ content: 'Just text',
568
+ documents: [],
569
+ videos: [],
570
+ audios: [],
571
+ image_urls: [],
572
+ },
573
+ };
574
+
575
+ const result = formatMessage(input);
576
+
577
+ expect(result).toMatchObject({
578
+ role: 'user',
579
+ content: 'Just text',
580
+ });
581
+ });
582
+
583
+ it('formats media with langChain flag', () => {
584
+ const input = {
585
+ message: {
586
+ role: 'user',
587
+ content: 'Check this image',
588
+ image_urls: [
589
+ {
590
+ type: 'image_url' as const,
591
+ image_url: { url: 'https://example.com/image.jpg' },
592
+ },
593
+ ],
594
+ },
595
+ langChain: true,
596
+ };
597
+
598
+ const result = formatMessage(input);
599
+
600
+ expect(result).toBeInstanceOf(HumanMessage);
601
+ expect(Array.isArray(result.lc_kwargs.content)).toBe(true);
602
+ expect(result.lc_kwargs.content).toHaveLength(2);
603
+ });
604
+ });
605
+
190
606
  describe('formatLangChainMessages', () => {
191
607
  it('formats an array of messages for LangChain', () => {
192
608
  const messages = [
@@ -389,6 +389,14 @@ export function checkValidNumber(value: unknown): value is number {
389
389
  return typeof value === 'number' && !isNaN(value) && value > 0;
390
390
  }
391
391
 
392
+ type ThinkingBlocks = {
393
+ thinking_blocks?: Array<{
394
+ type: 'thinking';
395
+ thinking: string;
396
+ signature: string;
397
+ }>;
398
+ };
399
+
392
400
  export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
393
401
  const indexTokenCountMap = { ...factoryParams.indexTokenCountMap };
394
402
  let lastTurnStartIndex = factoryParams.startIndex;
@@ -402,6 +410,49 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
402
410
  context: BaseMessage[];
403
411
  indexTokenCountMap: Record<string, number | undefined>;
404
412
  } {
413
+ if (
414
+ factoryParams.provider === Providers.OPENAI &&
415
+ factoryParams.thinkingEnabled === true
416
+ ) {
417
+ for (let i = lastTurnStartIndex; i < params.messages.length; i++) {
418
+ const m = params.messages[i];
419
+ if (
420
+ m.getType() === 'ai' &&
421
+ typeof m.additional_kwargs.reasoning_content === 'string' &&
422
+ Array.isArray(
423
+ (
424
+ m.additional_kwargs.provider_specific_fields as
425
+ | ThinkingBlocks
426
+ | undefined
427
+ )?.thinking_blocks
428
+ ) &&
429
+ (m as AIMessage).tool_calls &&
430
+ ((m as AIMessage).tool_calls?.length ?? 0) > 0
431
+ ) {
432
+ const message = m as AIMessage;
433
+ const thinkingBlocks = (
434
+ message.additional_kwargs.provider_specific_fields as ThinkingBlocks
435
+ ).thinking_blocks;
436
+ const signature =
437
+ thinkingBlocks?.[thinkingBlocks.length - 1].signature;
438
+ const thinkingBlock: ThinkingContentText = {
439
+ signature,
440
+ type: ContentTypes.THINKING,
441
+ thinking: message.additional_kwargs.reasoning_content as string,
442
+ };
443
+
444
+ params.messages[i] = new AIMessage({
445
+ ...message,
446
+ content: [thinkingBlock],
447
+ additional_kwargs: {
448
+ ...message.additional_kwargs,
449
+ reasoning_content: undefined,
450
+ },
451
+ });
452
+ }
453
+ }
454
+ }
455
+
405
456
  let currentUsage: UsageMetadata | undefined;
406
457
  if (
407
458
  params.usageMetadata &&
package/src/run.ts CHANGED
@@ -34,7 +34,7 @@ export const defaultOmitOptions = new Set([
34
34
  export class Run<_T extends t.BaseGraphState> {
35
35
  id: string;
36
36
  private tokenCounter?: t.TokenCounter;
37
- private handlerRegistry: HandlerRegistry;
37
+ private handlerRegistry?: HandlerRegistry;
38
38
  private indexTokenCountMap?: Record<string, number>;
39
39
  graphRunnable?: t.CompiledStateWorkflow;
40
40
  Graph: StandardGraph | MultiAgentGraph | undefined;
@@ -73,7 +73,7 @@ export class Run<_T extends t.BaseGraphState> {
73
73
  this.Graph.handlerRegistry = handlerRegistry;
74
74
  }
75
75
  } else {
76
- // Default to legacy graph for 'standard' or undefined type
76
+ /** Default to legacy graph for 'standard' or undefined type */
77
77
  this.graphRunnable = this.createLegacyGraph(config.graphConfig);
78
78
  if (this.Graph) {
79
79
  this.Graph.compileOptions =
@@ -86,25 +86,38 @@ export class Run<_T extends t.BaseGraphState> {
86
86
  }
87
87
 
88
88
  private createLegacyGraph(
89
- config: t.LegacyGraphConfig
89
+ config: t.LegacyGraphConfig | t.StandardGraphConfig
90
90
  ): t.CompiledStateWorkflow {
91
- const {
92
- type: _type,
93
- llmConfig,
94
- signal,
95
- tools = [],
96
- ...agentInputs
97
- } = config;
98
- const { provider, ...clientOptions } = llmConfig;
99
-
100
- /** TEMP: Create agent configuration for the single agent */
101
- const agentConfig: t.AgentInputs = {
102
- ...agentInputs,
103
- tools,
104
- provider,
105
- clientOptions,
106
- agentId: 'default',
107
- };
91
+ let agentConfig: t.AgentInputs;
92
+ let signal: AbortSignal | undefined;
93
+
94
+ /** Check if this is a multi-agent style config (has agents array) */
95
+ if ('agents' in config && Array.isArray(config.agents)) {
96
+ if (config.agents.length === 0) {
97
+ throw new Error('At least one agent must be provided');
98
+ }
99
+ agentConfig = config.agents[0];
100
+ signal = config.signal;
101
+ } else {
102
+ /** Legacy path: build agent config from llmConfig */
103
+ const {
104
+ type: _type,
105
+ llmConfig,
106
+ signal: legacySignal,
107
+ tools = [],
108
+ ...agentInputs
109
+ } = config as t.LegacyGraphConfig;
110
+ const { provider, ...clientOptions } = llmConfig;
111
+
112
+ agentConfig = {
113
+ ...agentInputs,
114
+ tools,
115
+ provider,
116
+ clientOptions,
117
+ agentId: 'default',
118
+ };
119
+ signal = legacySignal;
120
+ }
108
121
 
109
122
  const standardGraph = new StandardGraph({
110
123
  signal,
@@ -113,10 +126,8 @@ export class Run<_T extends t.BaseGraphState> {
113
126
  tokenCounter: this.tokenCounter,
114
127
  indexTokenCountMap: this.indexTokenCountMap,
115
128
  });
116
- // propagate compile options from graph config
117
- standardGraph.compileOptions = (
118
- config as t.LegacyGraphConfig
119
- ).compileOptions;
129
+ /** Propagate compile options from graph config */
130
+ standardGraph.compileOptions = config.compileOptions;
120
131
  this.Graph = standardGraph;
121
132
  return standardGraph.createWorkflow();
122
133
  }
@@ -145,7 +156,7 @@ export class Run<_T extends t.BaseGraphState> {
145
156
  static async create<T extends t.BaseGraphState>(
146
157
  config: t.RunConfig
147
158
  ): Promise<Run<T>> {
148
- // Create tokenCounter if indexTokenCountMap is provided but tokenCounter is not
159
+ /** Create tokenCounter if indexTokenCountMap is provided but tokenCounter is not */
149
160
  if (config.indexTokenCountMap && !config.tokenCounter) {
150
161
  config.tokenCounter = await createTokenCounter();
151
162
  }
@@ -179,7 +190,7 @@ export class Run<_T extends t.BaseGraphState> {
179
190
  ) {
180
191
  return;
181
192
  }
182
- const handler = this.handlerRegistry.getHandler(eventName);
193
+ const handler = this.handlerRegistry?.getHandler(eventName);
183
194
  if (handler && this.Graph) {
184
195
  await handler.handle(
185
196
  eventName,
@@ -251,7 +262,7 @@ export class Run<_T extends t.BaseGraphState> {
251
262
  continue;
252
263
  }
253
264
 
254
- const handler = this.handlerRegistry.getHandler(eventName);
265
+ const handler = this.handlerRegistry?.getHandler(eventName);
255
266
  if (handler) {
256
267
  await handler.handle(eventName, data, metadata, this.Graph);
257
268
  }