/** * Tavily AI Search - OpenClaw Plugin (OpenClaw 2026 api.registerTool API) * Provides web search optimized for AI/LLM consumption. * @see https://docs.openclaw.ai/plugins/agent-tools */ const https = require('https'); const TAVILY_API_KEY = process.env.TAVILY_API_KEY || ''; const TAVILY_API_HOST = 'api.tavily.com'; function tavilySearch(query, options = {}) { const { search_depth = 'basic', topic = 'general', max_results = 5, include_answer = true, include_raw_content = false, include_images = false, include_domains = null, exclude_domains = null, } = options; return new Promise((resolve, reject) => { const requestBody = JSON.stringify({ api_key: TAVILY_API_KEY, query, search_depth, topic, max_results, include_answer, include_raw_content, include_images, include_domains, exclude_domains, }); const reqOptions = { hostname: TAVILY_API_HOST, port: 443, path: '/search', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(requestBody), }, }; const req = https.request(reqOptions, (res) => { let data = ''; res.on('data', (chunk) => (data += chunk)); res.on('end', () => { try { resolve(JSON.parse(data)); } catch (e) { reject(new Error(`Failed to parse Tavily response: ${e.message}`)); } }); }); req.on('error', (e) => reject(new Error(`Tavily API request failed: ${e.message}`))); req.write(requestBody); req.end(); }); } /** * OpenClaw 2026 plugin entry: register tool via api.registerTool() */ function register(api) { const hasRegisterTool = typeof api === 'object' && typeof api.registerTool === 'function'; if (api && api.logger && typeof api.logger.info === 'function') { api.logger.info('[Tavily] plugin loaded, hasRegisterTool=' + hasRegisterTool); } else { console.log('[Tavily] plugin loaded, hasRegisterTool=' + hasRegisterTool); } if (!hasRegisterTool) { // Legacy loader: return tools array for older gateways const tool = { name: 'tavily_search', description: 'AI-optimized web search using Tavily. Best for research, news, fact-checking, and gathering authoritative sources.', input_schema: { type: 'object', properties: { query: { type: 'string', description: 'Search query string' }, search_depth: { type: 'string', enum: ['basic', 'advanced'], default: 'basic' }, topic: { type: 'string', enum: ['general', 'news'], default: 'general' }, max_results: { type: 'integer', minimum: 1, maximum: 10, default: 5 }, include_answer: { type: 'boolean', default: true }, }, required: ['query'], }, async execute(params) { const out = await runTavily(params); return { content: [{ type: 'text', text: typeof out === 'string' ? out : JSON.stringify(out, null, 2) }] }; }, }; return { tools: [tool] }; } api.registerTool( { name: 'tavily_search', description: 'AI-optimized web search using Tavily. Best for research, news, fact-checking, and gathering authoritative sources.', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query string' }, search_depth: { type: 'string', enum: ['basic', 'advanced'], description: 'basic (fast) or advanced (comprehensive)', default: 'basic' }, topic: { type: 'string', enum: ['general', 'news'], description: 'general or news (last 7 days)', default: 'general' }, max_results: { type: 'integer', minimum: 1, maximum: 10, description: 'Number of results', default: 5 }, include_answer: { type: 'boolean', description: 'Include AI-generated answer summary', default: true }, }, required: ['query'], }, async execute(_id, params) { const out = await runTavily(params); return { content: [{ type: 'text', text: typeof out === 'string' ? out : JSON.stringify(out, null, 2) }] }; }, }, { optional: true } ); if (api.logger && typeof api.logger.info === 'function') { api.logger.info('[Tavily] tavily_search registered via api.registerTool'); } else { console.log('[Tavily] tavily_search registered via api.registerTool'); } } async function runTavily(params) { if (!TAVILY_API_KEY) { return { success: false, error: 'TAVILY_API_KEY is not set. Set it in openclaw.json env or gateway environment.' }; } const { query, ...options } = params || {}; try { const result = await tavilySearch(query || '', options); if (result.error) return { success: false, error: result.error }; return { success: true, query: result.query, answer: result.answer, results: (result.results || []).map((r) => ({ title: r.title, url: r.url, content: r.content, score: r.score })), images: result.images || [], response_time: result.response_time, }; } catch (err) { return { success: false, error: err.message }; } } module.exports = register;