@librechat/agents 3.1.75-dev.1 → 3.1.76

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 (51) hide show
  1. package/dist/cjs/llm/openai/index.cjs +43 -0
  2. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  3. package/dist/cjs/llm/openai/utils/index.cjs +19 -10
  4. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  5. package/dist/cjs/messages/format.cjs +67 -10
  6. package/dist/cjs/messages/format.cjs.map +1 -1
  7. package/dist/cjs/tools/search/search.cjs +55 -66
  8. package/dist/cjs/tools/search/search.cjs.map +1 -1
  9. package/dist/cjs/tools/search/tavily-scraper.cjs +189 -0
  10. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -0
  11. package/dist/cjs/tools/search/tavily-search.cjs +372 -0
  12. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -0
  13. package/dist/cjs/tools/search/tool.cjs +26 -4
  14. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  15. package/dist/cjs/tools/search/utils.cjs +10 -3
  16. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  17. package/dist/esm/llm/openai/index.mjs +43 -0
  18. package/dist/esm/llm/openai/index.mjs.map +1 -1
  19. package/dist/esm/llm/openai/utils/index.mjs +19 -10
  20. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  21. package/dist/esm/messages/format.mjs +67 -10
  22. package/dist/esm/messages/format.mjs.map +1 -1
  23. package/dist/esm/tools/search/search.mjs +55 -66
  24. package/dist/esm/tools/search/search.mjs.map +1 -1
  25. package/dist/esm/tools/search/tavily-scraper.mjs +186 -0
  26. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -0
  27. package/dist/esm/tools/search/tavily-search.mjs +370 -0
  28. package/dist/esm/tools/search/tavily-search.mjs.map +1 -0
  29. package/dist/esm/tools/search/tool.mjs +26 -4
  30. package/dist/esm/tools/search/tool.mjs.map +1 -1
  31. package/dist/esm/tools/search/utils.mjs +10 -3
  32. package/dist/esm/tools/search/utils.mjs.map +1 -1
  33. package/dist/types/messages/format.d.ts +4 -1
  34. package/dist/types/tools/search/tavily-scraper.d.ts +19 -0
  35. package/dist/types/tools/search/tavily-search.d.ts +4 -0
  36. package/dist/types/tools/search/types.d.ts +99 -5
  37. package/dist/types/tools/search/utils.d.ts +2 -2
  38. package/package.json +1 -1
  39. package/src/llm/custom-chat-models.smoke.test.ts +175 -1
  40. package/src/llm/openai/index.ts +124 -0
  41. package/src/llm/openai/utils/index.ts +23 -14
  42. package/src/llm/openai/utils/messages.test.ts +159 -0
  43. package/src/messages/format.ts +90 -13
  44. package/src/messages/formatAgentMessages.test.ts +166 -1
  45. package/src/tools/search/search.ts +83 -73
  46. package/src/tools/search/tavily-scraper.ts +235 -0
  47. package/src/tools/search/tavily-search.ts +424 -0
  48. package/src/tools/search/tavily.test.ts +965 -0
  49. package/src/tools/search/tool.ts +36 -26
  50. package/src/tools/search/types.ts +134 -11
  51. package/src/tools/search/utils.ts +13 -5
