@librechat/agents 2.4.317 → 2.4.319

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 (83) hide show
  1. package/dist/cjs/events.cjs +3 -3
  2. package/dist/cjs/events.cjs.map +1 -1
  3. package/dist/cjs/main.cjs +5 -2
  4. package/dist/cjs/main.cjs.map +1 -1
  5. package/dist/cjs/messages/ids.cjs +23 -0
  6. package/dist/cjs/messages/ids.cjs.map +1 -0
  7. package/dist/cjs/stream.cjs +8 -155
  8. package/dist/cjs/stream.cjs.map +1 -1
  9. package/dist/cjs/tools/handlers.cjs +144 -0
  10. package/dist/cjs/tools/handlers.cjs.map +1 -0
  11. package/dist/cjs/tools/search/content.cjs +140 -0
  12. package/dist/cjs/tools/search/content.cjs.map +1 -0
  13. package/dist/cjs/tools/search/firecrawl.cjs +17 -37
  14. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  15. package/dist/cjs/tools/search/format.cjs +79 -29
  16. package/dist/cjs/tools/search/format.cjs.map +1 -1
  17. package/dist/cjs/tools/search/highlights.cjs +64 -13
  18. package/dist/cjs/tools/search/highlights.cjs.map +1 -1
  19. package/dist/cjs/tools/search/search.cjs +13 -15
  20. package/dist/cjs/tools/search/search.cjs.map +1 -1
  21. package/dist/cjs/tools/search/tool.cjs +42 -12
  22. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  23. package/dist/cjs/tools/search/utils.cjs +35 -0
  24. package/dist/cjs/tools/search/utils.cjs.map +1 -0
  25. package/dist/esm/events.mjs +1 -1
  26. package/dist/esm/events.mjs.map +1 -1
  27. package/dist/esm/main.mjs +3 -1
  28. package/dist/esm/main.mjs.map +1 -1
  29. package/dist/esm/messages/ids.mjs +21 -0
  30. package/dist/esm/messages/ids.mjs.map +1 -0
  31. package/dist/esm/stream.mjs +7 -152
  32. package/dist/esm/stream.mjs.map +1 -1
  33. package/dist/esm/tools/handlers.mjs +141 -0
  34. package/dist/esm/tools/handlers.mjs.map +1 -0
  35. package/dist/esm/tools/search/content.mjs +119 -0
  36. package/dist/esm/tools/search/content.mjs.map +1 -0
  37. package/dist/esm/tools/search/firecrawl.mjs +18 -37
  38. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  39. package/dist/esm/tools/search/format.mjs +79 -29
  40. package/dist/esm/tools/search/format.mjs.map +1 -1
  41. package/dist/esm/tools/search/highlights.mjs +64 -13
  42. package/dist/esm/tools/search/highlights.mjs.map +1 -1
  43. package/dist/esm/tools/search/search.mjs +12 -14
  44. package/dist/esm/tools/search/search.mjs.map +1 -1
  45. package/dist/esm/tools/search/tool.mjs +42 -12
  46. package/dist/esm/tools/search/tool.mjs.map +1 -1
  47. package/dist/esm/tools/search/utils.mjs +32 -0
  48. package/dist/esm/tools/search/utils.mjs.map +1 -0
  49. package/dist/types/index.d.ts +1 -0
  50. package/dist/types/messages/ids.d.ts +3 -0
  51. package/dist/types/messages/index.d.ts +1 -0
  52. package/dist/types/stream.d.ts +0 -8
  53. package/dist/types/tools/handlers.d.ts +8 -0
  54. package/dist/types/tools/search/content.d.ts +4 -0
  55. package/dist/types/tools/search/firecrawl.d.ts +6 -86
  56. package/dist/types/tools/search/format.d.ts +4 -1
  57. package/dist/types/tools/search/highlights.d.ts +1 -1
  58. package/dist/types/tools/search/search.d.ts +1 -1
  59. package/dist/types/tools/search/test.d.ts +1 -0
  60. package/dist/types/tools/search/tool.d.ts +12 -4
  61. package/dist/types/tools/search/types.d.ts +388 -53
  62. package/dist/types/tools/search/utils.d.ts +3 -0
  63. package/package.json +2 -1
  64. package/src/events.ts +49 -15
  65. package/src/index.ts +1 -0
  66. package/src/messages/ids.ts +26 -0
  67. package/src/messages/index.ts +1 -0
  68. package/src/scripts/search.ts +5 -3
  69. package/src/stream.ts +4 -186
  70. package/src/tools/handlers.ts +167 -0
  71. package/src/tools/search/content.test.ts +173 -0
  72. package/src/tools/search/content.ts +147 -0
  73. package/src/tools/search/firecrawl.ts +27 -144
  74. package/src/tools/search/format.ts +89 -31
  75. package/src/tools/search/highlights.ts +99 -17
  76. package/src/tools/search/output.md +2775 -0
  77. package/src/tools/search/search.ts +42 -54
  78. package/src/tools/search/test.html +884 -0
  79. package/src/tools/search/test.md +643 -0
  80. package/src/tools/search/test.ts +159 -0
  81. package/src/tools/search/tool.ts +52 -15
  82. package/src/tools/search/types.ts +439 -61
  83. package/src/tools/search/utils.ts +43 -0
