@librechat/agents 2.3.93 → 2.3.95

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.
@@ -1,4 +1,9 @@
1
- import { HumanMessage, AIMessage, SystemMessage, ToolMessage } from '@langchain/core/messages';
1
+ import {
2
+ HumanMessage,
3
+ AIMessage,
4
+ SystemMessage,
5
+ ToolMessage,
6
+ } from '@langchain/core/messages';
2
7
  import type { TPayload } from '@/types';
3
8
  import { formatAgentMessages } from './format';
4
9
  import { ContentTypes } from '@/common';
@@ -16,7 +21,9 @@ describe('formatAgentMessages', () => {
16
21
  });
17
22
 
18
23
  it('should handle system messages', () => {
19
- const payload = [{ role: 'system', content: 'You are a helpful assistant.' }];
24
+ const payload = [
25
+ { role: 'system', content: 'You are a helpful assistant.' },
26
+ ];
20
27
  const result = formatAgentMessages(payload);
21
28
  expect(result.messages).toHaveLength(1);
22
29
  expect(result.messages[0]).toBeInstanceOf(SystemMessage);
@@ -80,7 +87,7 @@ describe('formatAgentMessages', () => {
80
87
  expect(result.messages[0].content).toHaveLength(2);
81
88
  });
82
89
 
83
- it('should throw an error for invalid tool call structure', () => {
90
+ it('should heal invalid tool call structure by creating a preceding AIMessage', () => {
84
91
  const payload = [
85
92
  {
86
93
  role: 'assistant',
@@ -97,7 +104,26 @@ describe('formatAgentMessages', () => {
97
104
  ],
98
105
  },
99
106
  ];
100
- expect(() => formatAgentMessages(payload)).toThrow('Invalid tool call structure');
107
+ const result = formatAgentMessages(payload);
108
+
109
+ // Should have 2 messages: an AIMessage and a ToolMessage
110
+ expect(result.messages).toHaveLength(2);
111
+ expect(result.messages[0]).toBeInstanceOf(AIMessage);
112
+ expect(result.messages[1]).toBeInstanceOf(ToolMessage);
113
+
114
+ // The AIMessage should have an empty content and the tool_call
115
+ expect(result.messages[0].content).toBe('');
116
+ expect((result.messages[0] as AIMessage).tool_calls).toHaveLength(1);
117
+ expect((result.messages[0] as AIMessage).tool_calls?.[0]).toEqual({
118
+ id: '123',
119
+ name: 'search',
120
+ args: { query: 'weather' },
121
+ });
122
+
123
+ // The ToolMessage should have the correct properties
124
+ expect((result.messages[1] as ToolMessage).tool_call_id).toBe('123');
125
+ expect(result.messages[1].name).toBe('search');
126
+ expect(result.messages[1].content).toBe('The weather is sunny.');
101
127
  });
102
128
 
103
129
  it('should handle tool calls with non-JSON args', () => {
@@ -105,7 +131,11 @@ describe('formatAgentMessages', () => {
105
131
  {
106
132
  role: 'assistant',
107
133
  content: [
108
- { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Checking...', tool_call_ids: ['123'] },
134
+ {
135
+ type: ContentTypes.TEXT,
136
+ [ContentTypes.TEXT]: 'Checking...',
137
+ tool_call_ids: ['123'],
138
+ },
109
139
  {
110
140
  type: ContentTypes.TOOL_CALL,
111
141
  tool_call: {
@@ -120,7 +150,9 @@ describe('formatAgentMessages', () => {
120
150
  ];
121
151
  const result = formatAgentMessages(payload);
122
152
  expect(result.messages).toHaveLength(2);
123
- expect((result.messages[0] as AIMessage).tool_calls?.[0].args).toStrictEqual({ input: 'non-json-string' });
153
+ expect(
154
+ (result.messages[0] as AIMessage).tool_calls?.[0].args
155
+ ).toStrictEqual({ input: 'non-json-string' });
124
156
  });
125
157
 
126
158
  it('should handle complex tool calls with multiple steps', () => {
@@ -139,7 +171,8 @@ describe('formatAgentMessages', () => {
139
171
  id: 'search_1',
140
172
  name: 'search',
141
173
  args: '{"query":"weather in New York"}',
142
- output: 'The weather in New York is currently sunny with a temperature of 75°F.',
174
+ output:
175
+ 'The weather in New York is currently sunny with a temperature of 75°F.',
143
176
  },
144
177
  },
145
178
  {
@@ -156,7 +189,10 @@ describe('formatAgentMessages', () => {
156
189
  output: '23.89°C',
157
190
  },
158
191
  },
159
- { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Here\'s your answer.' },
192
+ {
193
+ type: ContentTypes.TEXT,
194
+ [ContentTypes.TEXT]: 'Here\'s your answer.',
195
+ },
160
196
  ],
161
197
  },
162
198
  ];
@@ -171,7 +207,9 @@ describe('formatAgentMessages', () => {
171
207
  expect(result.messages[4]).toBeInstanceOf(AIMessage);
172
208
 
173
209
  // Check first AIMessage
174
- expect(result.messages[0].content).toBe('I\'ll search for that information.');
210
+ expect(result.messages[0].content).toBe(
211
+ 'I\'ll search for that information.'
212
+ );
175
213
  expect((result.messages[0] as AIMessage).tool_calls).toHaveLength(1);
176
214
  expect((result.messages[0] as AIMessage).tool_calls?.[0]).toEqual({
177
215
  id: 'search_1',
@@ -183,11 +221,13 @@ describe('formatAgentMessages', () => {
183
221
  expect((result.messages[1] as ToolMessage).tool_call_id).toBe('search_1');
184
222
  expect(result.messages[1].name).toBe('search');
185
223
  expect(result.messages[1].content).toBe(
186
- 'The weather in New York is currently sunny with a temperature of 75°F.',
224
+ 'The weather in New York is currently sunny with a temperature of 75°F.'
187
225
  );
188
226
 
189
227
  // Check second AIMessage
190
- expect(result.messages[2].content).toBe('Now, I\'ll convert the temperature.');
228
+ expect(result.messages[2].content).toBe(
229
+ 'Now, I\'ll convert the temperature.'
230
+ );
191
231
  expect((result.messages[2] as AIMessage).tool_calls).toHaveLength(1);
192
232
  expect((result.messages[2] as AIMessage).tool_calls?.[0]).toEqual({
193
233
  id: 'convert_1',
@@ -211,11 +251,18 @@ describe('formatAgentMessages', () => {
211
251
  { role: 'user', content: 'Hello' },
212
252
  {
213
253
  role: 'assistant',
214
- content: [{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Hi there!' }],
254
+ content: [
255
+ { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Hi there!' },
256
+ ],
215
257
  },
216
258
  {
217
259
  role: 'assistant',
218
- content: [{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'How can I help you?' }],
260
+ content: [
261
+ {
262
+ type: ContentTypes.TEXT,
263
+ [ContentTypes.TEXT]: 'How can I help you?',
264
+ },
265
+ ],
219
266
  },
220
267
  { role: 'user', content: 'What\'s the weather?' },
221
268
  {
@@ -240,7 +287,10 @@ describe('formatAgentMessages', () => {
240
287
  {
241
288
  role: 'assistant',
242
289
  content: [
243
- { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Here\'s the weather information.' },
290
+ {
291
+ type: ContentTypes.TEXT,
292
+ [ContentTypes.TEXT]: 'Here\'s the weather information.',
293
+ },
244
294
  ],
245
295
  },
246
296
  ];
@@ -270,13 +320,18 @@ describe('formatAgentMessages', () => {
270
320
  expect(result.messages[3].content).toBe('Let me check that for you.');
271
321
  expect(result.messages[4].content).toBe('Sunny, 75°F');
272
322
  expect(result.messages[5].content).toStrictEqual([
273
- { [ContentTypes.TEXT]: 'Here\'s the weather information.', type: ContentTypes.TEXT },
323
+ {
324
+ [ContentTypes.TEXT]: 'Here\'s the weather information.',
325
+ type: ContentTypes.TEXT,
326
+ },
274
327
  ]);
275
328
 
276
329
  // Check that there are no consecutive AIMessages
277
330
  const messageTypes = result.messages.map((message) => message.constructor);
278
331
  for (let i = 0; i < messageTypes.length - 1; i++) {
279
- expect(messageTypes[i] === AIMessage && messageTypes[i + 1] === AIMessage).toBe(false);
332
+ expect(
333
+ messageTypes[i] === AIMessage && messageTypes[i + 1] === AIMessage
334
+ ).toBe(false);
280
335
  }
281
336
 
282
337
  // Additional check to ensure the consecutive assistant messages were combined
@@ -289,7 +344,10 @@ describe('formatAgentMessages', () => {
289
344
  role: 'assistant',
290
345
  content: [
291
346
  { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Initial response' },
292
- { type: ContentTypes.THINK, [ContentTypes.THINK]: 'Reasoning about the problem...' },
347
+ {
348
+ type: ContentTypes.THINK,
349
+ [ContentTypes.THINK]: 'Reasoning about the problem...',
350
+ },
293
351
  { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Final answer' },
294
352
  ],
295
353
  },
@@ -299,7 +357,9 @@ describe('formatAgentMessages', () => {
299
357
 
300
358
  expect(result.messages).toHaveLength(1);
301
359
  expect(result.messages[0]).toBeInstanceOf(AIMessage);
302
- expect(result.messages[0].content).toEqual('Initial response\nFinal answer');
360
+ expect(result.messages[0].content).toEqual(
361
+ 'Initial response\nFinal answer'
362
+ );
303
363
  });
304
364
 
305
365
  it('should join TEXT content as string when THINK content type is present', () => {
@@ -307,10 +367,22 @@ describe('formatAgentMessages', () => {
307
367
  {
308
368
  role: 'assistant',
309
369
  content: [
310
- { type: ContentTypes.THINK, [ContentTypes.THINK]: 'Analyzing the problem...' },
311
- { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'First part of response' },
312
- { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Second part of response' },
313
- { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Final part of response' },
370
+ {
371
+ type: ContentTypes.THINK,
372
+ [ContentTypes.THINK]: 'Analyzing the problem...',
373
+ },
374
+ {
375
+ type: ContentTypes.TEXT,
376
+ [ContentTypes.TEXT]: 'First part of response',
377
+ },
378
+ {
379
+ type: ContentTypes.TEXT,
380
+ [ContentTypes.TEXT]: 'Second part of response',
381
+ },
382
+ {
383
+ type: ContentTypes.TEXT,
384
+ [ContentTypes.TEXT]: 'Final part of response',
385
+ },
314
386
  ],
315
387
  },
316
388
  ];
@@ -321,9 +393,11 @@ describe('formatAgentMessages', () => {
321
393
  expect(result.messages[0]).toBeInstanceOf(AIMessage);
322
394
  expect(typeof result.messages[0].content).toBe('string');
323
395
  expect(result.messages[0].content).toBe(
324
- 'First part of response\nSecond part of response\nFinal part of response',
396
+ 'First part of response\nSecond part of response\nFinal part of response'
397
+ );
398
+ expect(result.messages[0].content).not.toContain(
399
+ 'Analyzing the problem...'
325
400
  );
326
- expect(result.messages[0].content).not.toContain('Analyzing the problem...');
327
401
  });
328
402
 
329
403
  it('should exclude ERROR type content parts', () => {
@@ -351,10 +425,13 @@ describe('formatAgentMessages', () => {
351
425
  { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Final answer' },
352
426
  ]);
353
427
 
354
- const hasErrorContent = Array.isArray(result.messages[0].content) && result.messages[0].content.some(
355
- (item) =>
356
- item.type === ContentTypes.ERROR || JSON.stringify(item).includes('An error occurred'),
357
- );
428
+ const hasErrorContent =
429
+ Array.isArray(result.messages[0].content) &&
430
+ result.messages[0].content.some(
431
+ (item) =>
432
+ item.type === ContentTypes.ERROR ||
433
+ JSON.stringify(item).includes('An error occurred')
434
+ );
358
435
  expect(hasErrorContent).toBe(false);
359
436
  });
360
437
  it('should handle indexTokenCountMap and return updated map', () => {
@@ -364,7 +441,7 @@ describe('formatAgentMessages', () => {
364
441
  ];
365
442
 
366
443
  const indexTokenCountMap = {
367
- 0: 5, // 5 tokens for "Hello"
444
+ 0: 5, // 5 tokens for "Hello"
368
445
  1: 10, // 10 tokens for "Hi there!"
369
446
  };
370
447
 
@@ -401,8 +478,8 @@ describe('formatAgentMessages', () => {
401
478
  ];
402
479
 
403
480
  const indexTokenCountMap = {
404
- 0: 10, // 10 tokens for "What's the weather?"
405
- 1: 50, // 50 tokens for the assistant message with tool call
481
+ 0: 10, // 10 tokens for "What's the weather?"
482
+ 1: 50, // 50 tokens for the assistant message with tool call
406
483
  };
407
484
 
408
485
  const result = formatAgentMessages(payload, indexTokenCountMap);
@@ -410,11 +487,14 @@ describe('formatAgentMessages', () => {
410
487
  // The original message at index 1 should be split into two messages
411
488
  expect(result.messages).toHaveLength(3);
412
489
  expect(result.indexTokenCountMap).toBeDefined();
413
- expect(result.indexTokenCountMap?.[0]).toBe(10); // User message stays the same
490
+ expect(result.indexTokenCountMap?.[0]).toBe(10); // User message stays the same
414
491
 
415
492
  // The assistant message tokens should be distributed across the resulting messages
416
- const totalAssistantTokens = Object.values(result.indexTokenCountMap || {})
417
- .reduce((sum, count) => sum + count, 0) - 10; // Subtract user message tokens
493
+ const totalAssistantTokens =
494
+ Object.values(result.indexTokenCountMap || {}).reduce(
495
+ (sum, count) => sum + count,
496
+ 0
497
+ ) - 10; // Subtract user message tokens
418
498
 
419
499
  expect(totalAssistantTokens).toBe(50); // Should match the original token count
420
500
  });
@@ -462,7 +542,7 @@ describe('formatAgentMessages', () => {
462
542
  ];
463
543
 
464
544
  const indexTokenCountMap = {
465
- 0: 100, // 100 tokens for the complex assistant message
545
+ 0: 100, // 100 tokens for the complex assistant message
466
546
  };
467
547
 
468
548
  const result = formatAgentMessages(payload, indexTokenCountMap);
@@ -472,8 +552,10 @@ describe('formatAgentMessages', () => {
472
552
  expect(result.indexTokenCountMap).toBeDefined();
473
553
 
474
554
  // The sum of all token counts should equal the original
475
- const totalTokens = Object.values(result.indexTokenCountMap || {})
476
- .reduce((sum, count) => sum + count, 0);
555
+ const totalTokens = Object.values(result.indexTokenCountMap || {}).reduce(
556
+ (sum, count) => sum + count,
557
+ 0
558
+ );
477
559
 
478
560
  expect(totalTokens).toBe(100);
479
561
 
@@ -497,7 +579,7 @@ describe('formatAgentMessages', () => {
497
579
  ];
498
580
 
499
581
  const indexTokenCountMap = {
500
- 0: 60, // 60 tokens for the message with filtered content
582
+ 0: 60, // 60 tokens for the message with filtered content
501
583
  };
502
584
 
503
585
  const result = formatAgentMessages(payload, indexTokenCountMap);
@@ -524,7 +606,7 @@ describe('formatAgentMessages', () => {
524
606
  ];
525
607
 
526
608
  const indexTokenCountMap = {
527
- 0: 40, // 40 tokens for the message with filtered content
609
+ 0: 40, // 40 tokens for the message with filtered content
528
610
  };
529
611
 
530
612
  const result = formatAgentMessages(payload, indexTokenCountMap);
@@ -563,8 +645,8 @@ describe('formatAgentMessages', () => {
563
645
  ];
564
646
 
565
647
  const indexTokenCountMap = {
566
- 0: 15, // 15 tokens for the user message
567
- 1: 45, // 45 tokens for the assistant message with tool call
648
+ 0: 15, // 15 tokens for the user message
649
+ 1: 45, // 45 tokens for the assistant message with tool call
568
650
  };
569
651
 
570
652
  const result = formatAgentMessages(payload, indexTokenCountMap);
@@ -581,12 +663,210 @@ describe('formatAgentMessages', () => {
581
663
  expect(result.messages[2]).toBeInstanceOf(ToolMessage);
582
664
 
583
665
  // The sum of all token counts should equal the original total
584
- const totalTokens = Object.values(result.indexTokenCountMap || {})
585
- .reduce((sum, count) => sum + count, 0);
666
+ const totalTokens = Object.values(result.indexTokenCountMap || {}).reduce(
667
+ (sum, count) => sum + count,
668
+ 0
669
+ );
586
670
 
587
671
  expect(totalTokens).toBe(60); // 15 + 45
588
672
  });
589
673
 
674
+ it('should handle an AI message with 5 tool calls in a single message', () => {
675
+ const payload = [
676
+ {
677
+ role: 'assistant',
678
+ content: [
679
+ {
680
+ type: ContentTypes.TEXT,
681
+ [ContentTypes.TEXT]: 'I\'ll perform multiple operations for you.',
682
+ tool_call_ids: ['tool_1', 'tool_2', 'tool_3', 'tool_4', 'tool_5'],
683
+ },
684
+ {
685
+ type: ContentTypes.TOOL_CALL,
686
+ tool_call: {
687
+ id: 'tool_1',
688
+ name: 'search',
689
+ args: '{"query":"latest news"}',
690
+ output: 'Found several news articles.',
691
+ },
692
+ },
693
+ {
694
+ type: ContentTypes.TOOL_CALL,
695
+ tool_call: {
696
+ id: 'tool_2',
697
+ name: 'check_weather',
698
+ args: '{"location":"New York"}',
699
+ output: 'Sunny, 75°F',
700
+ },
701
+ },
702
+ {
703
+ type: ContentTypes.TOOL_CALL,
704
+ tool_call: {
705
+ id: 'tool_3',
706
+ name: 'calculate',
707
+ args: '{"expression":"356 * 24"}',
708
+ output: '8544',
709
+ },
710
+ },
711
+ {
712
+ type: ContentTypes.TOOL_CALL,
713
+ tool_call: {
714
+ id: 'tool_4',
715
+ name: 'translate',
716
+ args: '{"text":"Hello world","source":"en","target":"fr"}',
717
+ output: 'Bonjour le monde',
718
+ },
719
+ },
720
+ {
721
+ type: ContentTypes.TOOL_CALL,
722
+ tool_call: {
723
+ id: 'tool_5',
724
+ name: 'fetch_data',
725
+ args: '{"endpoint":"/api/users","params":{"limit":5}}',
726
+ output:
727
+ '{"users":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"},{"id":3,"name":"Charlie"},{"id":4,"name":"David"},{"id":5,"name":"Eve"}]}',
728
+ },
729
+ },
730
+ ],
731
+ },
732
+ ];
733
+
734
+ const result = formatAgentMessages(payload);
735
+
736
+ // Should have 6 messages: 1 AIMessage and 5 ToolMessages
737
+ expect(result.messages).toHaveLength(6);
738
+
739
+ // Check message types in the correct sequence
740
+ expect(result.messages[0]).toBeInstanceOf(AIMessage); // Initial message with all tool calls
741
+ expect(result.messages[1]).toBeInstanceOf(ToolMessage); // Tool 1 response
742
+ expect(result.messages[2]).toBeInstanceOf(ToolMessage); // Tool 2 response
743
+ expect(result.messages[3]).toBeInstanceOf(ToolMessage); // Tool 3 response
744
+ expect(result.messages[4]).toBeInstanceOf(ToolMessage); // Tool 4 response
745
+ expect(result.messages[5]).toBeInstanceOf(ToolMessage); // Tool 5 response
746
+
747
+ // Check AIMessage has all 5 tool calls
748
+ expect(result.messages[0].content).toBe(
749
+ 'I\'ll perform multiple operations for you.'
750
+ );
751
+ expect((result.messages[0] as AIMessage).tool_calls).toHaveLength(5);
752
+
753
+ // Verify each tool call in the AIMessage
754
+ expect((result.messages[0] as AIMessage).tool_calls?.[0]).toEqual({
755
+ id: 'tool_1',
756
+ name: 'search',
757
+ args: { query: 'latest news' },
758
+ });
759
+
760
+ expect((result.messages[0] as AIMessage).tool_calls?.[1]).toEqual({
761
+ id: 'tool_2',
762
+ name: 'check_weather',
763
+ args: { location: 'New York' },
764
+ });
765
+
766
+ expect((result.messages[0] as AIMessage).tool_calls?.[2]).toEqual({
767
+ id: 'tool_3',
768
+ name: 'calculate',
769
+ args: { expression: '356 * 24' },
770
+ });
771
+
772
+ expect((result.messages[0] as AIMessage).tool_calls?.[3]).toEqual({
773
+ id: 'tool_4',
774
+ name: 'translate',
775
+ args: { text: 'Hello world', source: 'en', target: 'fr' },
776
+ });
777
+
778
+ expect((result.messages[0] as AIMessage).tool_calls?.[4]).toEqual({
779
+ id: 'tool_5',
780
+ name: 'fetch_data',
781
+ args: { endpoint: '/api/users', params: { limit: 5 } },
782
+ });
783
+
784
+ // Check each ToolMessage
785
+ expect((result.messages[1] as ToolMessage).tool_call_id).toBe('tool_1');
786
+ expect(result.messages[1].name).toBe('search');
787
+ expect(result.messages[1].content).toBe('Found several news articles.');
788
+
789
+ expect((result.messages[2] as ToolMessage).tool_call_id).toBe('tool_2');
790
+ expect(result.messages[2].name).toBe('check_weather');
791
+ expect(result.messages[2].content).toBe('Sunny, 75°F');
792
+
793
+ expect((result.messages[3] as ToolMessage).tool_call_id).toBe('tool_3');
794
+ expect(result.messages[3].name).toBe('calculate');
795
+ expect(result.messages[3].content).toBe('8544');
796
+
797
+ expect((result.messages[4] as ToolMessage).tool_call_id).toBe('tool_4');
798
+ expect(result.messages[4].name).toBe('translate');
799
+ expect(result.messages[4].content).toBe('Bonjour le monde');
800
+
801
+ expect((result.messages[5] as ToolMessage).tool_call_id).toBe('tool_5');
802
+ expect(result.messages[5].name).toBe('fetch_data');
803
+ expect(result.messages[5].content).toBe(
804
+ '{"users":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"},{"id":3,"name":"Charlie"},{"id":4,"name":"David"},{"id":5,"name":"Eve"}]}'
805
+ );
806
+ });
807
+
808
+ it('should heal tool call structure with thinking content', () => {
809
+ const payload = [
810
+ {
811
+ role: 'assistant',
812
+ content: [
813
+ {
814
+ type: ContentTypes.THINK,
815
+ [ContentTypes.THINK]:
816
+ 'I\'ll add this agreement as an observation to our existing troubleshooting task in the project memory system.',
817
+ },
818
+ {
819
+ type: ContentTypes.TOOL_CALL,
820
+ tool_call: {
821
+ id: 'tooluse_Zz-mw_wHTrWTvDHaCbfaZg',
822
+ name: 'add_observations_mcp_project-memory',
823
+ args: '{"observations":[{"entityName":"MCP_Tool_Error_Troubleshooting","contents":["Agreement established: Document all future tests in the project memory system to maintain a comprehensive troubleshooting log","This will provide a structured record of the entire troubleshooting process and help identify patterns in the error behavior"]}]}',
824
+ type: 'tool_call',
825
+ progress: 1,
826
+ output:
827
+ '[\n {\n "entityName": "MCP_Tool_Error_Troubleshooting",\n "addedObservations": [\n {\n "content": "Agreement established: Document all future tests in the project memory system to maintain a comprehensive troubleshooting log",\n "timestamp": "2025-03-26T00:46:42.154Z"\n },\n {\n "content": "This will provide a structured record of the entire troubleshooting process and help identify patterns in the error behavior",\n "timestamp": "2025-03-26T00:46:42.154Z"\n }\n ]\n }\n]',
828
+ },
829
+ },
830
+ {
831
+ type: ContentTypes.TEXT,
832
+ [ContentTypes.TEXT]:
833
+ '\n\nI\'ve successfully added our agreement to the project memory system. The observation has been recorded in the "MCP_Tool_Error_Troubleshooting" entity with the current timestamp.\n\nGoing forward, I will:\n\n1. Document each test we perform\n2. Record the methodology and results\n3. Update the project memory with our findings\n4. Establish appropriate relationships between tests and related components\n5. Provide a summary of what we\'ve learned from each test\n\nThis structured approach will help us build a comprehensive knowledge base of the error behavior and our troubleshooting process, which may prove valuable for resolving similar issues in the future or for other developers facing similar challenges.\n\nWhat test would you like to perform next in our troubleshooting process?',
834
+ },
835
+ ],
836
+ },
837
+ ];
838
+
839
+ const result = formatAgentMessages(payload);
840
+
841
+ // Should have 3 messages: an AIMessage with empty content, a ToolMessage, and a final AIMessage with the text
842
+ expect(result.messages).toHaveLength(3);
843
+ expect(result.messages[0]).toBeInstanceOf(AIMessage);
844
+ expect(result.messages[1]).toBeInstanceOf(ToolMessage);
845
+ expect(result.messages[2]).toBeInstanceOf(AIMessage);
846
+
847
+ // The first AIMessage should have an empty content and the tool_call
848
+ expect(result.messages[0].content).toBe('');
849
+ expect((result.messages[0] as AIMessage).tool_calls).toHaveLength(1);
850
+ expect((result.messages[0] as AIMessage).tool_calls?.[0].name).toBe(
851
+ 'add_observations_mcp_project-memory'
852
+ );
853
+
854
+ // The ToolMessage should have the correct properties
855
+ expect((result.messages[1] as ToolMessage).tool_call_id).toBe(
856
+ 'tooluse_Zz-mw_wHTrWTvDHaCbfaZg'
857
+ );
858
+ expect(result.messages[1].name).toBe('add_observations_mcp_project-memory');
859
+ expect(result.messages[1].content).toContain(
860
+ 'MCP_Tool_Error_Troubleshooting'
861
+ );
862
+
863
+ // The final AIMessage should contain the text response
864
+ expect(typeof result.messages[2].content).toBe('string');
865
+ expect((result.messages[2].content as string).trim()).toContain(
866
+ 'I\'ve successfully added our agreement to the project memory system'
867
+ );
868
+ });
869
+
590
870
  it('should demonstrate how messages can be filtered out, reducing count', () => {
591
871
  // Two input messages where one gets completely filtered out
592
872
  const payload = [
@@ -594,16 +874,22 @@ describe('formatAgentMessages', () => {
594
874
  {
595
875
  role: 'assistant',
596
876
  content: [
597
- { type: ContentTypes.THINK, [ContentTypes.THINK]: 'Thinking about response...' },
598
- { type: ContentTypes.ERROR, [ContentTypes.ERROR]: 'Error in processing' },
877
+ {
878
+ type: ContentTypes.THINK,
879
+ [ContentTypes.THINK]: 'Thinking about response...',
880
+ },
881
+ {
882
+ type: ContentTypes.ERROR,
883
+ [ContentTypes.ERROR]: 'Error in processing',
884
+ },
599
885
  { type: ContentTypes.AGENT_UPDATE, update: 'Working on it...' },
600
886
  ],
601
887
  },
602
888
  ];
603
889
 
604
890
  const indexTokenCountMap = {
605
- 0: 10, // 10 tokens for the user message
606
- 1: 30, // 30 tokens for the assistant message that will be filtered out
891
+ 0: 10, // 10 tokens for the user message
892
+ 1: 30, // 30 tokens for the assistant message that will be filtered out
607
893
  };
608
894
 
609
895
  const result = formatAgentMessages(payload, indexTokenCountMap);
@@ -621,8 +907,10 @@ describe('formatAgentMessages', () => {
621
907
  expect(result.indexTokenCountMap?.[0]).toBe(10);
622
908
 
623
909
  // The total tokens should be just the user message tokens
624
- const totalTokens = Object.values(result.indexTokenCountMap || {})
625
- .reduce((sum, count) => sum + count, 0);
910
+ const totalTokens = Object.values(result.indexTokenCountMap || {}).reduce(
911
+ (sum, count) => sum + count,
912
+ 0
913
+ );
626
914
 
627
915
  expect(totalTokens).toBe(10);
628
916
  });