Files
openclaw/extensions/tavily/src/tavily-search-tool.ts
2026-03-27 01:45:53 +00:00

98 lines
3.3 KiB
TypeScript

import { Type } from "@sinclair/typebox";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-runtime";
import {
jsonResult,
readNumberParam,
readStringParam,
} from "openclaw/plugin-sdk/provider-web-search";
import { runTavilySearch } from "./tavily-client.js";
function optionalStringEnum<const T extends readonly string[]>(
values: T,
options: { description?: string } = {},
) {
return Type.Optional(
Type.Unsafe<T[number]>({
type: "string",
enum: [...values],
...options,
}),
);
}
const TavilySearchToolSchema = Type.Object(
{
query: Type.String({ description: "Search query string." }),
search_depth: optionalStringEnum(["basic", "advanced"] as const, {
description: 'Search depth: "basic" (default, faster) or "advanced" (more thorough).',
}),
topic: optionalStringEnum(["general", "news", "finance"] as const, {
description: 'Search topic: "general" (default), "news", or "finance".',
}),
max_results: Type.Optional(
Type.Number({
description: "Number of results to return (1-20).",
minimum: 1,
maximum: 20,
}),
),
include_answer: Type.Optional(
Type.Boolean({
description: "Include an AI-generated answer summary (default: false).",
}),
),
time_range: optionalStringEnum(["day", "week", "month", "year"] as const, {
description: "Filter results by recency: 'day', 'week', 'month', or 'year'.",
}),
include_domains: Type.Optional(
Type.Array(Type.String(), {
description: "Only include results from these domains.",
}),
),
exclude_domains: Type.Optional(
Type.Array(Type.String(), {
description: "Exclude results from these domains.",
}),
),
},
{ additionalProperties: false },
);
export function createTavilySearchTool(api: OpenClawPluginApi) {
return {
name: "tavily_search",
label: "Tavily Search",
description:
"Search the web using Tavily Search API. Supports search depth, topic filtering, domain filters, time ranges, and AI answer summaries.",
parameters: TavilySearchToolSchema,
execute: async (_toolCallId: string, rawParams: Record<string, unknown>) => {
const query = readStringParam(rawParams, "query", { required: true });
const searchDepth = readStringParam(rawParams, "search_depth") || undefined;
const topic = readStringParam(rawParams, "topic") || undefined;
const maxResults = readNumberParam(rawParams, "max_results", { integer: true });
const includeAnswer = rawParams.include_answer === true;
const timeRange = readStringParam(rawParams, "time_range") || undefined;
const includeDomains = Array.isArray(rawParams.include_domains)
? (rawParams.include_domains as string[]).filter(Boolean)
: undefined;
const excludeDomains = Array.isArray(rawParams.exclude_domains)
? (rawParams.exclude_domains as string[]).filter(Boolean)
: undefined;
return jsonResult(
await runTavilySearch({
cfg: api.config,
query,
searchDepth,
topic,
maxResults,
includeAnswer,
timeRange,
includeDomains: includeDomains?.length ? includeDomains : undefined,
excludeDomains: excludeDomains?.length ? excludeDomains : undefined,
}),
);
},
};
}