@@ -1,37 +1,69 @@
1
1
  import type * as t from './types';
2
+ import { getDomainName } from './utils';
2
3
 
3
4
  export function formatResultsForLLM(
4
5
  turn: number,
5
6
  results: t.SearchResultData
6
- ): string {
7
+ ): { output: string; references: t.ResultReference[] } {
7
8
  let output = '';
8
9
 
9
10
  const addSection = (title: string): void => {
10
11
  output += `\n=== ${title} ===\n`;
11
12
  };
12
13
 
14
+ const references: t.ResultReference[] = [];
13
15
  // Organic (web) results
14
- const organic = results.organic ?? [];
15
- if (organic.length) {
16
- addSection('Web Results, Turn ' + turn);
17
- organic.forEach((r, i) => {
16
+ if (results.organic?.length != null && results.organic.length > 0) {
17
+ addSection(`Web Results, Turn ${turn}`);
18
+ for (let i = 0; i < results.organic.length; i++) {
19
+ const r = results.organic[i];
18
20
  output += [
19
- `Source ${i}: ${r.title ?? '(no title)'}`,
20
- `Citation Anchor: \\ue202turn${turn}search${i}`,
21
+ `# Source ${i}: "${r.title ?? '(no title)'}"`,
22
+ `Anchor: \\ue202turn${turn}search${i}`,
21
23
  `URL: ${r.link}`,
22
24
  r.snippet != null ? `Summary: ${r.snippet}` : '',
23
25
  r.date != null ? `Date: ${r.date}` : '',
24
26
  r.attribution != null ? `Source: ${r.attribution}` : '',
25
27
  '',
26
- '--- Content Highlights ---',
27
- ...(r.highlights ?? [])
28
- .filter((h) => h.text.trim().length > 0)
29
- .map((h) => `[Relevance: ${h.score.toFixed(2)}]\n${h.text.trim()}`),
28
+ '\n## Highlights\n\n',
29
+ '',
30
30
  '',
31
31
  ]
32
32
  .filter(Boolean)
33
33
  .join('\n');
34
- });
34
+
35
+ (r.highlights ?? [])
36
+ .filter((h) => h.text.trim().length > 0)
37
+ .forEach((h, hIndex) => {
38
+ output += `### Highlight ${hIndex + 1} [Relevance: ${h.score.toFixed(2)}]\n\n`;
39
+ output += '```text\n' + h.text.trim() + '\n```\n\n';
40
+
41
+ if (h.references != null && h.references.length) {
42
+ output += 'Core References:\n';
43
+ output += h.references
44
+ .map((ref) => {
45
+ references.push({
46
+ link: ref.reference.originalUrl,
47
+ attribution: getDomainName(ref.reference.originalUrl),
48
+ title: (
49
+ ((ref.reference.title ?? '') || ref.reference.text) ??
50
+ ''
51
+ ).split('\n')[0],
52
+ });
53
+ return `- ${ref.type}#${ref.originalIndex + 1}: ${ref.reference.originalUrl}\n\t- Anchor: \\ue202turn${turn}ref${references.length - 1}`;
54
+ })
55
+ .join('\n');
56
+ output += '\n\n';
57
+ }
58
+
59
+ if (hIndex < (r.highlights?.length ?? 0) - 1) {
60
+ output += '---\n\n';
61
+ }
62
+ });
63
+
64
+ delete results.organic[i].highlights;
65
+ output += '\n';
66
+ }
35
67
  }
36
68
 
37
69
  // Ignoring these sections for now
@@ -70,23 +102,36 @@ export function formatResultsForLLM(
70
102
  if (results.knowledgeGraph != null) {
71
103
  addSection('Knowledge Graph');
72
104
  output += [
73
- `Title: ${results.knowledgeGraph.title ?? '(no title)'}`,
105
+ `**Title:** ${results.knowledgeGraph.title ?? '(no title)'}`,
106
+ results.knowledgeGraph.type != null
107
+ ? `**Type:** ${results.knowledgeGraph.type}`
108
+ : '',
74
109
  results.knowledgeGraph.description != null
75
- ? `Description: ${results.knowledgeGraph.description}`
110
+ ? `**Description:** ${results.knowledgeGraph.description}`
76
111
  : '',
77
- results.knowledgeGraph.type != null
78
- ? `Type: ${results.knowledgeGraph.type}`
112
+ results.knowledgeGraph.descriptionSource != null
113
+ ? `**Description Source:** ${results.knowledgeGraph.descriptionSource}`
114
+ : '',
115
+ results.knowledgeGraph.descriptionLink != null
116
+ ? `**Description Link:** ${results.knowledgeGraph.descriptionLink}`
79
117
  : '',
80
118
  results.knowledgeGraph.imageUrl != null
81
- ? `Image URL: ${results.knowledgeGraph.imageUrl}`
119
+ ? `**Image URL:** ${results.knowledgeGraph.imageUrl}`
120
+ : '',
121
+ results.knowledgeGraph.website != null
122
+ ? `**Website:** ${results.knowledgeGraph.website}`
82
123
  : '',
83
124
  results.knowledgeGraph.attributes != null
84
- ? `Attributes: ${JSON.stringify(results.knowledgeGraph.attributes, null, 2)}`
125
+ ? `**Attributes:**\n\`\`\`json\n${JSON.stringify(
126
+ results.knowledgeGraph.attributes,
127
+ null,
128
+ 2
129
+ )}\n\`\`\``
85
130
  : '',
86
131
  '',
87
132
  ]
88
133
  .filter(Boolean)
89
- .join('\n');
134
+ .join('\n\n');
90
135
  }
91
136
 
92
137
  // Answer Box
@@ -94,31 +139,44 @@ export function formatResultsForLLM(
94
139
  addSection('Answer Box');
95
140
  output += [
96
141
  results.answerBox.title != null
97
- ? `Title: ${results.answerBox.title}`
98
- : '',
99
- results.answerBox.answer != null
100
- ? `Answer: ${results.answerBox.answer}`
142
+ ? `**Title:** ${results.answerBox.title}`
101
143
  : '',
102
144
  results.answerBox.snippet != null
103
- ? `Snippet: ${results.answerBox.snippet}`
145
+ ? `**Snippet:** ${results.answerBox.snippet}`
146
+ : '',
147
+ results.answerBox.snippetHighlighted != null
148
+ ? `**Snippet Highlighted:** ${results.answerBox.snippetHighlighted
149
+ .map((s) => `\`${s}\``)
150
+ .join(' ')}`
151
+ : '',
152
+ results.answerBox.link != null
153
+ ? `**Link:** ${results.answerBox.link}`
104
154
  : '',
105
- results.answerBox.date != null ? `Date: ${results.answerBox.date}` : '',
106
155
  '',
107
156
  ]
108
157
  .filter(Boolean)
109
- .join('\n');
158
+ .join('\n\n');
110
159
  }
111
160
 
112
161
  // People also ask
113
162
  const peopleAlsoAsk = results.peopleAlsoAsk ?? [];
114
163
  if (peopleAlsoAsk.length) {
115
164
  addSection('People Also Ask');
116
- peopleAlsoAsk.forEach((p, _i) => {
117
- output += [`Q: ${p.question}`, `A: ${p.answer}`, '']
165
+ peopleAlsoAsk.forEach((p, i) => {
166
+ output += [
167
+ `### Question ${i + 1}:`,
168
+ `"${p.question}"`,
169
+ `${p.snippet != null && p.snippet ? `Snippet: ${p.snippet}}` : ''}`,
170
+ `${p.title != null && p.title ? `Title: ${p.title}` : ''}`,
171
+ `${p.link != null && p.link ? `Link: ${p.link}` : ''}`,
172
+ '',
173
+ ]
118
174
  .filter(Boolean)
119
- .join('\n');
175
+ .join('\n\n');
120
176
  });