@@ -0,0 +1,159 @@
1
+ import { AIMessage, HumanMessage, ToolMessage } from '@langchain/core/messages';
2
+ import { _convertMessagesToOpenAIParams } from './index';
3
+
4
+ describe('_convertMessagesToOpenAIParams', () => {
5
+ it('includes reasoning_content for assistant messages in tool-call context when requested', () => {
6
+ const messages = [
7
+ new AIMessage({
8
+ content: '',
9
+ tool_calls: [
10
+ {
11
+ id: 'call_1',
12
+ name: 'calculator',
13
+ args: { input: '127 * 453' },
14
+ type: 'tool_call',
15
+ },
16
+ ],
17
+ additional_kwargs: {
18
+ reasoning_content: 'Need calculator.',
19
+ },
20
+ }),
21
+ new ToolMessage({
22
+ content: '57531',
23
+ tool_call_id: 'call_1',
24
+ }),
25
+ new AIMessage({
26
+ content: '127 * 453 = 57531.',
27
+ additional_kwargs: {
28
+ reasoning_content: 'Calculator returned 57531.',
29
+ },
30
+ }),
31
+ ];
32
+
33
+ const params = _convertMessagesToOpenAIParams(messages, 'deepseek-v4-pro', {
34
+ includeReasoningContent: true,
35
+ });
36
+
37
+ expect(params).toHaveLength(3);
38
+ expect(params[0]).toEqual(
39
+ expect.objectContaining({
40
+ role: 'assistant',
41
+ content: '',
42
+ reasoning_content: 'Need calculator.',
43
+ })
44
+ );
45
+ expect(params[2]).toEqual(
46
+ expect.objectContaining({
47
+ role: 'assistant',
48
+ reasoning_content: 'Calculator returned 57531.',
49
+ })
50
+ );
51
+ });
52
+
53
+ it('does not include reasoning_content for no-tool assistant messages', () => {
54
+ const messages = [
55
+ new AIMessage({
56
+ content: '127 * 453 = 57531.',
57
+ additional_kwargs: {
58
+ reasoning_content: 'Mental calculation.',
59
+ },
60
+ }),
61
+ ];
62
+
63
+ const params = _convertMessagesToOpenAIParams(messages, 'deepseek-v4-pro', {
64
+ includeReasoningContent: true,
65
+ });
66
+
67
+ expect(params).toHaveLength(1);
68
+ expect(params[0]).not.toHaveProperty('reasoning_content');
69
+ });
70
+
71
+ it('does not include reasoning_content unless explicitly requested', () => {
72
+ const messages = [
73
+ new AIMessage({
74
+ content: '',
75
+ tool_calls: [
76
+ {
77
+ id: 'call_1',
78
+ name: 'calculator',
79
+ args: { input: '127 * 453' },
80
+ type: 'tool_call',
81
+ },
82
+ ],
83
+ additional_kwargs: {
84
+ reasoning_content: 'Need calculator.',
85
+ },
86
+ }),
87
+ ];
88
+
89
+ const params = _convertMessagesToOpenAIParams(messages, 'deepseek-v4-pro');
90
+
91
+ expect(params).toHaveLength(1);
92
+ expect(params[0]).not.toHaveProperty('reasoning_content');
93
+ });
94
+
95
+ it('keeps reasoning_content latched after tool-call context is established', () => {
96
+ const messages = [
97
+ new AIMessage({
98
+ content: 'No tool was needed.',
99
+ additional_kwargs: {
100
+ reasoning_content: 'Initial no-tool reasoning.',
101
+ },
102
+ }),
103
+ new HumanMessage('Use the calculator.'),
104
+ new AIMessage({
105
+ content: '',
106
+ tool_calls: [
107
+ {
108
+ id: 'call_1',
109
+ name: 'calculator',
110
+ args: { input: '127 * 453' },
111
+ type: 'tool_call',
112
+ },
113
+ ],
114
+ additional_kwargs: {
115
+ reasoning_content: 'Need calculator.',
116
+ },
117
+ }),
118
+ new ToolMessage({
119
+ content: '57531',
120
+ tool_call_id: 'call_1',
121
+ }),
122
+ new AIMessage({
123
+ content: '127 * 453 = 57531.',
124
+ additional_kwargs: {
125
+ reasoning_content: 'Calculator returned 57531.',
126
+ },
127
+ }),
128
+ new HumanMessage('Was that correct?'),
129
+ new AIMessage({
130
+ content: 'Yes.',
131
+ additional_kwargs: {
132
+ reasoning_content: 'The prior calculator result is available.',
133
+ },
134
+ }),
135
+ ];
136
+
137
+ const params = _convertMessagesToOpenAIParams(messages, 'deepseek-v4-pro', {
138
+ includeReasoningContent: true,
139
+ });
140
+
141
+ expect(params).toHaveLength(7);
142
+ expect(params[0]).not.toHaveProperty('reasoning_content');
143
+ expect(params[2]).toEqual(
144
+ expect.objectContaining({
145
+ reasoning_content: 'Need calculator.',
146
+ })
147
+ );
148
+ expect(params[4]).toEqual(
149
+ expect.objectContaining({
150
+ reasoning_content: 'Calculator returned 57531.',
151
+ })
152
+ );
153
+ expect(params[6]).toEqual(
154
+ expect.objectContaining({
155
+ reasoning_content: 'The prior calculator result is available.',
156
+ })
157
+ );
158
+ });
159
+ });
@@ -7,13 +7,19 @@ import {
7
7
  HumanMessage,
8
8
  SystemMessage,
9
9
  } from '@langchain/core/messages';
