@librechat/agents 2.4.318 → 2.4.320

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 (50) 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/search.cjs +18 -8
  12. package/dist/cjs/tools/search/search.cjs.map +1 -1
  13. package/dist/cjs/tools/search/tool.cjs +85 -61
  14. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  15. package/dist/cjs/tools/search/utils.cjs +3 -1
  16. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  17. package/dist/esm/events.mjs +1 -1
  18. package/dist/esm/events.mjs.map +1 -1
  19. package/dist/esm/main.mjs +3 -1
  20. package/dist/esm/main.mjs.map +1 -1
  21. package/dist/esm/messages/ids.mjs +21 -0
  22. package/dist/esm/messages/ids.mjs.map +1 -0
  23. package/dist/esm/stream.mjs +7 -152
  24. package/dist/esm/stream.mjs.map +1 -1
  25. package/dist/esm/tools/handlers.mjs +141 -0
  26. package/dist/esm/tools/handlers.mjs.map +1 -0
  27. package/dist/esm/tools/search/search.mjs +18 -8
  28. package/dist/esm/tools/search/search.mjs.map +1 -1
  29. package/dist/esm/tools/search/tool.mjs +84 -60
  30. package/dist/esm/tools/search/tool.mjs.map +1 -1
  31. package/dist/esm/tools/search/utils.mjs +3 -1
  32. package/dist/esm/tools/search/utils.mjs.map +1 -1
  33. package/dist/types/index.d.ts +1 -0
  34. package/dist/types/messages/ids.d.ts +3 -0
  35. package/dist/types/messages/index.d.ts +1 -0
  36. package/dist/types/stream.d.ts +0 -8
  37. package/dist/types/tools/handlers.d.ts +8 -0
  38. package/dist/types/tools/search/search.d.ts +1 -1
  39. package/dist/types/tools/search/types.d.ts +28 -8
  40. package/package.json +1 -1
  41. package/src/events.ts +49 -15
  42. package/src/index.ts +1 -0
  43. package/src/messages/ids.ts +26 -0
  44. package/src/messages/index.ts +1 -0
  45. package/src/stream.ts +4 -186
  46. package/src/tools/handlers.ts +167 -0
  47. package/src/tools/search/search.ts +27 -15
  48. package/src/tools/search/tool.ts +137 -91
  49. package/src/tools/search/types.ts +37 -10
  50. package/src/tools/search/utils.ts +5 -1
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable no-console */
2
2
  import { z } from 'zod';
3
3
  import { tool, DynamicStructuredTool } from '@langchain/core/tools';
4
+ import type { RunnableConfig } from '@langchain/core/runnables';
4
5
  import type * as t from './types';
5
6
  import { createSearchAPI, createSourceProcessor } from './search';
6
7
  import { createFirecrawlScraper } from './firecrawl';
@@ -38,6 +39,129 @@ Examples:
38
39
  - "in" for India
