@librechat/agents 2.4.319 → 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.
@@ -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,92 +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
- const data: t.SearchResultData = { turn, ...searchResult, references };
174
- return [output, { [Constants.WEB_SEARCH]: data }];
175
- },
176
- {
177
- name: Constants.WEB_SEARCH,
178
- description: `
179
- Real-time search. Results have required citation anchors.
180
-
181
- Note: Use ONCE per reply unless instructed otherwise.
182
-
183
- Anchors:
184
- - \\ue202turnXtypeY
185
- - X = turn idx, type = 'search' | 'news' | 'image' | 'ref', Y = item idx
186
-
187
- Special Markers:
188
- - \\ue203...\\ue204 — highlight start/end of cited text (for Standalone or Group citations)
189
- - \\ue200...\\ue201 — group block (e.g. \\ue200\\ue202turn0search1\\ue202turn0news2\\ue201)
190
-
191
- **CITE EVERY NON-OBVIOUS FACT/QUOTE:**
192
- Use anchor marker(s) immediately after the statement:
193
- - Standalone: "Pure functions produce same output. \\ue202turn0search0"
194
- - Standalone (multiple): "Today's News \\ue202turn0search0\\ue202turn0news0"
195
- - Highlight: "\\ue203Highlight text.\\ue204\\ue202turn0news1"
196
- - Group: "Sources. \\ue200\\ue202turn0search0\\ue202turn0news1\\ue201"
197
- - Group Highlight: "\\ue203Highlight for group.\\ue204 \\ue200\\ue202turn0search0\\ue202turn0news1\\ue201"
198
- - Image: "See photo \\ue202turn0image0."
243
+ const search = createSearchProcessor({
244
+ searchAPI,
245
+ sourceProcessor,
246
+ onGetHighlights,
247
+ });
199
248
 
200
- **NEVER use markdown links, [1], or footnotes. CITE ONLY with anchors provided.**
201
- `.trim(),
202
- schema: SearchToolSchema,
203
- responseFormat: Constants.CONTENT_AND_ARTIFACT,
204
- }
205
- );
249
+ return createTool({
250
+ search,
251
+ schema: toolSchema,
252
+ onSearchResults: _onSearchResults,
253
+ });
206
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
 
@@ -143,6 +144,7 @@ export interface SearchToolConfig
143
144
  results: SearchResult,
144
145
  runnableConfig?: RunnableConfig
145
146
  ) => void;
147
+ onGetHighlights?: (link: string) => void;
146
148
  }
147
149
  export interface MediaReference {
148
150
  originalUrl: string;
@@ -205,7 +207,7 @@ export interface ScrapeMetadata {
205
207
  publishedTime?: string;
206
208
  modifiedTime?: string;
207
209
  // Twitter metadata
208
- 'twitter:site'?: string;
210
+ 'twitter:site'?: string | boolean | number | null;
209
211
  'twitter:creator'?: string;
210
212
  'twitter:card'?: string;
211
213
  'twitter:image'?: string;
@@ -555,3 +557,28 @@ export interface SearXNGResult {
555
557
  publishedDate?: string;
556
558
  img_src?: string;
557
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(