import type { ParallelSearchParams } from '@/tools/parallel/types' import type { ToolConfig, ToolResponse } from '@/tools/types' export const searchTool: ToolConfig = { id: 'parallel_search', name: 'Parallel AI Search', description: 'Search the web using Parallel AI. Provides comprehensive search results with intelligent processing and content extraction.', version: '1.0.0', hosting: { envKeyPrefix: 'PARALLEL_API_KEY', apiKeyParam: 'apiKey', byokProviderId: 'parallel_ai', pricing: { type: 'custom', getCost: (_params, output) => { if (!Array.isArray(output.results)) { throw new Error('Parallel search response missing results array') } // Parallel Search: $0.005 base (includes ≤10 results), +$0.001 per result beyond 10 // https://docs.parallel.ai/resources/pricing const resultCount = output.results.length const additionalResults = Math.max(0, resultCount - 10) const cost = 0.005 + additionalResults * 0.001 return { cost, metadata: { resultCount, additionalResults } } }, }, rateLimit: { mode: 'per_request', requestsPerMinute: 30, }, }, params: { objective: { type: 'string', required: true, visibility: 'user-or-llm', description: 'The search objective or question to answer', }, search_queries: { type: 'string', required: false, visibility: 'user-or-llm', description: 'Comma-separated list of search queries to execute', }, mode: { type: 'string', required: false, visibility: 'user-only', description: 'Search mode: one-shot, agentic, or fast (default: one-shot)', }, max_results: { type: 'number', required: false, visibility: 'user-only', description: 'Maximum number of results to return (default: 10)', }, max_chars_per_result: { type: 'number', required: false, visibility: 'user-only', description: 'Maximum characters per result excerpt (minimum: 1000)', }, include_domains: { type: 'string', required: false, visibility: 'user-or-llm', description: 'Comma-separated list of domains to restrict search results to', }, exclude_domains: { type: 'string', required: false, visibility: 'user-or-llm', description: 'Comma-separated list of domains to exclude from search results', }, apiKey: { type: 'string', required: true, visibility: 'user-only', description: 'Parallel AI API Key', }, }, request: { url: 'https://api.parallel.ai/v1beta/search', method: 'POST', headers: (params) => ({ 'Content-Type': 'application/json', 'x-api-key': params.apiKey, 'parallel-beta': 'search-extract-2025-10-10', }), body: (params) => { const body: Record = { objective: params.objective, } if (params.search_queries) { if (Array.isArray(params.search_queries)) { body.search_queries = params.search_queries } else if (typeof params.search_queries === 'string') { const queries = params.search_queries .split(',') .map((q: string) => q.trim()) .filter((q: string) => q.length > 0) if (queries.length > 0) body.search_queries = queries } } if (params.mode) body.mode = params.mode if (params.max_results) body.max_results = Number(params.max_results) if (params.max_chars_per_result) { body.excerpts = { max_chars_per_result: Number(params.max_chars_per_result) } } const sourcePolicy: Record = {} if (params.include_domains) { sourcePolicy.include_domains = params.include_domains .split(',') .map((d: string) => d.trim()) .filter((d: string) => d.length > 0) } if (params.exclude_domains) { sourcePolicy.exclude_domains = params.exclude_domains .split(',') .map((d: string) => d.trim()) .filter((d: string) => d.length > 0) } if (Object.keys(sourcePolicy).length > 0) { body.source_policy = sourcePolicy } return body }, }, transformResponse: async (response: Response) => { if (!response.ok) { const errorText = await response.text() throw new Error(`Parallel AI search failed: ${response.status} - ${errorText}`) } const data = await response.json() if (!data.results) { return { success: false, error: 'No results returned from search', output: { results: [], search_id: data.search_id ?? null, }, } } return { success: true, output: { search_id: data.search_id ?? null, results: data.results.map((result: Record) => ({ url: result.url ?? null, title: result.title ?? null, publish_date: result.publish_date ?? null, excerpts: result.excerpts ?? [], })), }, } }, outputs: { search_id: { type: 'string', description: 'Unique identifier for this search request', }, results: { type: 'array', description: 'Search results with excerpts from relevant pages', items: { type: 'object', properties: { url: { type: 'string', description: 'The URL of the search result' }, title: { type: 'string', description: 'The title of the search result' }, publish_date: { type: 'string', description: 'Publication date of the page (YYYY-MM-DD)', optional: true, }, excerpts: { type: 'array', description: 'LLM-optimized excerpts from the page', items: { type: 'string' }, }, }, }, }, }, }