| | import { |
| | AuthType, |
| | SafeSearchTypes, |
| | SearchCategories, |
| | extractVariableName, |
| | } from 'librechat-data-provider'; |
| | import { webSearchAuth } from '@librechat/data-schemas'; |
| | import type { |
| | RerankerTypes, |
| | TCustomConfig, |
| | SearchProviders, |
| | ScraperProviders, |
| | TWebSearchConfig, |
| | } from 'librechat-data-provider'; |
| | import type { TWebSearchKeys, TWebSearchCategories } from '@librechat/data-schemas'; |
| |
|
| | export function extractWebSearchEnvVars({ |
| | keys, |
| | config, |
| | }: { |
| | keys: TWebSearchKeys[]; |
| | config: TCustomConfig['webSearch'] | undefined; |
| | }): string[] { |
| | if (!config) { |
| | return []; |
| | } |
| |
|
| | const authFields: string[] = []; |
| | const relevantKeys = keys.filter((k) => k in config); |
| |
|
| | for (const key of relevantKeys) { |
| | const value = config[key]; |
| | if (typeof value === 'string') { |
| | const varName = extractVariableName(value); |
| | if (varName) { |
| | authFields.push(varName); |
| | } |
| | } |
| | } |
| |
|
| | return authFields; |
| | } |
| |
|
| | |
| | |
| | |
| | export interface WebSearchAuthResult { |
| | |
| | authenticated: boolean; |
| | |
| | authTypes: [TWebSearchCategories, AuthType][]; |
| | |
| | authResult: Partial<TWebSearchConfig>; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export async function loadWebSearchAuth({ |
| | userId, |
| | webSearchConfig, |
| | loadAuthValues, |
| | throwError = true, |
| | }: { |
| | userId: string; |
| | webSearchConfig: TCustomConfig['webSearch']; |
| | loadAuthValues: (params: { |
| | userId: string; |
| | authFields: string[]; |
| | optional?: Set<string>; |
| | throwError?: boolean; |
| | }) => Promise<Record<string, string>>; |
| | throwError?: boolean; |
| | }): Promise<WebSearchAuthResult> { |
| | let authenticated = true; |
| | const authResult: Partial<TWebSearchConfig> = {}; |
| |
|
| | |
| | async function checkAuth<C extends TWebSearchCategories>( |
| | category: C, |
| | ): Promise<[boolean, boolean]> { |
| | type ServiceType = keyof (typeof webSearchAuth)[C]; |
| | let isUserProvided = false; |
| |
|
| | |
| | let specificService: ServiceType | undefined; |
| | if (category === SearchCategories.PROVIDERS && webSearchConfig?.searchProvider) { |
| | specificService = webSearchConfig.searchProvider as unknown as ServiceType; |
| | } else if (category === SearchCategories.SCRAPERS && webSearchConfig?.scraperProvider) { |
| | specificService = webSearchConfig.scraperProvider as unknown as ServiceType; |
| | } else if (category === SearchCategories.RERANKERS && webSearchConfig?.rerankerType) { |
| | specificService = webSearchConfig.rerankerType as unknown as ServiceType; |
| | } |
| |
|
| | |
| | const services = specificService |
| | ? [specificService] |
| | : (Object.keys(webSearchAuth[category]) as ServiceType[]); |
| |
|
| | for (const service of services) { |
| | |
| | if (!webSearchAuth[category][service]) { |
| | continue; |
| | } |
| |
|
| | const serviceConfig = webSearchAuth[category][service]; |
| |
|
| | |
| | const requiredKeys: TWebSearchKeys[] = []; |
| | const optionalKeys: TWebSearchKeys[] = []; |
| |
|
| | for (const key in serviceConfig) { |
| | const typedKey = key as TWebSearchKeys; |
| | if (serviceConfig[typedKey as keyof typeof serviceConfig] === 1) { |
| | requiredKeys.push(typedKey); |
| | } else if (serviceConfig[typedKey as keyof typeof serviceConfig] === 0) { |
| | optionalKeys.push(typedKey); |
| | } |
| | } |
| |
|
| | if (requiredKeys.length === 0) continue; |
| |
|
| | const requiredAuthFields = extractWebSearchEnvVars({ |
| | keys: requiredKeys, |
| | config: webSearchConfig, |
| | }); |
| | const optionalAuthFields = extractWebSearchEnvVars({ |
| | keys: optionalKeys, |
| | config: webSearchConfig, |
| | }); |
| | if (requiredAuthFields.length !== requiredKeys.length) continue; |
| |
|
| | const allKeys = [...requiredKeys, ...optionalKeys]; |
| | const allAuthFields = [...requiredAuthFields, ...optionalAuthFields]; |
| | const optionalSet = new Set(optionalAuthFields); |
| |
|
| | try { |
| | const authValues = await loadAuthValues({ |
| | userId, |
| | authFields: allAuthFields, |
| | optional: optionalSet, |
| | throwError, |
| | }); |
| |
|
| | let allFieldsAuthenticated = true; |
| | for (let j = 0; j < allAuthFields.length; j++) { |
| | const field = allAuthFields[j]; |
| | const value = authValues[field]; |
| | const originalKey = allKeys[j]; |
| | if (originalKey) authResult[originalKey] = value; |
| | if (!optionalSet.has(field) && !value) { |
| | allFieldsAuthenticated = false; |
| | break; |
| | } |
| | if (!isUserProvided && process.env[field] !== value) { |
| | isUserProvided = true; |
| | } |
| | } |
| |
|
| | if (!allFieldsAuthenticated) { |
| | continue; |
| | } |
| | if (category === SearchCategories.PROVIDERS) { |
| | authResult.searchProvider = service as SearchProviders; |
| | } else if (category === SearchCategories.SCRAPERS) { |
| | authResult.scraperProvider = service as ScraperProviders; |
| | } else if (category === SearchCategories.RERANKERS) { |
| | authResult.rerankerType = service as RerankerTypes; |
| | } |
| | return [true, isUserProvided]; |
| | } catch { |
| | continue; |
| | } |
| | } |
| | return [false, isUserProvided]; |
| | } |
| |
|
| | const categories = [ |
| | SearchCategories.PROVIDERS, |
| | SearchCategories.SCRAPERS, |
| | SearchCategories.RERANKERS, |
| | ] as const; |
| | const authTypes: [TWebSearchCategories, AuthType][] = []; |
| | for (const category of categories) { |
| | const [isCategoryAuthenticated, isUserProvided] = await checkAuth(category); |
| | if (!isCategoryAuthenticated) { |
| | authenticated = false; |
| | authTypes.push([category, AuthType.USER_PROVIDED]); |
| | continue; |
| | } |
| | authTypes.push([category, isUserProvided ? AuthType.USER_PROVIDED : AuthType.SYSTEM_DEFINED]); |
| | } |
| |
|
| | authResult.safeSearch = webSearchConfig?.safeSearch ?? SafeSearchTypes.MODERATE; |
| | authResult.scraperTimeout = |
| | webSearchConfig?.scraperTimeout ?? webSearchConfig?.firecrawlOptions?.timeout ?? 7500; |
| | authResult.firecrawlOptions = webSearchConfig?.firecrawlOptions; |
| |
|
| | return { |
| | authTypes, |
| | authResult, |
| | authenticated, |
| | }; |
| | } |
| |
|