121
177
  }
122
-
123
- return output.trim();
178
+ return {
179
+ output: output.trim(),
180
+ references,
181
+ };
124
182
  }
@@ -122,6 +122,89 @@ function findBestBoundary(text: string, direction = 'backward'): number {
122
122
  return direction === 'backward' ? text.length : 0;
123
123
  }
124
124
 
125
+ /**
126
+ * Tracks references used in a highlight without changing their numbers
127
+ */
128
+ function trackReferencesInHighlight(
129
+ text: string,
130
+ sourceResult: t.ValidSource // Source containing the original references
131
+ ): {
132
+ references: {
133
+ type: 'link' | 'image' | 'video';
134
+ originalIndex: number;
135
+ reference: t.MediaReference; // Original reference object
136
+ }[];
137
+ } {
138
+ // Track used references
139
+ const references: {
140
+ type: 'link' | 'image' | 'video';
141
+ originalIndex: number;
142
+ reference: t.MediaReference;
143
+ }[] = [];
144
+
145
+ if (!text || text.length === 0 || !text.includes('#')) {
146
+ return { references }; // Early return
147
+ }
148
+
149
+ // Quick check for reference markers
150
+ if (
151
+ !text.includes('link#') &&
152
+ !text.includes('image#') &&
153
+ !text.includes('video#')
154
+ ) {
155
+ return { references };
156
+ }
157
+
158
+ // Get references from the source if available
159
+ const sourceRefs = sourceResult.references || {
160
+ links: [],
161
+ images: [],
162
+ videos: [],
163
+ };
164
+
165
+ // Find references but don't modify text
166
+ const refRegex = /\((link|image|video)#(\d+)(?:\s+"([^"]*)")?\)/g;
167
+ let match;
168
+
169
+ while ((match = refRegex.exec(text)) !== null) {
170
+ const [, type, indexStr] = match;
171
+ const originalIndex = parseInt(indexStr, 10) - 1; // Convert to 0-based
172
+
173
+ // Get the source array for this type
174
+ const refType = type as 'link' | 'image' | 'video';
175
+ const sourceArray = sourceRefs[`${refType}s`] as
176
+ | t.MediaReference[]
177
+ | undefined;
178
+
179
+ // Skip if invalid reference
180
+ if (
181
+ !sourceArray ||
182
+ originalIndex < 0 ||
183
+ originalIndex >= sourceArray.length
184
+ ) {
185
+ continue; // Skip invalid references
186
+ }
187
+
188
+ // Get original reference
189
+ const reference = sourceArray[originalIndex];
190
+
191
+ // Track if not already tracked
192
+ const alreadyTracked = references.some(
193
+ (ref) => ref.type === refType && ref.originalIndex === originalIndex
194
+ );
195
+
196
+ if (!alreadyTracked) {
197
+ references.push({
198
+ type: refType,
199
+ originalIndex,
200
+ reference,
201
+ });
202
+ }
203
+ }
204
+
205
+ return { references };
206
+ }
207
+
125
208
  /**
126
209
  * Expand highlights in search results using smart boundary detection.
127
210
  *
@@ -131,25 +214,19 @@ function findBestBoundary(text: string, direction = 'backward'): number {
131
214
  * @param searchResults - Search results object
132
215
  * @param mainExpandBy - Primary expansion size on each side (default: 300)
133
216
  * @param separatorExpandBy - Additional range to look for separators (default: 150)
134
- * @returns Copy of search results with expanded highlights
217
+ * @returns Copy of search results with expanded highlights and tracked references
135
218
  */
136
219
  export function expandHighlights(
137
220
  searchResults: t.SearchResultData,
138
221
  mainExpandBy = 300,
139
222
  separatorExpandBy = 150
140
223
  ): t.SearchResultData {
141
- // 1. Avoid full deep copy - only copy what we modify
224
+ // Avoid deep copy - only copy what we modify
142
225
  const resultCopy = { ...searchResults };
226
+ if (resultCopy.organic) resultCopy.organic = [...resultCopy.organic];
227
+ if (resultCopy.topStories) resultCopy.topStories = [...resultCopy.topStories];
143
228
 
144
- // Only deep copy the relevant arrays
145
- if (resultCopy.organic) {
146
- resultCopy.organic = [...resultCopy.organic];
147
- }
148
- if (resultCopy.topStories) {
149
- resultCopy.topStories = [...resultCopy.topStories];
150
- }
151
-
152
- // 5. Process the results efficiently
229
+ // Process the results efficiently
153
230
  const processResultTypes = ['organic', 'topStories'] as const;
154
231
 
155
232
  for (const resultType of processResultTypes) {
@@ -170,23 +247,26 @@ export function expandHighlights(
170
247
  const resultCopy = { ...result };
171
248
  const content = result.content;
172
249
  const highlights = [];
173
-
174
250
  // Process each highlight
175
251
  for (const highlight of result.highlights) {
176
- const highlightText = highlight.text;
252
+ const { references } = trackReferencesInHighlight(
253
+ highlight.text,
254
+ result
255
+ );
177
256
 
178
- let startPos = content.indexOf(highlightText);
179
- let highlightLen = highlightText.length;
257
+ let startPos = content.indexOf(highlight.text);
258
+ let highlightLen = highlight.text.length;
180
259
 
181
260
  if (startPos === -1) {
182
261
  // Try with stripped whitespace
183
- const strippedHighlight = highlightText.trim();
262
+ const strippedHighlight = highlight.text.trim();
184
263
  startPos = content.indexOf(strippedHighlight);
185
264
 
186
265
  if (startPos === -1) {
187
266
  highlights.push({
188
267
  text: highlight.text,
189
268
  score: highlight.score,
269
+ references,
190
270
  });
191
271
  continue;
192
272
  }
@@ -225,11 +305,13 @@ export function expandHighlights(
225
305
  highlights.push({
226
306
  text: expandedHighlightText,
227
307
  score: highlight.score,
308
+ references,
228
309
  });
229
310
  }
230
311
 
231
- delete resultCopy.content;
232
312
  resultCopy.highlights = highlights;
313
+ delete resultCopy.content;
314
+ delete resultCopy.references;
233
315
  return resultCopy;
234
316
  });
235
317
  }