10
- import type { MessageContentImageUrl } from '@langchain/core/messages';
10
+ import type {
11
+ MessageContent,
12
+ MessageContentImageUrl,
13
+ } from '@langchain/core/messages';
11
14
  import type { ToolCall } from '@langchain/core/messages/tool';
12
15
  import type {
16
+ BedrockReasoningContentText,
13
17
  ExtendedMessageContent,
18
+ GoogleReasoningContentText,
14
19
  MessageContentComplex,
15
20
  ReasoningContentText,
16
21
  SummaryContentBlock,
22
+ ThinkingContentText,
17
23
  ToolCallContent,
18
24
  ToolCallPart,
19
25
  TPayload,
@@ -277,18 +283,86 @@ export const formatFromLangChain = (
277
283
  };
278
284
  };
279
285
 
286
+ interface FormatAssistantMessageOptions {
287
+ preserveReasoningContent?: boolean;
288
+ }
289
+
290
+ interface FormatAgentMessagesOptions {
291
+ provider?: Providers;
292
+ }
293
+
294
+ function extractReasoningContent(
295
+ part: MessageContentComplex | undefined | null
296
+ ): string {
297
+ if (part == null || typeof part !== 'object') {
298
+ return '';
299
+ }
300
+ if (part.type === ContentTypes.THINK) {
301
+ const think = (part as ReasoningContentText).think;
302
+ return typeof think === 'string' ? think : '';
303
+ }
304
+ if (part.type === ContentTypes.THINKING) {
305
+ const thinking = (part as ThinkingContentText).thinking;
306
+ return typeof thinking === 'string' ? thinking : '';
307
+ }
308
+ if (part.type === ContentTypes.REASONING) {
309
+ const reasoning = (part as GoogleReasoningContentText).reasoning;
310
+ return typeof reasoning === 'string' ? reasoning : '';
311
+ }
312
+ if (part.type === ContentTypes.REASONING_CONTENT) {
313
+ const reasoningText = (part as BedrockReasoningContentText).reasoningText;
314
+ return typeof reasoningText.text === 'string' ? reasoningText.text : '';
315
+ }
316
+ return '';
317
+ }
318
+
280
319
  /**
281
320
  * Helper function to format an assistant message
282
321
  * @param message The message to format
322
+ * @param options Optional formatting options
283
323
  * @returns Array of formatted messages
284
324
  */
