provider-brave.ts
1 import process from 'node:process' 2 3 import { resolveTimeoutSeconds, safeFetch } from '@/lib/web-core' 4 import type { 5 WebSearchRequest, 6 WebSearchResult, 7 } from '@/server/tools/web-search/types' 8 import { DEFAULT_TIMEOUT_SECONDS } from '@/server/tools/web-search/types' 9 10 export async function performBraveSearch( 11 request: WebSearchRequest, 12 ): Promise<WebSearchResult[]> { 13 const apiKey = process.env.BRAVE_API_KEY 14 if (!apiKey) { 15 throw new Error('BRAVE_API_KEY environment variable is not set') 16 } 17 18 const url = new URL('https://api.search.brave.com/res/v1/web/search') 19 url.searchParams.set('q', request.query) 20 url.searchParams.set('count', String(request.maxResults ?? 10)) 21 22 if (request.country && request.country !== 'US') { 23 url.searchParams.set('country', request.country) 24 } 25 26 if (request.freshness) { 27 url.searchParams.set('freshness', request.freshness) 28 } 29 30 const timeoutMs = 31 resolveTimeoutSeconds(request.timeoutSeconds, DEFAULT_TIMEOUT_SECONDS) * 1000 32 33 const response = await safeFetch(url.toString(), { 34 method: 'GET', 35 headers: { 36 Accept: 'application/json', 37 'Accept-Encoding': 'gzip', 38 'X-Subscription-Token': apiKey, 39 }, 40 timeoutMs, 41 }) 42 43 if (!response.ok) { 44 throw new Error( 45 `Brave Search API error: ${response.status} ${response.statusText}`, 46 ) 47 } 48 49 const data = (await response.json()) as BraveSearchResponse 50 51 if (!data.web?.results) { 52 return [] 53 } 54 55 return data.web.results.map((result) => ({ 56 title: result.title, 57 url: result.url, 58 snippet: result.description ?? '', 59 publishedDate: result.age, 60 })) 61 } 62 63 interface BraveSearchResponse { 64 web?: { 65 results?: Array<{ 66 title: string 67 url: string 68 description?: string 69 age?: string 70 }> 71 } 72 }