66 lines
2.2 KiB
TypeScript
66 lines
2.2 KiB
TypeScript
/**
|
|
* Web Search Tool
|
|
* Uses SearXNG or Brave Search API.
|
|
*/
|
|
|
|
import { tool } from 'ai'
|
|
import { z } from 'zod'
|
|
import { toolRegistry } from './registry'
|
|
|
|
async function searchSearXNG(query: string, searxngUrl: string): Promise<any> {
|
|
const url = `${searxngUrl.replace(/\/+$/, '')}/search?q=${encodeURIComponent(query)}&format=json`
|
|
const response = await fetch(url, { headers: { 'Accept': 'application/json' } })
|
|
if (!response.ok) throw new Error(`SearXNG error: ${response.status}`)
|
|
const data = await response.json()
|
|
return (data.results || []).slice(0, 8).map((r: any) => ({
|
|
title: r.title,
|
|
url: r.url,
|
|
snippet: r.content || '',
|
|
}))
|
|
}
|
|
|
|
async function searchBrave(query: string, apiKey: string): Promise<any> {
|
|
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=8`
|
|
const response = await fetch(url, {
|
|
headers: { 'Accept': 'application/json', 'X-Subscription-Token': apiKey }
|
|
})
|
|
if (!response.ok) throw new Error(`Brave error: ${response.status}`)
|
|
const data = await response.json()
|
|
return (data.web?.results || []).map((r: any) => ({
|
|
title: r.title,
|
|
url: r.url,
|
|
snippet: r.description || '',
|
|
}))
|
|
}
|
|
|
|
toolRegistry.register({
|
|
name: 'web_search',
|
|
description: 'Search the web for information. Returns a list of results with titles, URLs and snippets.',
|
|
isInternal: false,
|
|
buildTool: (ctx) =>
|
|
tool({
|
|
description: 'Search the web for information. Returns results with titles, URLs and snippets.',
|
|
inputSchema: z.object({
|
|
query: z.string().describe('The search query'),
|
|
}),
|
|
execute: async ({ query }) => {
|
|
try {
|
|
const provider = ctx.config.WEB_SEARCH_PROVIDER || 'searxng'
|
|
|
|
if (provider === 'brave' || provider === 'both') {
|
|
const apiKey = ctx.config.BRAVE_SEARCH_API_KEY
|
|
if (apiKey) {
|
|
return await searchBrave(query, apiKey)
|
|
}
|
|
}
|
|
|
|
// Default: SearXNG
|
|
const searxngUrl = ctx.config.SEARXNG_URL || 'http://localhost:8080'
|
|
return await searchSearXNG(query, searxngUrl)
|
|
} catch (e: any) {
|
|
return { error: `Web search failed: ${e.message}` }
|
|
}
|
|
},
|
|
}),
|
|
})
|