Files
wehub-resource-sync c48612c494
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
Docs Build / Build docs site (push) Has been cancelled
Publish @openmaic packages / Build, validate & publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

110 lines
3.4 KiB
TypeScript

import {
resolveServerWebSearchProviderId,
resolveWebSearchApiKey,
resolveWebSearchBaseUrl,
} from '@/lib/server/provider-config';
import { WEB_SEARCH_PROVIDERS } from '@/lib/web-search/constants';
import type { BaiduSubSources, WebSearchProviderId } from '@/lib/web-search/types';
const OFFICIAL_CLIENT_BASE_URLS: Record<WebSearchProviderId, string[]> = {
tavily: ['https://api.tavily.com', 'https://api.tavily.com/search'],
bocha: [
'https://api.bocha.cn',
'https://api.bocha.cn/v1',
'https://api.bocha.cn/v1/web-search',
'https://api.bochaai.com',
'https://api.bochaai.com/v1',
'https://api.bochaai.com/v1/web-search',
],
brave: [
'https://search.brave.com',
'https://search.brave.com/search',
'https://api.search.brave.com',
],
baidu: ['https://qianfan.baidubce.com'],
minimax: [
'https://api.minimaxi.com',
'https://api.minimaxi.com/v1',
'https://api.minimaxi.com/v1/coding_plan',
'https://api.minimaxi.com/v1/coding_plan/search',
'https://api.minimax.io',
'https://api.minimax.io/v1',
'https://api.minimax.io/v1/coding_plan',
'https://api.minimax.io/v1/coding_plan/search',
],
doubao: ['https://open.feedcoopapi.com', 'https://open.feedcoopapi.com/search_api/web_search'],
};
function normalizeBaseUrl(value: string): string {
return value.replace(/\/+$/, '');
}
function assertWebSearchProviderId(
providerId: string | undefined,
): providerId is WebSearchProviderId {
return !!providerId && providerId in WEB_SEARCH_PROVIDERS;
}
export function resolveSafeClientWebSearchBaseUrl(
providerId: WebSearchProviderId,
clientBaseUrl?: string,
): string | undefined {
const trimmed = clientBaseUrl?.trim();
if (!trimmed) return undefined;
let normalized: string;
try {
const parsed = new URL(trimmed);
normalized = normalizeBaseUrl(parsed.toString());
} catch {
throw new Error(`Unsupported ${WEB_SEARCH_PROVIDERS[providerId].name} base URL`);
}
const allowed = OFFICIAL_CLIENT_BASE_URLS[providerId].map(normalizeBaseUrl);
if (!allowed.includes(normalized)) {
throw new Error(`Unsupported ${WEB_SEARCH_PROVIDERS[providerId].name} base URL`);
}
return normalized;
}
export function resolveWebSearchRouteBaseUrl(
providerId: WebSearchProviderId,
clientBaseUrl?: string,
): string | undefined {
const safeClientBaseUrl = resolveSafeClientWebSearchBaseUrl(providerId, clientBaseUrl);
return resolveWebSearchBaseUrl(providerId, safeClientBaseUrl);
}
export function resolveClassroomWebSearchConfig(input: {
webSearchProviderId?: WebSearchProviderId;
webSearchApiKey?: string;
baiduSubSources?: BaiduSubSources;
}):
| {
providerId: WebSearchProviderId;
apiKey: string;
baseUrl?: string;
baiduSubSources?: BaiduSubSources;
}
| undefined {
const requestedProviderId = assertWebSearchProviderId(input.webSearchProviderId)
? input.webSearchProviderId
: undefined;
const providerId =
requestedProviderId ?? (resolveServerWebSearchProviderId() as WebSearchProviderId | undefined);
if (!providerId) return undefined;
const provider = WEB_SEARCH_PROVIDERS[providerId];
const apiKey = resolveWebSearchApiKey(providerId, input.webSearchApiKey);
if (provider.requiresApiKey && !apiKey) return undefined;
return {
providerId,
apiKey,
baseUrl: resolveWebSearchBaseUrl(providerId),
...(providerId === 'baidu' && input.baiduSubSources
? { baiduSubSources: input.baiduSubSources }
: {}),
};
}