@librechat/agents 2.3.1 → 2.3.2

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 (77) hide show
  1. package/dist/cjs/graphs/Graph.cjs +6 -6
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/llm/anthropic/llm.cjs +7 -7
  4. package/dist/cjs/llm/anthropic/llm.cjs.map +1 -1
  5. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +6 -6
  6. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  7. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +24 -24
  8. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  9. package/dist/cjs/llm/fake.cjs.map +1 -1
  10. package/dist/cjs/llm/text.cjs.map +1 -1
  11. package/dist/cjs/main.cjs +3 -0
  12. package/dist/cjs/main.cjs.map +1 -1
  13. package/dist/cjs/messages/core.cjs +5 -5
  14. package/dist/cjs/messages/core.cjs.map +1 -1
  15. package/dist/cjs/messages/format.cjs +11 -9
  16. package/dist/cjs/messages/format.cjs.map +1 -1
  17. package/dist/cjs/messages/prune.cjs +155 -205
  18. package/dist/cjs/messages/prune.cjs.map +1 -1
  19. package/dist/cjs/run.cjs.map +1 -1
  20. package/dist/cjs/stream.cjs +3 -4
  21. package/dist/cjs/stream.cjs.map +1 -1
  22. package/dist/cjs/tools/ToolNode.cjs +1 -1
  23. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  24. package/dist/cjs/utils/tokens.cjs +3 -3
  25. package/dist/cjs/utils/tokens.cjs.map +1 -1
  26. package/dist/esm/graphs/Graph.mjs +6 -6
  27. package/dist/esm/graphs/Graph.mjs.map +1 -1
  28. package/dist/esm/llm/anthropic/llm.mjs +7 -7
  29. package/dist/esm/llm/anthropic/llm.mjs.map +1 -1
  30. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +6 -6
  31. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  32. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +24 -24
  33. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  34. package/dist/esm/llm/fake.mjs.map +1 -1
  35. package/dist/esm/llm/text.mjs.map +1 -1
  36. package/dist/esm/main.mjs +1 -1
  37. package/dist/esm/messages/core.mjs +5 -5
  38. package/dist/esm/messages/core.mjs.map +1 -1
  39. package/dist/esm/messages/format.mjs +11 -9
  40. package/dist/esm/messages/format.mjs.map +1 -1
  41. package/dist/esm/messages/prune.mjs +153 -206
  42. package/dist/esm/messages/prune.mjs.map +1 -1
  43. package/dist/esm/run.mjs.map +1 -1
  44. package/dist/esm/stream.mjs +3 -4
  45. package/dist/esm/stream.mjs.map +1 -1
  46. package/dist/esm/tools/ToolNode.mjs +1 -1
  47. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  48. package/dist/esm/utils/tokens.mjs +3 -3
  49. package/dist/esm/utils/tokens.mjs.map +1 -1
  50. package/dist/types/messages/format.d.ts +1 -2
  51. package/dist/types/messages/prune.d.ts +31 -2
  52. package/dist/types/types/stream.d.ts +2 -2
  53. package/dist/types/utils/tokens.d.ts +1 -1
  54. package/package.json +4 -3
  55. package/src/graphs/Graph.ts +8 -8
  56. package/src/llm/anthropic/llm.ts +7 -8
  57. package/src/llm/anthropic/types.ts +4 -4
  58. package/src/llm/anthropic/utils/message_inputs.ts +6 -6
  59. package/src/llm/anthropic/utils/message_outputs.ts +39 -39
  60. package/src/llm/fake.ts +2 -2
  61. package/src/llm/text.ts +1 -1
  62. package/src/messages/core.ts +6 -6
  63. package/src/messages/format.ts +43 -42
  64. package/src/messages/formatAgentMessages.test.ts +35 -35
  65. package/src/messages/formatAgentMessages.tools.test.ts +30 -30
  66. package/src/messages/prune.ts +182 -255
  67. package/src/messages/shiftIndexTokenCountMap.test.ts +18 -18
  68. package/src/mockStream.ts +1 -1
  69. package/src/run.ts +2 -2
  70. package/src/specs/prune.test.ts +89 -89
  71. package/src/specs/reasoning.test.ts +1 -1
  72. package/src/specs/thinking-prune.test.ts +265 -261
  73. package/src/specs/tool-error.test.ts +16 -17
  74. package/src/stream.ts +13 -14
  75. package/src/tools/ToolNode.ts +1 -1
  76. package/src/types/stream.ts +4 -3
  77. package/src/utils/tokens.ts +12 -12
