librechat-data-provider 0.7.82 → 0.7.85

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.
package/src/web.ts ADDED
@@ -0,0 +1,271 @@
1
+ import type {
2
+ ScraperTypes,
3
+ RerankerTypes,
4
+ TCustomConfig,
5
+ SearchProviders,
6
+ TWebSearchConfig,
7
+ } from './config';
8
+ import { extractVariableName } from './utils';
9
+ import { SearchCategories, SafeSearchTypes } from './config';
10
+ import { AuthType } from './schemas';
11
+
12
+ export function loadWebSearchConfig(
13
+ config: TCustomConfig['webSearch'],
14
+ ): TCustomConfig['webSearch'] {
15
+ const serperApiKey = config?.serperApiKey ?? '${SERPER_API_KEY}';
16
+ const firecrawlApiKey = config?.firecrawlApiKey ?? '${FIRECRAWL_API_KEY}';
17
+ const firecrawlApiUrl = config?.firecrawlApiUrl ?? '${FIRECRAWL_API_URL}';
18
+ const jinaApiKey = config?.jinaApiKey ?? '${JINA_API_KEY}';
19
+ const cohereApiKey = config?.cohereApiKey ?? '${COHERE_API_KEY}';
20
+ const safeSearch = config?.safeSearch ?? SafeSearchTypes.MODERATE;
21
+
22
+ return {
23
+ ...config,
24
+ safeSearch,
25
+ jinaApiKey,
26
+ cohereApiKey,
27
+ serperApiKey,
28
+ firecrawlApiKey,
29
+ firecrawlApiUrl,
30
+ };
31
+ }
32
+
33
+ export type TWebSearchKeys =
34
+ | 'serperApiKey'
35
+ | 'firecrawlApiKey'
36
+ | 'firecrawlApiUrl'
37
+ | 'jinaApiKey'
38
+ | 'cohereApiKey';
39
+
40
+ export type TWebSearchCategories =
41
+ | SearchCategories.PROVIDERS
42
+ | SearchCategories.SCRAPERS
43
+ | SearchCategories.RERANKERS;
44
+
45
+ export const webSearchAuth = {
46
+ providers: {
47
+ serper: {
48
+ serperApiKey: 1 as const,
49
+ },
50
+ },
51
+ scrapers: {
52
+ firecrawl: {
53
+ firecrawlApiKey: 1 as const,
54
+ /** Optional (0) */
55
+ firecrawlApiUrl: 0 as const,
56
+ },
57
+ },
58
+ rerankers: {
59
+ jina: { jinaApiKey: 1 as const },
60
+ cohere: { cohereApiKey: 1 as const },
61
+ },
62
+ };
63
+
64
+ /**
65
+ * Extracts all API keys from the webSearchAuth configuration object
66
+ */
67
+ export const webSearchKeys: TWebSearchKeys[] = [];
68
+
69
+ // Iterate through each category (providers, scrapers, rerankers)
70
+ for (const category of Object.keys(webSearchAuth)) {
71
+ const categoryObj = webSearchAuth[category as TWebSearchCategories];
72
+
73
+ // Iterate through each service within the category
74
+ for (const service of Object.keys(categoryObj)) {
75
+ const serviceObj = categoryObj[service as keyof typeof categoryObj];
76
+
77
+ // Extract the API keys from the service
78
+ for (const key of Object.keys(serviceObj)) {
79
+ webSearchKeys.push(key as TWebSearchKeys);
80
+ }
81
+ }
82
+ }
83
+
84
+ export function extractWebSearchEnvVars({
85
+ keys,
86
+ config,
87
+ }: {
88
+ keys: TWebSearchKeys[];
89
+ config: TCustomConfig['webSearch'] | undefined;
90
+ }): string[] {
91
+ if (!config) {
92
+ return [];
93
+ }
94
+
95
+ const authFields: string[] = [];
96
+ const relevantKeys = keys.filter((k) => k in config);
97
+
98
+ for (const key of relevantKeys) {
99
+ const value = config[key];
100
+ if (typeof value === 'string') {
101
+ const varName = extractVariableName(value);
102
+ if (varName) {
103
+ authFields.push(varName);
104
+ }
105
+ }
106
+ }
107
+
108
+ return authFields;
109
+ }
110
+
111
+ /**
112
+ * Type for web search authentication result
113
+ */
114
+ export interface WebSearchAuthResult {
115
+ /** Whether all required categories have at least one authenticated service */
116
+ authenticated: boolean;
117
+ /** Authentication type (user_provided or system_defined) by category */
118
+ authTypes: [TWebSearchCategories, AuthType][];
119
+ /** Original authentication values mapped to their respective keys */
120
+ authResult: Partial<TWebSearchConfig>;
121
+ }
122
+
123
+ /**
124
+ * Loads and verifies web search authentication values
125
+ * @param params - Authentication parameters
126
+ * @returns Authentication result
127
+ */
128
+ export async function loadWebSearchAuth({
129
+ userId,
130
+ webSearchConfig,
131
+ loadAuthValues,
132
+ throwError = true,
133
+ }: {
134
+ userId: string;
135
+ webSearchConfig: TCustomConfig['webSearch'];
136
+ loadAuthValues: (params: {
137
+ userId: string;
138
+ authFields: string[];
139
+ optional?: Set<string>;
140
+ throwError?: boolean;
141
+ }) => Promise<Record<string, string>>;
142
+ throwError?: boolean;
143
+ }): Promise<WebSearchAuthResult> {
144
+ let authenticated = true;
145
+ const authResult: Partial<TWebSearchConfig> = {};
146
+
147
+ /** Type-safe iterator for the category-service combinations */
148
+ async function checkAuth<C extends TWebSearchCategories>(
149
+ category: C,
150
+ ): Promise<[boolean, boolean]> {
151
+ type ServiceType = keyof (typeof webSearchAuth)[C];
152
+ let isUserProvided = false;
153
+
154
+ // Check if a specific service is specified in the config
155
+ let specificService: ServiceType | undefined;
156
+ if (category === SearchCategories.PROVIDERS && webSearchConfig?.searchProvider) {
157
+ specificService = webSearchConfig.searchProvider as unknown as ServiceType;
158
+ } else if (category === SearchCategories.SCRAPERS && webSearchConfig?.scraperType) {
159
+ specificService = webSearchConfig.scraperType as unknown as ServiceType;
160
+ } else if (category === SearchCategories.RERANKERS && webSearchConfig?.rerankerType) {
161
+ specificService = webSearchConfig.rerankerType as unknown as ServiceType;
162
+ }
163
+
164
+ // If a specific service is specified, only check that one
165
+ const services = specificService
166
+ ? [specificService]
167
+ : (Object.keys(webSearchAuth[category]) as ServiceType[]);
168
+
169
+ for (const service of services) {
170
+ // Skip if the service doesn't exist in the webSearchAuth config
171
+ if (!webSearchAuth[category][service]) {
172
+ continue;
173
+ }
174
+
175
+ const serviceConfig = webSearchAuth[category][service];
176
+
177
+ // Split keys into required and optional
178
+ const requiredKeys: TWebSearchKeys[] = [];
179
+ const optionalKeys: TWebSearchKeys[] = [];
180
+
181
+ for (const key in serviceConfig) {
182
+ const typedKey = key as TWebSearchKeys;
183
+ if (serviceConfig[typedKey as keyof typeof serviceConfig] === 1) {
184
+ requiredKeys.push(typedKey);
185
+ } else if (serviceConfig[typedKey as keyof typeof serviceConfig] === 0) {
186
+ optionalKeys.push(typedKey);
187
+ }
188
+ }
189
+
190
+ if (requiredKeys.length === 0) continue;
191
+
192
+ const requiredAuthFields = extractWebSearchEnvVars({
193
+ keys: requiredKeys,
194
+ config: webSearchConfig,
195
+ });
196
+ const optionalAuthFields = extractWebSearchEnvVars({
197
+ keys: optionalKeys,
198
+ config: webSearchConfig,
199
+ });
200
+ if (requiredAuthFields.length !== requiredKeys.length) continue;
201
+
202
+ const allKeys = [...requiredKeys, ...optionalKeys];
203
+ const allAuthFields = [...requiredAuthFields, ...optionalAuthFields];
204
+ const optionalSet = new Set(optionalAuthFields);
205
+
206
+ try {
207
+ const authValues = await loadAuthValues({
208
+ userId,
209
+ authFields: allAuthFields,
210
+ optional: optionalSet,
211
+ throwError,
212
+ });
213
+
214
+ let allFieldsAuthenticated = true;
215
+ for (let j = 0; j < allAuthFields.length; j++) {
216
+ const field = allAuthFields[j];
217
+ const value = authValues[field];
218
+ const originalKey = allKeys[j];
219
+ if (originalKey) authResult[originalKey] = value;
220
+ if (!optionalSet.has(field) && !value) {
221
+ allFieldsAuthenticated = false;
222
+ break;
223
+ }
224
+ if (!isUserProvided && process.env[field] !== value) {
225
+ isUserProvided = true;
226
+ }
227
+ }
228
+
229
+ if (!allFieldsAuthenticated) {
230
+ continue;
231
+ }
232
+ if (category === SearchCategories.PROVIDERS) {
233
+ authResult.searchProvider = service as SearchProviders;
234
+ } else if (category === SearchCategories.SCRAPERS) {
235
+ authResult.scraperType = service as ScraperTypes;
236
+ } else if (category === SearchCategories.RERANKERS) {
237
+ authResult.rerankerType = service as RerankerTypes;
238
+ }
239
+ return [true, isUserProvided];
240
+ } catch {
241
+ continue;
242
+ }
243
+ }
244
+ return [false, isUserProvided];
245
+ }
246
+
247
+ const categories = [
248
+ SearchCategories.PROVIDERS,
249
+ SearchCategories.SCRAPERS,
250
+ SearchCategories.RERANKERS,
251
+ ] as const;
252
+ const authTypes: [TWebSearchCategories, AuthType][] = [];
253
+ for (const category of categories) {
254
+ const [isCategoryAuthenticated, isUserProvided] = await checkAuth(category);
255
+ if (!isCategoryAuthenticated) {
256
+ authenticated = false;
257
+ authTypes.push([category, AuthType.USER_PROVIDED]);
258
+ continue;
259
+ }
260
+ authTypes.push([category, isUserProvided ? AuthType.USER_PROVIDED : AuthType.SYSTEM_DEFINED]);
261
+ }
262
+
263
+ authResult.safeSearch = webSearchConfig?.safeSearch ?? SafeSearchTypes.MODERATE;
264
+ authResult.scraperTimeout = webSearchConfig?.scraperTimeout ?? 7500;
265
+
266
+ return {
267
+ authTypes,
268
+ authResult,
269
+ authenticated,
270
+ };
271
+ }