import { HttpError, errorMessage, isAbortError } from "../shared/errors"; import { fetchJson } from "../shared/http"; import { TAVILY_EXTRACT_URL, TAVILY_SEARCH_URL, type ExtractResponse, type SearchResult } from "../types"; export function mapTavilyError(error: unknown): string { if (error instanceof HttpError) { if (error.status === 401) return "Invalid Tavily API key."; if (error.status === 429) return "Tavily rate limit exceeded. Try again later."; return `Tavily API error ${error.status}: ${error.body}`; } return `Fetch error: ${errorMessage(error)}`; } export async function extractWithTavily(urls: string[], format: "markdown" | "text", apiKey: string, signal?: AbortSignal): Promise { return await fetchJson(TAVILY_EXTRACT_URL, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify({ urls, format }), signal, }); } export async function searchWithTavily(query: string, apiKey: string, count: number, signal?: AbortSignal): Promise<{ results: SearchResult[]; error?: string }> { try { const data = await fetchJson(TAVILY_SEARCH_URL, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify({ query, search_depth: "advanced", max_results: count, include_answer: false, include_images: false, include_raw_content: false, }), signal, }); return { results: (data.results ?? []).map((result: any) => ({ title: result.title ?? "", url: result.url ?? "", snippet: result.content ?? "", source: "tavily" as const, })).slice(0, count), }; } catch (error) { if (isAbortError(error)) throw error; return { results: [], error: mapTavilyError(error) }; } }