@librechat/agents 2.4.20 → 2.4.22

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,5 +1,11 @@
1
1
  // src/messages.ts
2
- import { AIMessageChunk, HumanMessage, ToolMessage, AIMessage, BaseMessage } from '@langchain/core/messages';
2
+ import {
3
+ AIMessageChunk,
4
+ HumanMessage,
5
+ ToolMessage,
6
+ AIMessage,
7
+ BaseMessage,
8
+ } from '@langchain/core/messages';
3
9
  import type { ToolCall } from '@langchain/core/messages/tool';
4
10
  import type * as t from '@/types';
5
11
  import { Providers } from '@/common';
@@ -7,7 +13,7 @@ import { Providers } from '@/common';
7
13
  export function getConverseOverrideMessage({
8
14
  userMessage,
9
15
  lastMessageX,
10
- lastMessageY
16
+ lastMessageY,
11
17
  }: {
12
18
  userMessage: string[];
13
19
  lastMessageX: AIMessageChunk | null;
@@ -43,13 +49,22 @@ const allowedTypesByProvider: Record<string, string[]> = {
43
49
  const modifyContent = ({
44
50
  provider,
45
51
  messageType,
46
- content
52
+ content,
47
53
  }: {
48
- provider: Providers, messageType: string, content: t.ExtendedMessageContent[]
54
+ provider: Providers;
55
+ messageType: string;
56
+ content: t.ExtendedMessageContent[];
49
57
  }): t.ExtendedMessageContent[] => {
50
- const allowedTypes = allowedTypesByProvider[provider] ?? allowedTypesByProvider.default;
51
- return content.map(item => {
52
- if (item && typeof item === 'object' && 'type' in item && item.type != null && item.type) {
58
+ const allowedTypes =
59
+ allowedTypesByProvider[provider] ?? allowedTypesByProvider.default;
60
+ return content.map((item) => {
61
+ if (
62
+ item &&
63
+ typeof item === 'object' &&
64
+ 'type' in item &&
65
+ item.type != null &&
66
+ item.type
67
+ ) {
53
68
  let newType = item.type;
54
69
  if (newType.endsWith('_delta')) {
55
70
  newType = newType.replace('_delta', '');
@@ -59,7 +74,12 @@ const modifyContent = ({
59
74
  }
60
75
 
61
76
  /* Handle the edge case for empty object 'tool_use' input in AI messages */
62
- if (messageType === 'ai' && newType === 'tool_use' && 'input' in item && item.input === '') {
77
+ if (
78
+ messageType === 'ai' &&
79
+ newType === 'tool_use' &&
80
+ 'input' in item &&
81
+ item.input === ''
82
+ ) {
63
83
  return { ...item, type: newType, input: '{}' };
64
84
  }
65
85
 
@@ -69,7 +89,9 @@ const modifyContent = ({
69
89
  });
70
90
  };
71
91
 
72
- type ContentBlock = Partial<t.BedrockReasoningContentText> | t.MessageDeltaUpdate;
92
+ type ContentBlock =
93
+ | Partial<t.BedrockReasoningContentText>
94
+ | t.MessageDeltaUpdate;
73
95
 
74
96
  function reduceBlocks(blocks: ContentBlock[]): ContentBlock[] {
75
97
  const reduced: ContentBlock[] = [];
@@ -78,14 +100,25 @@ function reduceBlocks(blocks: ContentBlock[]): ContentBlock[] {
78
100
  const lastBlock = reduced[reduced.length - 1] as ContentBlock | undefined;
79
101
 
80
102
  // Merge consecutive 'reasoning_content'
81
- if (block.type === 'reasoning_content' && lastBlock?.type === 'reasoning_content') {
103
+ if (
104
+ block.type === 'reasoning_content' &&
105
+ lastBlock?.type === 'reasoning_content'
106
+ ) {
82
107
  // append text if exists
83
108
  if (block.reasoningText?.text != null && block.reasoningText.text) {
84
- (lastBlock.reasoningText as t.BedrockReasoningContentText['reasoningText']).text = (lastBlock.reasoningText?.text ?? '') + block.reasoningText.text;
109
+ (
110
+ lastBlock.reasoningText as t.BedrockReasoningContentText['reasoningText']
111
+ ).text =
112
+ (lastBlock.reasoningText?.text ?? '') + block.reasoningText.text;
85
113
  }
86
114
  // preserve the signature if exists
87
- if (block.reasoningText?.signature != null && block.reasoningText.signature) {
88
- (lastBlock.reasoningText as t.BedrockReasoningContentText['reasoningText']).signature = block.reasoningText.signature;
115
+ if (
116
+ block.reasoningText?.signature != null &&
117
+ block.reasoningText.signature
118
+ ) {
119
+ (
120
+ lastBlock.reasoningText as t.BedrockReasoningContentText['reasoningText']
121
+ ).signature = block.reasoningText.signature;
89
122
  }
90
123
  }
91
124
  // Merge consecutive 'text'
@@ -102,19 +135,35 @@ function reduceBlocks(blocks: ContentBlock[]): ContentBlock[] {
102
135
  return reduced;
103
136
  }
104
137
 
105
- export function modifyDeltaProperties(provider: Providers, obj?: AIMessageChunk): AIMessageChunk | undefined {
138
+ export function modifyDeltaProperties(
139
+ provider: Providers,
140
+ obj?: AIMessageChunk
141
+ ): AIMessageChunk | undefined {
106
142
  if (!obj || typeof obj !== 'object') return obj;
107
143
 
108
- const messageType = obj._getType ? obj._getType() : '';
144
+ const messageType = (obj as Partial<AIMessageChunk>)._getType
145
+ ? obj._getType()
146
+ : '';
109
147
 
110
148
  if (provider === Providers.BEDROCK && Array.isArray(obj.content)) {
111
149
  obj.content = reduceBlocks(obj.content as ContentBlock[]);
112
150
  }
113
151
  if (Array.isArray(obj.content)) {
114
- obj.content = modifyContent({ provider, messageType, content: obj.content });
152
+ obj.content = modifyContent({
153
+ provider,
154
+ messageType,
155
+ content: obj.content,
156
+ });
115
157
  }
116
- if (obj.lc_kwargs && Array.isArray(obj.lc_kwargs.content)) {
117
- obj.lc_kwargs.content = modifyContent({ provider, messageType, content: obj.lc_kwargs.content });
158
+ if (
159
+ (obj as Partial<AIMessageChunk>).lc_kwargs &&
160
+ Array.isArray(obj.lc_kwargs.content)
161
+ ) {
162
+ obj.lc_kwargs.content = modifyContent({
163
+ provider,
164
+ messageType,
165
+ content: obj.lc_kwargs.content,
166
+ });
118
167
  }
119
168
  return obj;
120
169
  }
@@ -124,48 +173,65 @@ export function formatAnthropicMessage(message: AIMessageChunk): AIMessage {
124
173
  return new AIMessage({ content: message.content });
125
174
  }
126
175
 
127
- const toolCallMap = new Map(message.tool_calls.map(tc => [tc.id, tc]));
176
+ const toolCallMap = new Map(message.tool_calls.map((tc) => [tc.id, tc]));
128
177
  let formattedContent: string | t.ExtendedMessageContent[];
129
178
 
130
179
  if (Array.isArray(message.content)) {
131
- formattedContent = message.content.reduce<t.ExtendedMessageContent[]>((acc, item) => {
132
- if (typeof item === 'object' && item !== null) {
133
- const extendedItem = item as t.ExtendedMessageContent;
134
- if (extendedItem.type === 'text' && extendedItem.text != null && extendedItem.text) {
135
- acc.push({ type: 'text', text: extendedItem.text });
136
- } else if (extendedItem.type === 'tool_use' && extendedItem.id != null && extendedItem.id) {
137
- const toolCall = toolCallMap.get(extendedItem.id);
138
- if (toolCall) {
139
- acc.push({
140
- type: 'tool_use',
141
- id: extendedItem.id,
142
- name: toolCall.name,
143
- input: toolCall.args as unknown as string
144
- });
145
- }
146
- } else if ('input' in extendedItem && extendedItem.input != null && extendedItem.input) {
147
- try {
148
- const parsedInput = JSON.parse(extendedItem.input);
149
- const toolCall = message.tool_calls?.find(tc => tc.args.input === parsedInput.input);
180
+ formattedContent = message.content.reduce<t.ExtendedMessageContent[]>(
181
+ (acc, item) => {
182
+ if (typeof item === 'object') {
183
+ const extendedItem = item as t.ExtendedMessageContent;
184
+ if (
185
+ extendedItem.type === 'text' &&
186
+ extendedItem.text != null &&
187
+ extendedItem.text
188
+ ) {
189
+ acc.push({ type: 'text', text: extendedItem.text });
190
+ } else if (
191
+ extendedItem.type === 'tool_use' &&
192
+ extendedItem.id != null &&
193
+ extendedItem.id
194
+ ) {
195
+ const toolCall = toolCallMap.get(extendedItem.id);
150
196
  if (toolCall) {
151
197
  acc.push({
152
198
  type: 'tool_use',
153
- id: toolCall.id,
199
+ id: extendedItem.id,
154
200
  name: toolCall.name,
155
- input: toolCall.args as unknown as string
201
+ input: toolCall.args as unknown as string,
156
202
  });
157
203
  }
158
- } catch {
159
- if (extendedItem.input) {
160
- acc.push({ type: 'text', text: extendedItem.input });
204
+ } else if (
205
+ 'input' in extendedItem &&
206
+ extendedItem.input != null &&
207
+ extendedItem.input
208
+ ) {
209
+ try {
210
+ const parsedInput = JSON.parse(extendedItem.input);
211
+ const toolCall = message.tool_calls?.find(
212
+ (tc) => tc.args.input === parsedInput.input
213
+ );
214
+ if (toolCall) {
215
+ acc.push({
216
+ type: 'tool_use',
217
+ id: toolCall.id,
218
+ name: toolCall.name,
219
+ input: toolCall.args as unknown as string,
220
+ });
221
+ }
222
+ } catch {
223
+ if (extendedItem.input) {
224
+ acc.push({ type: 'text', text: extendedItem.input });
225
+ }
161
226
  }
162
227
  }
228
+ } else if (typeof item === 'string') {
229
+ acc.push({ type: 'text', text: item });
163
230
  }
164
- } else if (typeof item === 'string') {
165
- acc.push({ type: 'text', text: item });
166
- }
167
- return acc;
168
- }, []);
231
+ return acc;
232
+ },
233
+ []
234
+ );
169
235
  } else if (typeof message.content === 'string') {
170
236
  formattedContent = message.content;
171
237
  } else {
@@ -179,39 +245,48 @@ export function formatAnthropicMessage(message: AIMessageChunk): AIMessage {
179
245
  // type: 'tool_call',
180
246
  // }));
181
247
 
182
- const formattedToolCalls: t.AgentToolCall[] = message.tool_calls.map(toolCall => ({
183
- id: toolCall.id ?? '',
184
- type: 'function',
185
- function: {
186
- name: toolCall.name,
187
- arguments: toolCall.args
188
- }
189
- }));
248
+ const formattedToolCalls: t.AgentToolCall[] = message.tool_calls.map(
249
+ (toolCall) => ({
250
+ id: toolCall.id ?? '',
251
+ type: 'function',
252
+ function: {
253
+ name: toolCall.name,
254
+ arguments: toolCall.args,
255
+ },
256
+ })
257
+ );
190
258
 
191
259
  return new AIMessage({
192
260
  content: formattedContent,
193
261
  tool_calls: formattedToolCalls as ToolCall[],
194
262
  additional_kwargs: {
195
263
  ...message.additional_kwargs,
196
- }
264
+ },
197
265
  });
198
266
  }
199
267
 
200
- export function convertMessagesToContent(messages: BaseMessage[]): t.MessageContentComplex[] {
268
+ export function convertMessagesToContent(
269
+ messages: BaseMessage[]
270
+ ): t.MessageContentComplex[] {
201
271
  const processedContent: t.MessageContentComplex[] = [];
202
272
 
203
273
  const addContentPart = (message: BaseMessage | null): void => {
204
- const content = message?.lc_kwargs.content != null ? message.lc_kwargs.content : message?.content;
274
+ const content =
275
+ message?.lc_kwargs.content != null
276
+ ? message.lc_kwargs.content
277
+ : message?.content;
205
278
  if (content === undefined) {
206
279
  return;
207
280
  }
208
281
  if (typeof content === 'string') {
209
282
  processedContent.push({
210
283
  type: 'text',
211
- text: content
284
+ text: content,
212
285
  });
213
286
  } else if (Array.isArray(content)) {
214
- const filteredContent = content.filter(item => item && item.type !== 'tool_use');
287
+ const filteredContent = content.filter(
288
+ (item) => item != null && item.type !== 'tool_use'
289
+ );
215
290
  processedContent.push(...filteredContent);
216
291
  }
217
292
  };
@@ -223,10 +298,13 @@ export function convertMessagesToContent(messages: BaseMessage[]): t.MessageCont
223
298
  const message = messages[i] as BaseMessage | null;
224
299
  const messageType = message?._getType();
225
300
 
226
- if (messageType === 'ai' && (message as AIMessage).tool_calls?.length) {
301
+ if (
302
+ messageType === 'ai' &&
303
+ ((message as AIMessage).tool_calls?.length ?? 0) > 0
304
+ ) {
227
305
  const tool_calls = (message as AIMessage).tool_calls || [];
228
306
  for (const tool_call of tool_calls) {
229
- if (!tool_call.id) {
307
+ if (tool_call.id == null || !tool_call.id) {
230
308
  continue;
231
309
  }
232
310
 
@@ -236,7 +314,10 @@ export function convertMessagesToContent(messages: BaseMessage[]): t.MessageCont
236
314
  addContentPart(message);
237
315
  currentAIMessageIndex = processedContent.length - 1;
238
316
  continue;
239
- } else if (messageType === 'tool' && (message as ToolMessage).tool_call_id) {
317
+ } else if (
318
+ messageType === 'tool' &&
319
+ (message as ToolMessage).tool_call_id
320
+ ) {
240
321
  const id = (message as ToolMessage).tool_call_id;
241
322
  const output = (message as ToolMessage).content;
242
323
  const tool_call = toolCallMap.get(id);
@@ -265,35 +346,41 @@ export function formatAnthropicArtifactContent(messages: BaseMessage[]): void {
265
346
  if (!(lastMessage instanceof ToolMessage)) return;
266
347
 
267
348
  // Find the latest AIMessage with tool_calls that this tool message belongs to
268
- const latestAIParentIndex = findLastIndex(messages,
269
- msg => (msg instanceof AIMessageChunk &&
270
- (msg.tool_calls?.length ?? 0) > 0 &&
271
- msg.tool_calls?.some(tc => tc.id === lastMessage.tool_call_id)) ?? false
349
+ const latestAIParentIndex = findLastIndex(
350
+ messages,
351
+ (msg) =>
352
+ (msg instanceof AIMessageChunk &&
353
+ (msg.tool_calls?.length ?? 0) > 0 &&
354
+ msg.tool_calls?.some((tc) => tc.id === lastMessage.tool_call_id)) ??
355
+ false
272
356
  );
273
357
 
274
358
  if (latestAIParentIndex === -1) return;
275
359
 
276
360
  // Check if any tool message after the AI message has array artifact content
277
361
  const hasArtifactContent = messages.some(
278
- (msg, i) => i > latestAIParentIndex
279
- && msg instanceof ToolMessage
280
- && msg.artifact != null
281
- && msg.artifact?.content != null
282
- && Array.isArray(msg.artifact.content)
362
+ (msg, i) =>
363
+ i > latestAIParentIndex &&
364
+ msg instanceof ToolMessage &&
365
+ msg.artifact != null &&
366
+ msg.artifact?.content != null &&
367
+ Array.isArray(msg.artifact.content)
283
368
  );
284
369
 
285
370
  if (!hasArtifactContent) return;
286
371
 
287
372
  const message = messages[latestAIParentIndex] as AIMessageChunk;
288
- const toolCallIds = message.tool_calls?.map(tc => tc.id) ?? [];
373
+ const toolCallIds = message.tool_calls?.map((tc) => tc.id) ?? [];
289
374
 
290
375
  for (let j = latestAIParentIndex + 1; j < messages.length; j++) {
291
376
  const msg = messages[j];
292
- if (msg instanceof ToolMessage &&
293
- toolCallIds.includes(msg.tool_call_id) &&
294
- msg.artifact != null &&
295
- Array.isArray(msg.artifact?.content) &&
296
- Array.isArray(msg.content)) {
377
+ if (
378
+ msg instanceof ToolMessage &&
379
+ toolCallIds.includes(msg.tool_call_id) &&
380
+ msg.artifact != null &&
381
+ Array.isArray(msg.artifact?.content) &&
382
+ Array.isArray(msg.content)
383
+ ) {
297
384
  msg.content = msg.content.concat(msg.artifact.content);
298
385
  }
299
386
  }
@@ -304,21 +391,25 @@ export function formatArtifactPayload(messages: BaseMessage[]): void {
304
391
  if (!(lastMessageY instanceof ToolMessage)) return;
305
392
 
306
393
  // Find the latest AIMessage with tool_calls that this tool message belongs to
307
- const latestAIParentIndex = findLastIndex(messages,
308
- msg => (msg instanceof AIMessageChunk &&
309
- (msg.tool_calls?.length ?? 0) > 0 &&
310
- msg.tool_calls?.some(tc => tc.id === lastMessageY.tool_call_id)) ?? false
394
+ const latestAIParentIndex = findLastIndex(
395
+ messages,
396
+ (msg) =>
397
+ (msg instanceof AIMessageChunk &&
398
+ (msg.tool_calls?.length ?? 0) > 0 &&
399
+ msg.tool_calls?.some((tc) => tc.id === lastMessageY.tool_call_id)) ??
400
+ false
311
401
  );
312
402
 
313
403
  if (latestAIParentIndex === -1) return;
314
404
 
315
405
  // Check if any tool message after the AI message has array artifact content
316
406
  const hasArtifactContent = messages.some(
317
- (msg, i) => i > latestAIParentIndex
318
- && msg instanceof ToolMessage
319
- && msg.artifact != null
320
- && msg.artifact?.content != null
321
- && Array.isArray(msg.artifact.content)
407
+ (msg, i) =>
408
+ i > latestAIParentIndex &&
409
+ msg instanceof ToolMessage &&
410
+ msg.artifact != null &&
411
+ msg.artifact?.content != null &&
412
+ Array.isArray(msg.artifact.content)
322
413
  );
323
414
 
324
415
  if (!hasArtifactContent) return;
@@ -326,20 +417,27 @@ export function formatArtifactPayload(messages: BaseMessage[]): void {
326
417
  // Collect all relevant tool messages and their artifacts
327
418
  const relevantMessages = messages
328
419
  .slice(latestAIParentIndex + 1)
329
- .filter(msg => msg instanceof ToolMessage) as ToolMessage[];
420
+ .filter((msg) => msg instanceof ToolMessage) as ToolMessage[];
330
421
 
331
422
  // Aggregate all content and artifacts
332
423
  const aggregatedContent: t.MessageContentComplex[] = [];
333
424
 
334
- relevantMessages.forEach(msg => {
425
+ relevantMessages.forEach((msg) => {
335
426
  if (!Array.isArray(msg.artifact?.content)) {
336
427
  return;
337
428
  }
338
- if (!Array.isArray(msg.content)) {
339
- return;
429
+ let currentContent = msg.content;
430
+ if (!Array.isArray(currentContent)) {
431
+ currentContent = [
432
+ {
433
+ type: 'text',
434
+ text: msg.content,
435
+ },
436
+ ];
340
437
  }
341
- aggregatedContent.push(...msg.content);
342
- msg.content = 'Tool response is included in the next message as a Human message';
438
+ aggregatedContent.push(...currentContent);
439
+ msg.content =
440
+ 'Tool response is included in the next message as a Human message';
343
441
  aggregatedContent.push(...msg.artifact.content);
344
442
  });
345
443
 
@@ -349,11 +447,14 @@ export function formatArtifactPayload(messages: BaseMessage[]): void {
349
447
  }
350
448
  }
351
449
 
352
- export function findLastIndex<T>(array: T[], predicate: (value: T) => boolean): number {
450
+ export function findLastIndex<T>(
451
+ array: T[],
452
+ predicate: (value: T) => boolean
453
+ ): number {
353
454
  for (let i = array.length - 1; i >= 0; i--) {
354
455
  if (predicate(array[i])) {
355
456
  return i;
356
457
  }
357
458
  }
358
459
  return -1;
359
- }
460
+ }
package/src/utils/llm.ts CHANGED
@@ -5,12 +5,23 @@ export function isOpenAILike(provider?: string | Providers): boolean {
5
5
  if (provider == null) {
6
6
  return false;
7
7
  }
8
- return ([Providers.OPENAI, Providers.AZURE] as string[]).includes(provider);
8
+ return (
9
+ [
10
+ Providers.OPENAI,
11
+ Providers.AZURE,
12
+ Providers.OPENROUTER,
13
+ Providers.XAI,
14
+ Providers.DEEPSEEK,
15
+ Providers.OLLAMA,
16
+ ] as string[]
17
+ ).includes(provider);
9
18
  }
10
19
 
11
20
  export function isGoogleLike(provider?: string | Providers): boolean {
12
21
  if (provider == null) {
13
22
  return false;
14
23
  }
15
- return ([Providers.GOOGLE, Providers.VERTEXAI] as string[]).includes(provider);
16
- }
24
+ return ([Providers.GOOGLE, Providers.VERTEXAI] as string[]).includes(
25
+ provider
26
+ );
27
+ }