You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

151 lines
5.1 KiB

/**
* 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;