39
40
  `.trim();
40
41
 
42
+ function createSearchProcessor({
43
+ searchAPI,
44
+ sourceProcessor,
45
+ onGetHighlights,
46
+ }: {
47
+ searchAPI: ReturnType<typeof createSearchAPI>;
48
+ sourceProcessor: ReturnType<typeof createSourceProcessor>;
49
+ onGetHighlights: t.SearchToolConfig['onGetHighlights'];
50
+ }) {
51
+ return async function ({
52
+ query,
53
+ country,
54
+ proMode = true,
55
+ maxSources = 5,
56
+ onSearchResults,
57
+ }: {
58
+ query: string;
59
+ country?: string;
60
+ maxSources?: number;
61
+ proMode?: boolean;
62
+ onSearchResults: t.SearchToolConfig['onSearchResults'];
63
+ }): Promise<t.SearchResultData> {
64
+ try {
65
+ const result = await searchAPI.getSources({ query, country });
66
+ onSearchResults?.(result);
67
+
68
+ if (!result.success) {
69
+ throw new Error(result.error ?? 'Search failed');
70
+ }
71
+
72
+ const processedSources = await sourceProcessor.processSources({
73
+ query,
74
+ result,
75
+ proMode,
76
+ onGetHighlights,
77
+ numElements: maxSources,
78
+ });
79
+ return expandHighlights(processedSources);
80
+ } catch (error) {
81
+ console.error('Error in search:', error);
82
+ return {
83
+ organic: [],
84
+ topStories: [],
85
+ images: [],
86
+ relatedSearches: [],
87
+ error: error instanceof Error ? error.message : String(error),
88
+ };
89
+ }
90
+ };
91
+ }
92
+
93
+ function createOnSearchResults({
94
+ runnableConfig,
95
+ onSearchResults,
96
+ }: {
97
+ runnableConfig: RunnableConfig;
98
+ onSearchResults: t.SearchToolConfig['onSearchResults'];
99
+ }) {
100
+ return function (results: t.SearchResult): void {
101
+ if (!onSearchResults) {
102
+ return;
103
+ }
104
+ onSearchResults(results, runnableConfig);
105
+ };
106
+ }
107
+
108
+ function createTool({
109
+ schema,
110
+ search,
111
+ onSearchResults: _onSearchResults,
112
+ }: {
113
+ schema: t.SearchToolSchema;
114
+ search: ReturnType<typeof createSearchProcessor>;
115
+ onSearchResults: t.SearchToolConfig['onSearchResults'];
116
+ }): DynamicStructuredTool<typeof schema> {
117
+ return tool<typeof schema>(
118
+ async (params, runnableConfig) => {
119
+ const { query, country: _c } = params;
120
+ const country = typeof _c === 'string' && _c ? _c : undefined;
121
+ const searchResult = await search({
122
+ query,
123
+ country,
124
+ onSearchResults: createOnSearchResults({
125
+ runnableConfig,
126
+ onSearchResults: _onSearchResults,
127
+ }),
128
+ });
129
+ const turn = runnableConfig.toolCall?.turn ?? 0;
130
+ const { output, references } = formatResultsForLLM(turn, searchResult);
131
+ const data: t.SearchResultData = { turn, ...searchResult, references };
132
+ return [output, { [Constants.WEB_SEARCH]: data }];
133
+ },
134
+ {
135
+ name: Constants.WEB_SEARCH,
136
+ description: `Real-time search. Results have required citation anchors.
137
+
138
+ Note: Use ONCE per reply unless instructed otherwise.
139
+
140
+ Anchors:
141
+ - \\ue202turnXtypeY
142
+ - X = turn idx, type = 'search' | 'news' | 'image' | 'ref', Y = item idx
143
+
144
+ Special Markers:
145
+ - \\ue203...\\ue204 — highlight start/end of cited text (for Standalone or Group citations)
146
+ - \\ue200...\\ue201 — group block (e.g. \\ue200\\ue202turn0search1\\ue202turn0news2\\ue201)
147
+
148
+ **CITE EVERY NON-OBVIOUS FACT/QUOTE:**
149
+ Use anchor marker(s) immediately after the statement:
150
+ - Standalone: "Pure functions produce same output. \\ue202turn0search0"
151
+ - Standalone (multiple): "Today's News \\ue202turn0search0\\ue202turn0news0"
152
+ - Highlight: "\\ue203Highlight text.\\ue204\\ue202turn0news1"
153
+ - Group: "Sources. \\ue200\\ue202turn0search0\\ue202turn0news1\\ue201"
154
+ - Group Highlight: "\\ue203Highlight for group.\\ue204 \\ue200\\ue202turn0search0\\ue202turn0news1\\ue201"
155
+ - Image: "See photo \\ue202turn0image0."
156
+
157
+ **NEVER use markdown links, [1], or footnotes. CITE ONLY with anchors provided.**
158
+ `.trim(),
159
+ schema: schema,
160
+ responseFormat: Constants.CONTENT_AND_ARTIFACT,
161
+ }
162
+ );
163
+ }
164
+
41
165
  /**
42
166
  * Creates a search tool with a schema that dynamically includes the country field
43
167
  * only when the searchProvider is 'serper'.
@@ -47,7 +171,7 @@ Examples:
47
171
  */
48
172
  export const createSearchTool = (
49
173
  config: t.SearchToolConfig = {}
50
- ): DynamicStructuredTool<typeof SearchToolSchema> => {
174
+ ): DynamicStructuredTool<typeof toolSchema> => {
51
175
  const {
52
176
  searchProvider = 'serper',
53
177
  serperApiKey,
@@ -63,6 +187,7 @@ export const createSearchTool = (
63
187
  jinaApiKey,
64
188
  cohereApiKey,
65
189
  onSearchResults: _onSearchResults,
190
+ onGetHighlights,
66
191
  } = config;
67
192
 
68
193
  const querySchema = z.string().describe(DEFAULT_QUERY_DESCRIPTION);
@@ -80,7 +205,7 @@ export const createSearchTool = (
80
205
  .describe(DEFAULT_COUNTRY_DESCRIPTION);
81
206
  }
82
207
 
83
- const SearchToolSchema = z.object(schemaObject);
208
+ const toolSchema = z.object(schemaObject);
84
209
 
85
210
  const searchAPI = createSearchAPI({
86
211
  searchProvider,
@@ -115,94 +240,15 @@ export const createSearchTool = (
115
240
  firecrawlScraper
116
241
  );
117
242
 
118
- const search = async ({
119
- query,
120
- country,
121
- proMode = true,
122
- maxSources = 5,
123
- onSearchResults,
124
- }: {
125
- query: string;
126
- country?: string;
127
- maxSources?: number;
128
- proMode?: boolean;
129
- onSearchResults?: (sources: t.SearchResult) => void;
130
- }): Promise<t.SearchResultData> => {
131
- try {
132
- const sources = await searchAPI.getSources({ query, country });
133
- onSearchResults?.(sources);
134
-
135
- if (!sources.success) {
136
- throw new Error(sources.error ?? 'Search failed');
137
- }
138
-
139
- const processedSources = await sourceProcessor.processSources(
140
- sources,
141
- maxSources,
142
- query,
143
- proMode
144
- );
145
- return expandHighlights(processedSources);
146
- } catch (error) {
147
- console.error('Error in search:', error);
148
- return {
149
- organic: [],
150
- topStories: [],
151
- images: [],
152
- relatedSearches: [],
153
- error: error instanceof Error ? error.message : String(error),
154
- };
155
- }
156
- };
157
-
158
- return tool<typeof SearchToolSchema>(
159
- async (params, runnableConfig) => {
160
- const { query, country: _c } = params;
161
- const country = typeof _c === 'string' && _c ? _c : undefined;
162
- const searchResult = await search({
163
- query,
164
- country,
165
- onSearchResults: _onSearchResults
166
- ? (result): void => {
167
- _onSearchResults(result, runnableConfig);
168
- }
169
- : undefined,
170
- });
171
- const turn = runnableConfig.toolCall?.turn ?? 0;
172
- const { output, references } = formatResultsForLLM(turn, searchResult);
173
- return [
174
- output,
175
- { [Constants.WEB_SEARCH]: { turn, ...searchResult, references } },
176
- ];
177
- },
178
- {
179
- name: Constants.WEB_SEARCH,
180
- description: `
181
- Real-time search. Results have required citation anchors.
182
-
183
- Note: Use ONCE per reply unless instructed otherwise.
184
-
185
- Anchors:
186
- - \\ue202turnXtypeY
187
- - X = turn idx, type = 'search' | 'news' | 'image' | 'ref', Y = item idx
188
-
189
- Special Markers:
190
- - \\ue203...\\ue204 — highlight start/end of cited text (for Standalone or Group citations)
191
- - \\ue200...\\ue201 — group block (e.g. \\ue200\\ue202turn0search1\\ue202turn0news2\\ue201)
192
-
193
- **CITE EVERY NON-OBVIOUS FACT/QUOTE:**
194
- Use anchor marker(s) immediately after the statement:
195
- - Standalone: "Pure functions produce same output. \\ue202turn0search0"
196
- - Standalone (multiple): "Today's News \\ue202turn0search0\\ue202turn0news0"
197
- - Highlight: "\\ue203Highlight text.\\ue204\\ue202turn0news1"
198
- - Group: "Sources. \\ue200\\ue202turn0search0\\ue202turn0news1\\ue201"
199
- - Group Highlight: "\\ue203Highlight for group.\\ue204 \\ue200\\ue202turn0search0\\ue202turn0news1\\ue201"
200
- - Image: "See photo \\ue202turn0image0."
243
+ const search = createSearchProcessor({
244
+ searchAPI,
245
+ sourceProcessor,
246
+ onGetHighlights,
247
+ });
201
248
 
