web-tool-execution.ts
1 import type { 2 BrowserFetchResponse, 3 DuckDuckGoSearchType, 4 WebFetchResponse, 5 WebSearchResponse, 6 } from '@/lib/shared/chat' 7 import { performBrowserFetch } from '@/server/tools/browser-fetch' 8 import { performWebFetch } from '@/server/tools/web-fetch' 9 import { performWebSearch } from '@/server/tools/web-search' 10 11 const MAX_WEB_TOOL_SEARCH_RESULTS = 10 12 const MAX_WEB_TOOL_FETCH_BYTES = 500_000 13 14 export type WebToolSearchExecutionInput = { 15 query: string 16 searchType?: DuckDuckGoSearchType 17 maxResults?: number 18 } 19 20 export type WebToolFetchExecutionInput = { 21 url: string 22 maxBytes?: number 23 } 24 25 export type BrowserToolFetchExecutionInput = { 26 url: string 27 maxBytes?: number 28 preserveLinks?: boolean 29 } 30 31 export type WebToolSearchExecutionResult = 32 | { 33 ok: true 34 response: WebSearchResponse 35 results: WebSearchResponse['results'] 36 } 37 | { ok: false; error: string } 38 39 export type WebToolFetchExecutionResult = 40 | { 41 ok: true 42 response: WebFetchResponse 43 } 44 | { ok: false; error: string } 45 46 export type BrowserToolFetchExecutionResult = 47 | { 48 ok: true 49 response: BrowserFetchResponse 50 } 51 | { ok: false; error: string } 52 53 export async function executeWebSearchTool( 54 input: WebToolSearchExecutionInput, 55 ): Promise<WebToolSearchExecutionResult> { 56 const normalizedMaxResults = normalizeWebSearchMaxResults(input.maxResults) 57 58 try { 59 const response = await performWebSearch({ 60 query: input.query, 61 searchType: input.searchType, 62 maxResults: normalizedMaxResults, 63 }) 64 65 return { 66 ok: true, 67 response, 68 results: normalizedMaxResults 69 ? response.results.slice(0, normalizedMaxResults) 70 : response.results, 71 } 72 } catch (error) { 73 return { 74 ok: false, 75 error: error instanceof Error ? error.message : 'Web search failed', 76 } 77 } 78 } 79 80 export async function executeWebFetchTool( 81 input: WebToolFetchExecutionInput, 82 ): Promise<WebToolFetchExecutionResult> { 83 const normalizedMaxBytes = normalizeWebFetchMaxBytes(input.maxBytes) 84 85 try { 86 const response = await performWebFetch({ 87 url: input.url, 88 maxBytes: normalizedMaxBytes, 89 }) 90 return { 91 ok: true, 92 response, 93 } 94 } catch (error) { 95 return { 96 ok: false, 97 error: error instanceof Error ? error.message : 'Web fetch failed', 98 } 99 } 100 } 101 102 export async function executeBrowserFetchTool( 103 input: BrowserToolFetchExecutionInput, 104 ): Promise<BrowserToolFetchExecutionResult> { 105 const normalizedMaxBytes = normalizeWebFetchMaxBytes(input.maxBytes) 106 const preserveLinks = input.preserveLinks === true 107 108 try { 109 const response = await performBrowserFetch({ 110 url: input.url, 111 maxBytes: normalizedMaxBytes, 112 ...(preserveLinks ? { preserveLinks: true } : {}), 113 }) 114 return { 115 ok: true, 116 response, 117 } 118 } catch (error) { 119 return { 120 ok: false, 121 error: error instanceof Error ? error.message : 'Browser fetch failed', 122 } 123 } 124 } 125 126 function normalizeWebSearchMaxResults(value: number | undefined): number | undefined { 127 if (typeof value !== 'number' || value <= 0) return undefined 128 return Math.min(value, MAX_WEB_TOOL_SEARCH_RESULTS) 129 } 130 131 function normalizeWebFetchMaxBytes(value: number | undefined): number | undefined { 132 if (typeof value !== 'number' || value <= 0) return undefined 133 return Math.min(value, MAX_WEB_TOOL_FETCH_BYTES) 134 }