@@ -7,7 +7,7 @@ import {
7
7
  UsageMetadata,
8
8
  AIMessageChunk,
9
9
  } from '@langchain/core/messages';
10
- import type { ToolCallChunk } from "@langchain/core/messages/tool";
10
+ import type { ToolCallChunk } from '@langchain/core/messages/tool';
11
11
  import { ToolCall } from '@langchain/core/messages/tool';
12
12
  import { ChatGeneration } from '@langchain/core/outputs';
13
13
  import { AnthropicMessageResponse } from '../types.js';
@@ -92,18 +92,18 @@ export function _makeMessageChunkFromAnthropicEvent(
92
92
  }),
93
93
  };
94
94
  } else if (
95
- data.type === "content_block_start" &&
96
- ["tool_use", "document"].includes(data.content_block.type)
95
+ data.type === 'content_block_start' &&
96
+ ['tool_use', 'document'].includes(data.content_block.type)
97
97
  ) {
98
98
  const contentBlock = data.content_block;
99
99
  let toolCallChunks: ToolCallChunk[];
100
- if (contentBlock.type === "tool_use") {
100
+ if (contentBlock.type === 'tool_use') {
101
101
  toolCallChunks = [
102
102
  {
103
103
  id: contentBlock.id,
104
104
  index: data.index,
105
105
  name: contentBlock.name,
106
- args: "",
106
+ args: '',
107
107
  },
108
108
  ];
109
109
  } else {
@@ -112,28 +112,28 @@ export function _makeMessageChunkFromAnthropicEvent(
112
112
  return {
113
113
  chunk: new AIMessageChunk({
114
114
  content: fields.coerceContentToString
115
- ? ""
115
+ ? ''
116
116
  : [
117
- {
118
- index: data.index,
119
- ...data.content_block,
120
- input: "",
121
- },
122
- ],
117
+ {
118
+ index: data.index,
119
+ ...data.content_block,
120
+ input: '',
121
+ },
122
+ ],
123
123
  additional_kwargs: {},
124
124
  tool_call_chunks: toolCallChunks,
125
125
  }),
126
126
  };
127
127
  } else if (
128
- data.type === "content_block_delta" &&
128
+ data.type === 'content_block_delta' &&
129
129
  [
130
- "text_delta",
131
- "citations_delta",
132
- "thinking_delta",
133
- "signature_delta",
130
+ 'text_delta',
131
+ 'citations_delta',
132
+ 'thinking_delta',
133
+ 'signature_delta',
134
134
  ].includes(data.delta.type)
135
135
  ) {
136
- if (fields.coerceContentToString && "text" in data.delta) {
136
+ if (fields.coerceContentToString && 'text' in data.delta) {
137
137
  return {
138
138
  chunk: new AIMessageChunk({
139
139
  content: data.delta.text,
@@ -142,30 +142,30 @@ export function _makeMessageChunkFromAnthropicEvent(
142
142
  } else {
143
143
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
144
  const contentBlock: Record<string, any> = data.delta;
145
- if ("citation" in contentBlock) {
145
+ if ('citation' in contentBlock) {
146
146
  contentBlock.citations = [contentBlock.citation];
147
147
  delete contentBlock.citation;
148
148
  }
149
149
  if (
150
- contentBlock.type === "thinking_delta" ||
151
- contentBlock.type === "signature_delta"
150
+ contentBlock.type === 'thinking_delta' ||
151
+ contentBlock.type === 'signature_delta'
152
152
  ) {
153
153
  return {
154
154
  chunk: new AIMessageChunk({
155
- content: [{ index: data.index, ...contentBlock, type: "thinking" }],
155
+ content: [{ index: data.index, ...contentBlock, type: 'thinking' }],
156
156
  }),
157
157
  };
158
158
  }
159
159
 
160
160
  return {
161
161
  chunk: new AIMessageChunk({
162
- content: [{ index: data.index, ...contentBlock, type: "text" }],
162
+ content: [{ index: data.index, ...contentBlock, type: 'text' }],
163
163
  }),
164
164
  };
165
165
  }
166
166
  } else if (
167
- data.type === "content_block_delta" &&
168
- data.delta.type === "input_json_delta"
167
+ data.type === 'content_block_delta' &&
168
+ data.delta.type === 'input_json_delta'
169
169
  ) {
170
170
  return {
171
171
  chunk: new AIMessageChunk({
@@ -208,19 +208,19 @@ export function _makeMessageChunkFromAnthropicEvent(
208
208
  };
209
209
  }
210
210
  } else if (
211
- data.type === "content_block_start" &&
212
- data.content_block.type === "redacted_thinking"
211
+ data.type === 'content_block_start' &&
212
+ data.content_block.type === 'redacted_thinking'
213
213
  ) {
214
214
  return {
215
215
  chunk: new AIMessageChunk({
216
216
  content: fields.coerceContentToString
217
- ? ""
217
+ ? ''
218
218
  : [{ index: data.index, ...data.content_block }],
219
219
  }),
220
220
  };
221
221
  } else if (
222
- data.type === "content_block_start" &&
223
- data.content_block.type === "thinking"
222
+ data.type === 'content_block_start' &&
223
+ data.content_block.type === 'thinking'
224
224
  ) {
225
225
  const content = data.content_block.thinking;
226
226
  return {
@@ -241,17 +241,17 @@ export function anthropicResponseToChatMessages(
241
241
  ): ChatGeneration[] {
242
242
  const usage: Record<string, number> | null | undefined =
243
243
  additionalKwargs.usage as Record<string, number> | null | undefined;
244
- const usageMetadata =
244
+ const usageMetadata =
245
245
  usage != null
246
246
  ? {
247
- input_tokens: usage.input_tokens ?? 0,
248
- output_tokens: usage.output_tokens ?? 0,
249
- total_tokens: (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
250
- input_token_details: {
251
- cache_creation: usage.cache_creation_input_tokens,
252
- cache_read: usage.cache_read_input_tokens,
253
- },
254
- }
247
+ input_tokens: usage.input_tokens ?? 0,
248
+ output_tokens: usage.output_tokens ?? 0,
249
+ total_tokens: (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
250
+ input_token_details: {
251
+ cache_creation: usage.cache_creation_input_tokens,
252
+ cache_read: usage.cache_read_input_tokens,
253
+ },
254
+ }
255
255
  : undefined;
256
256
  if (messages.length === 1 && messages[0].type === 'text') {
257
257
  return [
package/src/llm/fake.ts CHANGED
@@ -63,7 +63,7 @@ export class FakeChatModel extends FakeListChatModel {
63
63
  },
64
64
  })),
65
65
  } : undefined,
66
- })});
66
+ })});
67
67
  }
68
68
 
69
69
  async *_streamResponseChunks(
@@ -102,7 +102,7 @@ export class FakeChatModel extends FakeListChatModel {
102
102
  args: JSON.stringify(toolCall.args),
103
103
  id: toolCall.id,
104
104
  type: 'tool_call_chunk',
105
- } as ToolCallChunk
105
+ } as ToolCallChunk;
106
106
  });
107
107
  const responseChunk = this._createResponseChunk('', toolCallChunks);
108
108
  yield responseChunk;
package/src/llm/text.ts CHANGED
@@ -1,4 +1,4 @@
1
- /* eslint-disable no-console */
1
+
2
2
  export interface TextStreamOptions {
3
3
  minChunkSize?: number;
4
4
  maxChunkSize?: number;
@@ -78,20 +78,20 @@ function reduceBlocks(blocks: ContentBlock[]): ContentBlock[] {
78
78
  const lastBlock = reduced[reduced.length - 1];
79
79
 
80
80
  // Merge consecutive 'reasoning_content'
81
- if (block.type === 'reasoning_content' && lastBlock?.type === 'reasoning_content') {
81
+ if (block.type === 'reasoning_content' && lastBlock.type === 'reasoning_content') {
82
82
  // append text if exists
83
- if (block.reasoningText?.text) {
83
+ if (block.reasoningText.text) {
84
84
  lastBlock.reasoningText.text = (lastBlock.reasoningText.text || '') + block.reasoningText.text;
85
85
  }
86
86
  // preserve the signature if exists
87
- if (block.reasoningText?.signature) {
87
+ if (block.reasoningText.signature) {
88
88
  lastBlock.reasoningText.signature = block.reasoningText.signature;
89
89
  }
90
90
  }
91
91
  // Merge consecutive 'text'
92
- else if (block.type === 'text' && lastBlock?.type === 'text') {
92
+ else if (block.type === 'text' && lastBlock.type === 'text') {
93
93
  lastBlock.text += block.text;
94
- }
94
+ }
95
95
  // add a new block as it's a different type or first element
96
96
  else {
97
97
  // deep copy to avoid mutation of original
@@ -155,7 +155,7 @@ export function formatAnthropicMessage(message: AIMessageChunk): AIMessage {
155
155
  input: toolCall.args as unknown as string
156
156
  });
157
157
  }
158
- } catch (e) {
158
+ } catch {
159
159
  if (extendedItem.input) {
160
160
  acc.push({ type: 'text', text: extendedItem.input });
161
161
  }
@@ -1,5 +1,5 @@
1
- import { ToolMessage, BaseMessage } from '@langchain/core/messages';
2
- import { HumanMessage, AIMessage, SystemMessage, getBufferString } from '@langchain/core/messages';
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { ToolMessage, BaseMessage , HumanMessage, AIMessage, SystemMessage, getBufferString } from '@langchain/core/messages';
3
3
  import type { MessageContentImageUrl } from '@langchain/core/messages';
4
4
  import type { ToolCall } from '@langchain/core/messages/tool';
5
5
  import type { MessageContentComplex, ToolCallPart, TPayload, TMessage } from '@/types';
@@ -38,17 +38,17 @@ export const formatVisionMessage = ({ message, image_urls, endpoint }: VisionMes
38
38
  ...message,
39
39
  content: [] as MessageContentComplex[]
40
40
  };
41
-
41
+
42
42
  if (endpoint === Providers.ANTHROPIC) {
43
43
  result.content = [
44
- ...image_urls,
44
+ ...image_urls,
45
45
  { type: ContentTypes.TEXT, text: message.content }
46
46
  ] as MessageContentComplex[];
47
47
  return result;
48
48
  }
49
49
 
50
50
  result.content = [
51
- { type: ContentTypes.TEXT, text: message.content },
51
+ { type: ContentTypes.TEXT, text: message.content },
52
52
  ...image_urls
53
53
  ] as MessageContentComplex[];
54
54
 
@@ -87,13 +87,14 @@ interface FormattedMessage {
87
87
  * @param {FormatMessageParams} params - The parameters for formatting.
88
88
  * @returns {FormattedMessage | HumanMessage | AIMessage | SystemMessage} - The formatted message.
89
89
  */
90
- export const formatMessage = ({
91
- message,
92
- userName,
93
- assistantName,
94
- endpoint,
95
- langChain = false
90
+ export const formatMessage = ({
91
+ message,
92
+ userName,
93
+ assistantName,
94
+ endpoint,
95
+ langChain = false
96
96
  }: FormatMessageParams): FormattedMessage | HumanMessage | AIMessage | SystemMessage => {
97
+ // eslint-disable-next-line prefer-const
97
98
  let { role: _role, _name, sender, text, content: _content, lc_id } = message;
98
99
  if (lc_id && lc_id[2] && !langChain) {
99
100
  const roleMapping: Record<string, string> = {
@@ -103,7 +104,7 @@ export const formatMessage = ({
103
104
  };
104
105
  _role = roleMapping[lc_id[2]] || _role;
105
106
  }
106
- const role = _role ?? (sender && sender?.toLowerCase() === 'user' ? 'user' : 'assistant');
107
+ const role = _role ?? (sender != null && sender && sender.toLowerCase() === 'user' ? 'user' : 'assistant');
107
108
  const content = _content ?? text ?? '';
108
109
  const formattedMessage: FormattedMessage = {
109
110
  role,
@@ -122,19 +123,19 @@ export const formatMessage = ({
122
123
  });
123
124
  }
124
125
 
125
- if (_name) {
126
+ if (_name != null && _name) {
126
127
  formattedMessage.name = _name;
127
128
  }
128
129
 
129
- if (userName && formattedMessage.role === 'user') {
130
+ if (userName != null && userName && formattedMessage.role === 'user') {
130
131
  formattedMessage.name = userName;
131
132
  }
132
133
 
133
- if (assistantName && formattedMessage.role === 'assistant') {
134
+ if (assistantName != null && assistantName && formattedMessage.role === 'assistant') {
134
135
  formattedMessage.name = assistantName;
135
136
  }
136
137
 
137
- if (formattedMessage.name) {
138
+ if (formattedMessage.name != null && formattedMessage.name) {
138
139
  // Conform to API regex: ^[a-zA-Z0-9_-]{1,64}$
139
140
  // https://community.openai.com/t/the-format-of-the-name-field-in-the-documentation-is-incorrect/175684/2
140
141
  formattedMessage.name = formattedMessage.name.replace(/[^a-zA-Z0-9_-]/g, '_');
@@ -165,7 +166,7 @@ export const formatMessage = ({
165
166
  * @returns {Array<HumanMessage | AIMessage | SystemMessage>} - The array of formatted LangChain messages.
166
167
  */
167
168
  export const formatLangChainMessages = (
168
- messages: Array<MessageInput>,
169
+ messages: Array<MessageInput>,
169
170
  formatOptions: Omit<FormatMessageParams, 'message' | 'langChain'>
170
171
  ): Array<HumanMessage | AIMessage | SystemMessage> => {
171
172
  return messages.map((msg) => {
@@ -237,7 +238,7 @@ function formatAssistantMessage(message: Partial<TMessage>): Array<AIMessage | T
237
238
  content: part.text || '',
238
239
  });
239
240
  formattedMessages.push(lastAIMessage);
240
- } else if (part?.type === ContentTypes.TOOL_CALL) {
241
+ } else if (part.type === ContentTypes.TOOL_CALL) {
241
242
  if (!lastAIMessage) {
242
243
  throw new Error('Invalid tool call structure: No preceding AIMessage with tool_call_ids');
243
244
  }
@@ -251,7 +252,7 @@ function formatAssistantMessage(message: Partial<TMessage>): Array<AIMessage | T
251
252
  if (typeof _args === 'string') {
252
253
  args = JSON.parse(_args);
253
254
  }
254
- } catch (e) {
255
+ } catch {
255
256
  if (typeof _args === 'string') {
256
257
  args = { input: _args };
257
258
  }
@@ -290,7 +291,7 @@ function formatAssistantMessage(message: Partial<TMessage>): Array<AIMessage | T
290
291
  return acc;
291
292
  }, '')
292
293
  .trim();
293
-
294
+
294
295
  if (content) {
295
296
  formattedMessages.push(new AIMessage({ content }));
296
297
  }
@@ -310,7 +311,7 @@ function formatAssistantMessage(message: Partial<TMessage>): Array<AIMessage | T
310
311
  * @returns {Object} - Object containing formatted messages and updated indexTokenCountMap if provided.
311
312
  */
312
313
  export const formatAgentMessages = (
313
- payload: TPayload,
314
+ payload: TPayload,
314
315
  indexTokenCountMap?: Record<number, number>,
315
316
  tools?: Set<string>
316
317
  ): {
@@ -332,11 +333,11 @@ export const formatAgentMessages = (
332
333
  message.content = [{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: message.content }];
333
334
  }
334
335
  if (message.role !== 'assistant') {
335
- messages.push(formatMessage({
336
- message: message as MessageInput,
337
- langChain: true
336
+ messages.push(formatMessage({
337
+ message: message as MessageInput,
338
+ langChain: true
338
339
  }) as HumanMessage | AIMessage | SystemMessage);
339
-
340
+
340
341
  // Update the index mapping for this message
341
342
  indexMapping[i] = [messages.length - 1];
342
343
  continue;
@@ -350,12 +351,12 @@ export const formatAgentMessages = (
350
351
  // First, check if this message contains tool calls
351
352
  let hasToolCalls = false;
352
353
  let hasInvalidTool = false;
353
- let toolNames: string[] = [];
354
-
354
+ const toolNames: string[] = [];
355
+
355
356
  const content = message.content;
356
357
  if (content && Array.isArray(content)) {
357
358
  for (const part of content) {
358
- if (part?.type === ContentTypes.TOOL_CALL) {
359
+ if (part.type === ContentTypes.TOOL_CALL) {
359
360
  hasToolCalls = true;
360
361
  if (tools.size === 0) {
361
362
  hasInvalidTool = true;
@@ -369,17 +370,17 @@ export const formatAgentMessages = (
369
370
  }
370
371
  }
371
372
  }
372
-
373
+
373
374
  // If this message has tool calls and at least one is invalid, we need to convert it
374
375
  if (hasToolCalls && hasInvalidTool) {
375
376
  // We need to collect all related messages (this message and any subsequent tool messages)
376
377
  const toolSequence: BaseMessage[] = [];
377
378
  let sequenceEndIndex = i;
378
-
379
+
379
380
  // Process the current assistant message to get the AIMessage with tool calls
380
381
  const formattedMessages = formatAssistantMessage(message);
381
382
  toolSequence.push(...formattedMessages);
382
-
383
+
383
384
  // Look ahead for any subsequent assistant messages that might be part of this tool sequence
384
385
  let j = i + 1;
385
386
  while (j < payload.length && payload[j].role === 'assistant') {
@@ -388,13 +389,13 @@ export const formatAgentMessages = (
388
389
  const content = payload[j].content;
389
390
  if (content && Array.isArray(content)) {
390
391
  for (const part of content) {
391
- if (part?.type === ContentTypes.TOOL_CALL) {
392
+ if (part.type === ContentTypes.TOOL_CALL) {
392
393
  isToolResponse = true;
393
394
  break;
394
395
  }
395
396
  }
396
397
  }
397
-
398
+
398
399
  if (isToolResponse) {
399
400
  // This is part of the tool sequence, add it
400
401
  const nextMessages = formatAssistantMessage(payload[j]);
@@ -406,20 +407,20 @@ export const formatAgentMessages = (
406
407
  break;
407
408
  }
408
409
  }
409
-
410
+
410
411
  // Convert the sequence to a string
411
412
  const bufferString = getBufferString(toolSequence);
412
413
  messages.push(new AIMessage({ content: bufferString }));
413
-
414
+
414
415
  // Skip the messages we've already processed
415
416
  i = sequenceEndIndex;
416
-
417
+
417
418
  // Update the index mapping for this sequence
418
419
  const resultIndices = [messages.length - 1];
419
420
  for (let k = i; k >= i && k <= sequenceEndIndex; k++) {
420
421
  indexMapping[k] = resultIndices;
421
422
  }
422
-
423
+
423
424
  continue;
424
425
  }
425
426
  }
@@ -427,7 +428,7 @@ export const formatAgentMessages = (
427
428
  // Process the assistant message using the helper function
428
429
  const formattedMessages = formatAssistantMessage(message);
429
430
  messages.push(...formattedMessages);
430
-
431
+
431
432
  // Update the index mapping for this assistant message
432
433
  // Store all indices that were created from this original message
433
434
  const endMessageIndex = messages.length;
@@ -443,7 +444,7 @@ export const formatAgentMessages = (
443
444
  for (let originalIndex = 0; originalIndex < payload.length; originalIndex++) {
444
445
  const resultIndices = indexMapping[originalIndex] || [];
445
446
  const tokenCount = indexTokenCountMap[originalIndex];
446
-
447
+
447
448
  if (tokenCount !== undefined) {
448
449
  if (resultIndices.length === 1) {
449
450
  // Simple 1:1 mapping
@@ -506,7 +507,7 @@ export const formatContentStrings = (payload: Array<BaseMessage>): Array<BaseMes
506
507
  /**
507
508
  * Adds a value at key 0 for system messages and shifts all key indices by one in an indexTokenCountMap.
508
509
  * This is useful when adding a system message at the beginning of a conversation.
509
- *
510
+ *
510
511
  * @param indexTokenCountMap - The original map of message indices to token counts
511
512
  * @param instructionsTokenCount - The token count for the system message to add at index 0
512
513
  * @returns A new map with the system message at index 0 and all other indices shifted by 1
@@ -518,12 +519,12 @@ export function shiftIndexTokenCountMap(
518
519
  // Create a new map to avoid modifying the original
519
520
  const shiftedMap: Record<number, number> = {};
520
521
  shiftedMap[0] = instructionsTokenCount;
521
-
522
+
522
523
  // Shift all existing indices by 1
523
524
  for (const [indexStr, tokenCount] of Object.entries(indexTokenCountMap)) {
524
525
  const index = Number(indexStr);
525
526
  shiftedMap[index + 1] = tokenCount;
526
527
  }
527
-
528
+
528
529
  return shiftedMap;
529
530
  }
@@ -362,14 +362,14 @@ describe('formatAgentMessages', () => {
362
362
  { role: 'user', content: 'Hello' },
363
363
  { role: 'assistant', content: 'Hi there!' },
364
364
  ];
365
-
365
+
366
366
  const indexTokenCountMap = {
367
367
  0: 5, // 5 tokens for "Hello"
368
368
  1: 10, // 10 tokens for "Hi there!"
369
369
  };
370
-
370
+
371
371
  const result = formatAgentMessages(payload, indexTokenCountMap);
372
-
372
+
373
373
  expect(result.messages).toHaveLength(2);
374
374
  expect(result.indexTokenCountMap).toBeDefined();
375
375
  expect(result.indexTokenCountMap?.[0]).toBe(5);
@@ -399,23 +399,23 @@ describe('formatAgentMessages', () => {
399
399
  ],
400
400
  },
401
401
  ];
402
-
402
+
403
403
  const indexTokenCountMap = {
404
404
  0: 10, // 10 tokens for "What's the weather?"
405
405
  1: 50, // 50 tokens for the assistant message with tool call
406
406
  };
407
-
407
+
408
408
  const result = formatAgentMessages(payload, indexTokenCountMap);
409
-
409
+
410
410
  // The original message at index 1 should be split into two messages
411
411
  expect(result.messages).toHaveLength(3);
412
412
  expect(result.indexTokenCountMap).toBeDefined();
413
413
  expect(result.indexTokenCountMap?.[0]).toBe(10); // User message stays the same
414
-
414
+
415
415
  // The assistant message tokens should be distributed across the resulting messages
416
416
  const totalAssistantTokens = Object.values(result.indexTokenCountMap || {})
417
417
  .reduce((sum, count) => sum + count, 0) - 10; // Subtract user message tokens
418
-
418
+
419
419
  expect(totalAssistantTokens).toBe(50); // Should match the original token count
420
420
  });
421
421
 
@@ -460,23 +460,23 @@ describe('formatAgentMessages', () => {
460
460
  ],
461
461
  },
462
462
  ];
463
-
463
+
464
464
  const indexTokenCountMap = {
465
465
  0: 100, // 100 tokens for the complex assistant message
466
466
  };
467
-
467
+
468
468
  const result = formatAgentMessages(payload, indexTokenCountMap);
469
-
469
+
470
470
  // One message expands to 5 messages (2 tool calls + text before, between, and after)
471
471
  expect(result.messages).toHaveLength(5);
472
472
  expect(result.indexTokenCountMap).toBeDefined();
473
-
473
+
474
474
  // The sum of all token counts should equal the original
475
475
  const totalTokens = Object.values(result.indexTokenCountMap || {})
476
476
  .reduce((sum, count) => sum + count, 0);
477
-
477
+
478
478
  expect(totalTokens).toBe(100);
479
-
479
+
480
480
  // Check that each resulting message has a token count
481
481
  for (let i = 0; i < result.messages.length; i++) {
482
482
  expect(result.indexTokenCountMap?.[i]).toBeDefined();
@@ -495,17 +495,17 @@ describe('formatAgentMessages', () => {
495
495
  ],
496
496
  },
497
497
  ];
498
-
498
+
499
499
  const indexTokenCountMap = {
500
500
  0: 60, // 60 tokens for the message with filtered content
501
501
  };
502
-
502
+
503
503
  const result = formatAgentMessages(payload, indexTokenCountMap);
504
-
504
+
505
505
  // Only one message should remain after filtering
506
506
  expect(result.messages).toHaveLength(1);
507
507
  expect(result.indexTokenCountMap).toBeDefined();
508
-
508
+
509
509
  // All tokens should be assigned to the remaining message
510
510
  expect(result.indexTokenCountMap?.[0]).toBe(60);
511
511
  });
@@ -522,17 +522,17 @@ describe('formatAgentMessages', () => {
522
522
  ],
523
523
  },
524
524
  ];
525
-
525
+
526
526
  const indexTokenCountMap = {
527
527
  0: 40, // 40 tokens for the message with filtered content
528
528
  };
529
-
529
+
530
530
  const result = formatAgentMessages(payload, indexTokenCountMap);
531
-
531
+
532
532
  // No messages should remain after filtering
533
533
  expect(result.messages).toHaveLength(0);
534
534
  expect(result.indexTokenCountMap).toBeDefined();
535
-
535
+
536
536
  // The token count map should be empty since there are no messages
537
537
  expect(Object.keys(result.indexTokenCountMap || {})).toHaveLength(0);
538
538
  });
@@ -561,29 +561,29 @@ describe('formatAgentMessages', () => {
561
561
  ],
562
562
  },
563
563
  ];
564
-
564
+
565
565
  const indexTokenCountMap = {
566
566
  0: 15, // 15 tokens for the user message
567
567
  1: 45, // 45 tokens for the assistant message with tool call
568
568
  };
569
-
569
+
570
570
  const result = formatAgentMessages(payload, indexTokenCountMap);
571
-
571
+
572
572
  // 2 input messages become 3 output messages (user + assistant + tool)
573
573
  expect(payload).toHaveLength(2);
574
574
  expect(result.messages).toHaveLength(3);
575
575
  expect(result.indexTokenCountMap).toBeDefined();
576
576
  expect(Object.keys(result.indexTokenCountMap ?? {}).length).toBe(3);
577
-
577
+
578
578
  // Check message types
579
579
  expect(result.messages[0]).toBeInstanceOf(HumanMessage);
580
580
  expect(result.messages[1]).toBeInstanceOf(AIMessage);
581
581
  expect(result.messages[2]).toBeInstanceOf(ToolMessage);
582
-
582
+
583
583
  // The sum of all token counts should equal the original total
584
584
  const totalTokens = Object.values(result.indexTokenCountMap || {})
585
585
  .reduce((sum, count) => sum + count, 0);
586
-
586
+
587
587
  expect(totalTokens).toBe(60); // 15 + 45
588
588
  });
589
589
 
@@ -600,30 +600,30 @@ describe('formatAgentMessages', () => {
600
600
  ],
601
601
  },
602
602
  ];
603
-
603
+
604
604
  const indexTokenCountMap = {
605
605
  0: 10, // 10 tokens for the user message
606
606
  1: 30, // 30 tokens for the assistant message that will be filtered out
607
607
  };
608
-
608
+
609
609
  const result = formatAgentMessages(payload, indexTokenCountMap);
610
-
610
+
611
611
  // 2 input messages become 1 output message (only the user message remains)
612
612
  expect(payload).toHaveLength(2);
613
613
  expect(result.messages).toHaveLength(1);
614
614
  expect(result.indexTokenCountMap).toBeDefined();
615
615
  expect(Object.keys(result.indexTokenCountMap ?? {}).length).toBe(1);
616
-
616
+
617
617
  // Check message type
618
618
  expect(result.messages[0]).toBeInstanceOf(HumanMessage);
619
-
619
+
620
620
  // Only the user message tokens should remain
621
621
  expect(result.indexTokenCountMap?.[0]).toBe(10);
622
-
622
+
623
623
  // The total tokens should be just the user message tokens
624
624
  const totalTokens = Object.values(result.indexTokenCountMap || {})
625
625
  .reduce((sum, count) => sum + count, 0);
626
-
626
+
627
627
  expect(totalTokens).toBe(10);
628
628
  });
629
629
  });