Bonsai-Chat-WebGPU / src /lib /web-search.ts
WaveCut's picture
Add agentic artifact tools and live diagnostics
897170b verified
Raw History Blame Contribute Delete
2.87 kB
export interface WebSearchResult {
title: string;
snippet: string;
url: string;
updatedAt?: string;
}
export interface WebSearchResponse {
provider: 'wikipedia';
query: string;
language: 'en' | 'ru';
results: WebSearchResult[];
}
function plainText(value: unknown): string {
return String(value ?? '')
.replace(/<[^>]*>/gu, '')
.replaceAll('&quot;', '"')
.replaceAll('&#039;', "'")
.replaceAll('&amp;', '&')
.replaceAll('&lt;', '<')
.replaceAll('&gt;', '>')
.replace(/\s+/gu, ' ')
.trim()
.slice(0, 1200);
}
export async function searchWikipedia(
query: string,
requestedLanguage?: string,
requestedLimit?: number,
signal?: AbortSignal,
fetchImpl: typeof fetch = fetch,
): Promise<WebSearchResponse> {
const normalizedQuery = query.trim().slice(0, 500);
if (!normalizedQuery) throw new Error('web_search.query must be a non-empty string');
const language: 'en' | 'ru' = requestedLanguage === 'ru'
|| (requestedLanguage !== 'en' && /\p{Script=Cyrillic}/u.test(normalizedQuery))
? 'ru'
: 'en';
const limit = Math.min(8, Math.max(1, Math.round(requestedLimit ?? 5)));
const params = new URLSearchParams({
action: 'query',
list: 'search',
srsearch: normalizedQuery,
srlimit: String(limit),
srprop: 'snippet|timestamp',
format: 'json',
formatversion: '2',
origin: '*',
});
const controller = new AbortController();
const abort = () => controller.abort(signal?.reason);
signal?.addEventListener('abort', abort, { once: true });
const timer = setTimeout(() => controller.abort(new DOMException('web_search timed out', 'TimeoutError')), 8000);
try {
const response = await fetchImpl(`https://${language}.wikipedia.org/w/api.php?${params}`, {
method: 'GET',
credentials: 'omit',
referrerPolicy: 'no-referrer',
signal: controller.signal,
});
if (!response.ok) throw new Error(`web_search provider returned HTTP ${response.status}`);
const payload = await response.json() as {
query?: { search?: Array<{ title?: unknown; snippet?: unknown; timestamp?: unknown }> };
error?: { info?: unknown };
};
if (payload.error) throw new Error(`web_search provider error: ${String(payload.error.info ?? 'unknown error')}`);
const results = (payload.query?.search ?? []).slice(0, limit).map((item) => {
const title = plainText(item.title);
return {
title,
snippet: plainText(item.snippet),
url: `https://${language}.wikipedia.org/wiki/${encodeURIComponent(title.replaceAll(' ', '_'))}`,
...(typeof item.timestamp === 'string' ? { updatedAt: item.timestamp } : {}),
};
});
return { provider: 'wikipedia', query: normalizedQuery, language, results };
} finally {
clearTimeout(timer);
signal?.removeEventListener('abort', abort);
}
}