285
325
  function formatAssistantMessage(
286
- message: Partial<TMessage>
326
+ message: Partial<TMessage>,
327
+ options?: FormatAssistantMessageOptions
287
328
  ): Array<AIMessage | ToolMessage> {
288
329
  const formattedMessages: Array<AIMessage | ToolMessage> = [];
289
330
  let currentContent: MessageContentComplex[] = [];
290
331
  let lastAIMessage: AIMessage | null = null;
291
332
  let hasReasoning = false;
333
+ let pendingReasoningContent = '';
334
+ const shouldPreserveReasoningContent =
335
+ options?.preserveReasoningContent === true;
336
+
337
+ const takePendingReasoningContent = (): string | undefined => {
338
+ if (!shouldPreserveReasoningContent || !pendingReasoningContent) {
339
+ return undefined;
340
+ }
341
+ const reasoningContent = pendingReasoningContent;
342
+ pendingReasoningContent = '';
343
+ return reasoningContent;
344
+ };
345
+
346
+ const createAIMessage = (content: MessageContent): AIMessage => {
347
+ const reasoningContent = takePendingReasoningContent();
348
+ return new AIMessage({
349
+ content,
350
+ ...(reasoningContent != null && {
351
+ additional_kwargs: { reasoning_content: reasoningContent },
352
+ }),
353
+ });
354
+ };
355
+
356
+ const attachPendingReasoningContent = (aiMessage: AIMessage): void => {
357
+ const reasoningContent = takePendingReasoningContent();
358
+ if (reasoningContent == null) {
359
+ return;
360
+ }
361
+ aiMessage.additional_kwargs.reasoning_content =
362
+ typeof aiMessage.additional_kwargs.reasoning_content === 'string'
363
+ ? `${aiMessage.additional_kwargs.reasoning_content}${reasoningContent}`
364
+ : reasoningContent;
365
+ };
292
366
 
293
367
  if (Array.isArray(message.content)) {
294
368
  for (const part of message.content as Array<
@@ -311,15 +385,13 @@ function formatAssistantMessage(
311
385
  }, '');
312
386
  content =
313
387
  `${content}\n${part[ContentTypes.TEXT] ?? part.text ?? ''}`.trim();
314
- lastAIMessage = new AIMessage({ content });
388
+ lastAIMessage = createAIMessage(content);
315
389
  formattedMessages.push(lastAIMessage);
316
390
  currentContent = [];
317
391
  continue;
318
392
  }
319
393
  // Create a new AIMessage with this text and prepare for tool calls
320
- lastAIMessage = new AIMessage({
321
- content: part.text != null ? part.text : '',
322
- });
394
+ lastAIMessage = createAIMessage(part.text != null ? part.text : '');
323
395
  formattedMessages.push(lastAIMessage);
324
396
  } else if (part.type === ContentTypes.TOOL_CALL) {
325
397
  // Skip malformed tool call entries without tool_call property
@@ -344,8 +416,10 @@ function formatAssistantMessage(
344
416
 
345
417
  if (!lastAIMessage) {
346
418
  // "Heal" the payload by creating an AIMessage to precede the tool call
347
- lastAIMessage = new AIMessage({ content: '' });
419
+ lastAIMessage = createAIMessage('');
348
420
  formattedMessages.push(lastAIMessage);
421
+ } else {
422
+ attachPendingReasoningContent(lastAIMessage);
349
423
  }
350
424
 
351
425
  const tool_call: ToolCallPart = _tool_call;
@@ -377,10 +451,12 @@ function formatAssistantMessage(
377
451
  } else if (
378
452
  part.type === ContentTypes.THINK ||
379
453
  part.type === ContentTypes.THINKING ||
454
+ part.type === ContentTypes.REASONING ||
380
455
  part.type === ContentTypes.REASONING_CONTENT ||
381
456
  part.type === 'redacted_thinking'
382
457
  ) {
383
458
  hasReasoning = true;
459
+ pendingReasoningContent += extractReasoningContent(part);
384
460
  continue;
385
461
  } else if (
386
462
  part.type === ContentTypes.ERROR ||
@@ -411,12 +487,10 @@ function formatAssistantMessage(
411
487
  .trim();
412
488
 
413
489
  if (content) {
414
- formattedMessages.push(new AIMessage({ content }));
490
+ formattedMessages.push(createAIMessage(content));
415
491
  }
416
492
  } else if (currentContent.length > 0) {
417
- formattedMessages.push(
418
- new AIMessage({ content: toLangChainContent(currentContent) })
419
- );
493
+ formattedMessages.push(createAIMessage(toLangChainContent(currentContent)));
420
494
  }
421
495
 
422
496
  return formattedMessages;
@@ -832,7 +906,8 @@ export const formatAgentMessages = (
832
906
  /** Pre-resolved skill bodies keyed by skill name. When present, HumanMessages
833
907
  * are reconstructed after skill ToolMessages to restore skill instructions
834
908
  * that were only in LangGraph state during the original run. */
835
- skills?: Map<string, string>
909
+ skills?: Map<string, string>,
910
+ options?: FormatAgentMessagesOptions
836
911
  ): {
837
912
  messages: Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>;
838
913
  indexTokenCountMap?: Record<number, number>;
@@ -1080,7 +1155,9 @@ export const formatAgentMessages = (
1080
1155
  }
1081
1156
  }
1082
1157
 
1083
- const formattedMessages = formatAssistantMessage(processedMessage);
1158
+ const formattedMessages = formatAssistantMessage(processedMessage, {
1159
+ preserveReasoningContent: options?.provider === Providers.DEEPSEEK,
1160
+ });
1084
1161
  if (sourceMessageId != null && sourceMessageId !== '') {
1085
1162
  for (const formattedMessage of formattedMessages) {
1086
1163
  formattedMessage.id = sourceMessageId;
@@ -6,7 +6,7 @@ import {
6
6
  } from '@langchain/core/messages';
7
7
  import type { MessageContentComplex, TPayload } from '@/types';
8
8
  import { formatAgentMessages } from './format';
9
- import { ContentTypes } from '@/common';
9
+ import { ContentTypes, Providers } from '@/common';
10
10
 
11
11
  describe('formatAgentMessages', () => {
12
12
  it('should format simple user and AI messages', () => {
@@ -967,6 +967,171 @@ describe('formatAgentMessages', () => {
967
967
  );
968
968
  });
969
969
 
970
+ it('should preserve hidden reasoning_content for DeepSeek assistant messages', () => {
971
+ const payload: TPayload = [
972
+ {
973
+ role: 'assistant',
974
+ content: [
975
+ {
976
+ type: ContentTypes.THINK,
977
+ [ContentTypes.THINK]: 'Need calculator.',
978
+ },
979
+ {
980
+ type: ContentTypes.TEXT,
981
+ [ContentTypes.TEXT]: 'Using calculator.',
982
+ tool_call_ids: ['call_1'],
983
+ },
984
+ {
985
+ type: ContentTypes.TOOL_CALL,
986
+ tool_call: {
987
+ id: 'call_1',
988
+ name: 'calculator',
989
+ args: '{"input":"127 * 453"}',
990
+ output: '57531',
991
+ },
992
+ },
993
+ {
994
+ type: ContentTypes.THINK,
995
+ [ContentTypes.THINK]: 'Calculator returned 57531.',
996
+ },
997
+ {
998
+ type: ContentTypes.TEXT,
999
+ [ContentTypes.TEXT]: '127 * 453 = 57531.',
1000
+ },
1001
+ ],
1002
+ },
1003
+ ];
1004
+
1005
+ const defaultResult = formatAgentMessages(payload);
1006
+ expect(
1007
+ (defaultResult.messages[0] as AIMessage).additional_kwargs
1008
+ .reasoning_content
1009
+ ).toBeUndefined();
1010
+
1011
+ const result = formatAgentMessages(
1012
+ payload,
1013
+ undefined,
1014
+ undefined,
1015
+ undefined,
1016
+ { provider: Providers.DEEPSEEK }
1017
+ );
1018
+
1019
+ expect(result.messages).toHaveLength(3);
1020
+ expect(result.messages[0]).toBeInstanceOf(AIMessage);
1021
+ expect(result.messages[1]).toBeInstanceOf(ToolMessage);
1022
+ expect(result.messages[2]).toBeInstanceOf(AIMessage);
1023
+
1024
+ const toolCallMessage = result.messages[0] as AIMessage;
1025
+ const finalMessage = result.messages[2] as AIMessage;
1026
+
1027
+ expect(toolCallMessage.content).toBe('Using calculator.');
1028
+ expect(toolCallMessage.tool_calls).toHaveLength(1);
1029
+ expect(toolCallMessage.additional_kwargs.reasoning_content).toBe(
1030
+ 'Need calculator.'
1031
+ );
1032
+ expect(finalMessage.content).toBe('127 * 453 = 57531.');
1033
+ expect(finalMessage.additional_kwargs.reasoning_content).toBe(
1034
+ 'Calculator returned 57531.'
1035
+ );
1036
+ });
1037
+
1038
+ it('should preserve DeepSeek reasoning from supported hidden content blocks', () => {
1039
+ const payload: TPayload = [
1040
+ {
1041
+ role: 'assistant',
1042
+ content: [
1043
+ {
1044
+ type: ContentTypes.THINK,
1045
+ [ContentTypes.THINK]: 'Think. ',
1046
+ },
1047
+ {
1048
+ type: ContentTypes.THINKING,
1049
+ thinking: 'Thinking. ',
1050
+ },
1051
+ {
1052
+ type: ContentTypes.REASONING,
1053
+ reasoning: 'Reasoning. ',
1054
+ },
1055
+ {
1056
+ type: ContentTypes.REASONING_CONTENT,
1057
+ reasoningText: { text: 'Reasoning content.' },
1058
+ },
1059
+ {
1060
+ type: ContentTypes.TEXT,
1061
+ [ContentTypes.TEXT]: 'Done.',
1062
+ },
1063
+ ],
1064
+ },
1065
+ ];
1066
+
1067
+ const result = formatAgentMessages(
1068
+ payload,
1069
+ undefined,
1070
+ undefined,
1071
+ undefined,
1072
+ { provider: Providers.DEEPSEEK }
1073
+ );
1074
+
1075
+ expect(result.messages).toHaveLength(1);
1076
+ expect(result.messages[0]).toBeInstanceOf(AIMessage);
1077
+ expect(result.messages[0].content).toBe('Done.');
1078
+ expect(
1079
+ (result.messages[0] as AIMessage).additional_kwargs.reasoning_content
1080
+ ).toBe('Think. Thinking. Reasoning. Reasoning content.');
1081
+ });
1082
+
1083
+ it('should attach later DeepSeek reasoning to an existing tool-call assistant message', () => {
1084
+ const payload: TPayload = [
1085
+ {
1086
+ role: 'assistant',
1087
+ content: [
1088
+ {
1089
+ type: ContentTypes.THINK,
1090
+ [ContentTypes.THINK]: 'Need calculator. ',
1091
+ },
1092
+ {
1093
+ type: ContentTypes.TEXT,
1094
+ [ContentTypes.TEXT]: 'Using calculator.',
1095
+ tool_call_ids: ['call_1'],
1096
+ },
1097
+ {
1098
+ type: ContentTypes.THINK,
1099
+ [ContentTypes.THINK]: 'Preparing tool call.',
1100
+ },
1101
+ {
1102
+ type: ContentTypes.TOOL_CALL,
1103
+ tool_call: {
1104
+ id: 'call_1',
1105
+ name: 'calculator',
1106
+ args: '{"input":"127 * 453"}',
1107
+ output: '57531',
1108
+ },
1109
+ },
1110
+ ],
1111
+ },
1112
+ ];
1113
+
1114
+ const result = formatAgentMessages(
1115
+ payload,
1116
+ undefined,
1117
+ undefined,
1118
+ undefined,
1119
+ { provider: Providers.DEEPSEEK }
1120
+ );
1121
+
1122
+ expect(result.messages).toHaveLength(2);
1123
+ expect(result.messages[0]).toBeInstanceOf(AIMessage);
1124
+ expect(result.messages[1]).toBeInstanceOf(ToolMessage);
1125
+
1126
+ const toolCallMessage = result.messages[0] as AIMessage;
1127
+
1128
+ expect(toolCallMessage.content).toBe('Using calculator.');
1129
+ expect(toolCallMessage.tool_calls).toHaveLength(1);
1130
+ expect(toolCallMessage.additional_kwargs.reasoning_content).toBe(
1131
+ 'Need calculator. Preparing tool call.'
1132
+ );
1133
+ });
1134
+
970
1135
  it('should strip thinking blocks and join TEXT parts as string', () => {
971
1136
  const payload = [
972
1137
  {