Files
xiaoli-cli/plugins/operit/tavily.ts
T
xiaoli-cli bot dfe21298b9 release: v5.4.2 - terminal模拟器 + Operit桥接
新增插件:
- terminal: 持久交互式终端,纯标准库,全平台,13个测试
- operit_bridge: Operit AI工具格式桥接,47个脚本288个工具,12个测试

其他:
- plugins/operit/: 47个Operit官方脚本
- 更新plugin_registry.json/plugins_config.py
- 更新about.txt/CHANGELOG.md至v5.4.2
2026-06-23 08:50:15 +00:00

198 lines
11 KiB
TypeScript

/* METADATA
{
"name": "tavily",
"display_name": {
"zh": "Tavily 搜索",
"en": "Tavily Search"
},
"description": {
"zh": "使用Tavily API执行高级网络搜索、内容提取、网站爬取和站点地图生成。",
"en": "Use the Tavily API for advanced web search, content extraction, website crawling, and sitemap generation."
},
"env": [
"TAVILY_API_KEY"
],
"category": "Search",
"tools": [
{
"name": "search",
"description": { "zh": "一个强大的网络搜索工具,使用Tavily的AI搜索引擎提供全面、实时的结果。", "en": "Powerful web search using Tavily's AI search engine for comprehensive, up-to-date results." },
"parameters": [
{ "name": "query", "description": { "zh": "搜索查询", "en": "Search query" }, "type": "string", "required": true },
{ "name": "search_depth", "description": { "zh": "搜索深度,可以是 'basic' 或 'advanced'", "en": "Search depth: 'basic' or 'advanced'" }, "type": "string", "required": false, "default": "basic" },
{ "name": "topic", "description": { "zh": "搜索类别,可以是 'general' 或 'news'", "en": "Search topic: 'general' or 'news'" }, "type": "string", "required": false, "default": "general" },
{ "name": "days", "description": { "zh": "包含在搜索结果中的过去天数", "en": "Number of past days to include in results" }, "type": "number", "required": false },
{ "name": "max_results", "description": { "zh": "返回的最大搜索结果数", "en": "Maximum number of search results" }, "type": "number", "required": false, "default": 10 },
{ "name": "include_images", "description": { "zh": "在响应中包含与查询相关的图像列表", "en": "Include a list of images related to the query in the response" }, "type": "boolean", "required": false, "default": false },
{ "name": "include_raw_content", "description": { "zh": "包含每个搜索结果的已清理和解析的HTML内容", "en": "Include cleaned and parsed HTML content for each search result" }, "type": "boolean", "required": false, "default": false },
{ "name": "include_domains", "description": { "zh": "要专门包含在搜索结果中的域名列表", "en": "Domain list to specifically include in results" }, "type": "array", "required": false, "default": [] },
{ "name": "exclude_domains", "description": { "zh": "要专门排除的域名列表", "en": "Domain list to specifically exclude from results" }, "type": "array", "required": false, "default": [] }
]
},
{
"name": "extract",
"description": { "zh": "一个强大的网页内容提取工具,从指定的URL检索和处理原始内容。", "en": "Extract web content from specified URLs and process the raw content." },
"parameters": [
{ "name": "urls", "description": { "zh": "要提取内容的URL列表", "en": "List of URLs to extract content from" }, "type": "array", "required": true },
{ "name": "extract_depth", "description": { "zh": "提取深度 - 'basic' 或 'advanced'", "en": "Extraction depth: 'basic' or 'advanced'" }, "type": "string", "required": false, "default": "basic" },
{ "name": "include_images", "description": { "zh": "在响应中包含从URL中提取的图像列表", "en": "Include a list of images extracted from the URL(s)" }, "type": "boolean", "required": false, "default": false },
{ "name": "format", "description": { "zh": "提取内容的格式,'markdown' 或 'text'", "en": "Output format: 'markdown' or 'text'" }, "type": "string", "required": false, "default": "markdown" }
]
},
{
"name": "crawl",
"description": { "zh": "一个强大的网络爬虫,从指定的基URL开始结构化地爬取网站。", "en": "Structured website crawler starting from a base URL." },
"parameters": [
{ "name": "url", "description": { "zh": "开始爬取的根URL", "en": "Root URL to start crawling from" }, "type": "string", "required": true },
{ "name": "max_depth", "description": { "zh": "爬取的最大深度", "en": "Maximum crawl depth" }, "type": "number", "required": false, "default": 1 },
{ "name": "max_breadth", "description": { "zh": "每层要跟随的最大链接数", "en": "Maximum number of links to follow per depth level" }, "type": "number", "required": false, "default": 20 },
{ "name": "limit", "description": { "zh": "爬虫将处理的总链接数", "en": "Total number of links the crawler will process" }, "type": "number", "required": false, "default": 50 },
{ "name": "instructions", "description": { "zh": "给爬虫的自然语言指令", "en": "Natural-language instructions for the crawler" }, "type": "string", "required": false },
{ "name": "allow_external", "description": { "zh": "是否允许跟随外部域名的链接", "en": "Whether to allow following links to external domains" }, "type": "boolean", "required": false, "default": false },
{ "name": "format", "description": { "zh": "提取内容的格式,'markdown' 或 'text'", "en": "Output format: 'markdown' or 'text'" }, "type": "string", "required": false, "default": "markdown" }
]
},
{
"name": "map",
"description": { "zh": "一个强大的网站地图工具,创建网站URL的结构化地图。", "en": "Generate a structured sitemap of website URLs." },
"parameters": [
{ "name": "url", "description": { "zh": "开始映射的根URL", "en": "Root URL to start mapping from" }, "type": "string", "required": true },
{ "name": "max_depth", "description": { "zh": "映射的最大深度", "en": "Maximum mapping depth" }, "type": "number", "required": false, "default": 1 },
{ "name": "max_breadth", "description": { "zh": "每层要跟随的最大链接数", "en": "Maximum number of links to follow per depth level" }, "type": "number", "required": false, "default": 20 },
{ "name": "limit", "description": { "zh": "爬虫将处理的总链接数", "en": "Total number of links the crawler will process" }, "type": "number", "required": false, "default": 50 }
]
}
]
}*/
const tavily = (function () {
const client = OkHttp.newClient();
const BASE_URLS = {
search: 'https://api.tavily.com/search',
extract: 'https://api.tavily.com/extract',
crawl: 'https://api.tavily.com/crawl',
map: 'https://api.tavily.com/map'
};
async function makeTavilyRequest(endpoint: string, params: any): Promise<any> {
const apiKey = getEnv("TAVILY_API_KEY");
if (!apiKey) {
throw new Error("Tavily API key is not set. Please configure it in the environment variables.");
}
const requestBody = { ...params };
const headers = {
'accept': 'application/json',
'content-type': 'application/json',
'Authorization': `Bearer ${apiKey}`
};
try {
const request = client.newRequest()
.url(endpoint)
.method('POST')
.headers(headers)
.body(JSON.stringify(requestBody), 'json');
const response = await request.build().execute();
if (!response.isSuccessful()) {
throw new Error(`Tavily API Error: ${response.statusCode} - ${response.content}`);
}
return JSON.parse(response.content);
} catch (error: any) {
console.error(`Tavily request failed: ${error.message}`);
throw error;
}
}
function formatSearchResults(response: any): string {
const output: string[] = [];
if (response.answer) {
output.push(`Answer: ${response.answer}`);
}
output.push('Detailed Results:');
response.results.forEach((result: any) => {
output.push(`\nTitle: ${result.title}`);
output.push(`URL: ${result.url}`);
output.push(`Content: ${result.content}`);
if (result.raw_content) {
output.push(`Raw Content: ${result.raw_content}`);
}
});
return output.join('\n');
}
function formatCrawlResults(response: any): string {
const output: string[] = [];
output.push(`Crawl Results:`);
output.push(`Base URL: ${response.base_url}`);
output.push('\nCrawled Pages:');
response.results.forEach((page: any, index: number) => {
output.push(`\n[${index + 1}] URL: ${page.url}`);
if (page.raw_content) {
const contentPreview = page.raw_content.length > 200
? page.raw_content.substring(0, 200) + "..."
: page.raw_content;
output.push(`Content: ${contentPreview}`);
}
});
return output.join('\n');
}
function formatMapResults(response: any): string {
const output: string[] = [];
output.push(`Site Map Results:`);
output.push(`Base URL: ${response.base_url}`);
output.push('\nMapped Pages:');
response.results.forEach((page: string, index: number) => {
output.push(`\n[${index + 1}] URL: ${page}`);
});
return output.join('\n');
}
async function search(params: any) {
const response = await makeTavilyRequest(BASE_URLS.search, params);
return formatSearchResults(response);
}
async function extract(params: any) {
const response = await makeTavilyRequest(BASE_URLS.extract, params);
// Extract uses the same format as search
return formatSearchResults(response);
}
async function crawl(params: any) {
const response = await makeTavilyRequest(BASE_URLS.crawl, params);
return formatCrawlResults(response);
}
async function map(params: any) {
const response = await makeTavilyRequest(BASE_URLS.map, params);
return formatMapResults(response);
}
async function wrap(func: (params: any) => Promise<any>, params: any, successMessage: string, failMessage: string) {
try {
const result = await func(params);
complete({ success: true, message: successMessage, data: result });
} catch (error: any) {
console.error(`Function ${func.name} failed! Error: ${error.message}`);
complete({ success: false, message: `${failMessage}: ${error.message}`, error_stack: error.stack });
}
}
return {
search: (p: any) => wrap(search, p, '搜索成功', '搜索失败'),
extract: (p: any) => wrap(extract, p, '提取成功', '提取失败'),
crawl: (p: any) => wrap(crawl, p, '爬取成功', '爬取失败'),
map: (p: any) => wrap(map, p, '映射成功', '映射失败')
};
})();
exports.search = tavily.search;
exports.extract = tavily.extract;
exports.crawl = tavily.crawl;
exports.map = tavily.map;