/ src / server / tools / web-search / provider-brave.ts
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  }