202
- **NEVER use markdown links, [1], or footnotes. CITE ONLY with anchors provided.**
203
- `.trim(),
204
- schema: SearchToolSchema,
205
- responseFormat: Constants.CONTENT_AND_ARTIFACT,
206
- }
207
- );
249
+ return createTool({
250
+ search,
251
+ schema: toolSchema,
252
+ onSearchResults: _onSearchResults,
253
+ });
208
254
  };
@@ -1,3 +1,4 @@
1
+ import { z } from 'zod';
1
2
  import type { RunnableConfig } from '@langchain/core/runnables';
2
3
  import type { BaseReranker } from './rerankers';
3
4
 
@@ -27,6 +28,7 @@ export type ResultReference = {
27
28
  attribution?: string;
28
29
  };
29
30
  export interface SearchResultData {
31
+ turn?: number;
30
32
  organic?: ProcessedOrganic[];
31
33
  topStories?: ProcessedTopStory[];
32
34
  images?: ImageResult[];
@@ -98,15 +100,6 @@ export interface ScraperExtractionResult {
98
100
  no_extraction: ScraperContentResult;
99
101
  }
100
102
 
101
- // Define type for SearXNG result
102
- export interface SearXNGResult {
103
- title?: string;
104
- url?: string;
105
- content?: string;
106
- publishedDate?: string;
107
- img_src?: string;
108
- }
109
-
110
103
  export interface JinaRerankerResult {
111
104
  index: number;
112
105
  relevance_score: number;
@@ -151,6 +144,7 @@ export interface SearchToolConfig
151
144
  results: SearchResult,
152
145
  runnableConfig?: RunnableConfig
153
146
  ) => void;
147
+ onGetHighlights?: (link: string) => void;
154
148
  }
155
149
  export interface MediaReference {
156
150
  originalUrl: string;
@@ -213,7 +207,7 @@ export interface ScrapeMetadata {
213
207
  publishedTime?: string;
214
208
  modifiedTime?: string;
215
209
  // Twitter metadata
216
- 'twitter:site'?: string;
210
+ 'twitter:site'?: string | boolean | number | null;
217
211
  'twitter:creator'?: string;
218
212
  'twitter:card'?: string;
219
213
  'twitter:image'?: string;
@@ -555,3 +549,36 @@ export interface SearxNGSearchPayload {
555
549
  */
556
550
  disabled_engines?: string;
557
551
  }
552
+
553
+ export interface SearXNGResult {
554
+ title?: string;
555
+ url?: string;
556
+ content?: string;
557
+ publishedDate?: string;
558
+ img_src?: string;
559
+ }
560
+
561
+ export type ProcessSourcesFields = {
562
+ result: SearchResult;
563
+ numElements: number;
564
+ query: string;
565
+ proMode: boolean;
566
+ onGetHighlights: SearchToolConfig['onGetHighlights'];
567
+ };
568
+
569
+ export type SearchToolSchema = z.ZodObject<
570
+ {
571
+ query: z.ZodString;
572
+ country?: z.ZodOptional<z.ZodString>;
573
+ },
574
+ 'strip',
575
+ z.ZodTypeAny,
576
+ {
577
+ query: string;
578
+ country?: unknown;
579
+ },
580
+ {
581
+ query: string;
582
+ country?: unknown;
583
+ }
584
+ >;
@@ -25,11 +25,15 @@ export function getAttribution(
25
25
  ): string | undefined {
26
26
  if (!metadata) return getDomainName(link, metadata);
27
27
 
28
+ const twitterSite = metadata['twitter:site'];
29
+ const twitterSiteFormatted =
30
+ typeof twitterSite === 'string' ? twitterSite.replace(/^@/, '') : undefined;
31
+
28
32
  const possibleAttributions = [
29
33
  metadata.ogSiteName,
30
34
  metadata['og:site_name'],
31
35
  metadata.title?.split('|').pop()?.trim(),
32
- metadata['twitter:site']?.replace(/^@/, ''),
36
+ twitterSiteFormatted,
33
37
  ];
34
38
 
35
39
  const attribution = possibleAttributions.find(