From 924e0807765cb5c1aef07e19d1c105379bf36eab Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Jan 2026 22:19:00 +0000 Subject: [PATCH] feat(tool): add tool_search for dynamic tool discovery Adds a new tool_search tool that allows the LLM to search for available tools by name or description using regex patterns. This is the first step toward reducing context window usage when many tools are available. Relates to #2418 --- packages/opencode/src/tool/registry.ts | 2 + packages/opencode/src/tool/tool-search.ts | 45 +++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 packages/opencode/src/tool/tool-search.ts diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 35e378f080b..872222257b5 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -26,6 +26,7 @@ import { Log } from "@/util/log" import { LspTool } from "./lsp" import { Truncate } from "./truncation" import { PlanExitTool, PlanEnterTool } from "./plan" +import { ToolSearchTool } from "./tool-search" export namespace ToolRegistry { const log = Log.create({ service: "tool.registry" }) @@ -108,6 +109,7 @@ export namespace ToolRegistry { WebSearchTool, CodeSearchTool, SkillTool, + ToolSearchTool, ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []), ...(config.experimental?.batch_tool === true ? [BatchTool] : []), ...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [PlanExitTool, PlanEnterTool] : []), diff --git a/packages/opencode/src/tool/tool-search.ts b/packages/opencode/src/tool/tool-search.ts new file mode 100644 index 00000000000..067ccd03fcd --- /dev/null +++ b/packages/opencode/src/tool/tool-search.ts @@ -0,0 +1,45 @@ +import z from "zod" +import { Tool } from "./tool" +import { ToolRegistry } from "./registry" + +export const ToolSearchTool = Tool.define("tool_search", { + description: `Search for available tools by name or description. +Use this when you need to find a tool for a specific task. +Returns a list of matching tool names that you can then use.`, + parameters: z.object({ + query: z.string().describe("Regex pattern to search tool names and descriptions"), + }), + async execute(params, ctx) { + const tools = await ToolRegistry.tools("", ctx.extra?.agent) + const regex = new RegExp(params.query, "i") + + const matches: { id: string; description: string }[] = [] + for (const tool of tools) { + if (tool.id === "tool_search" || tool.id === "invalid") continue + if (regex.test(tool.id) || regex.test(tool.description)) { + matches.push({ + id: tool.id, + description: tool.description.slice(0, 100), + }) + } + } + + if (matches.length === 0) { + return { + title: "No tools found", + metadata: { matches: 0 }, + output: `No tools found matching "${params.query}"`, + } + } + + const output = matches + .map((m) => `- ${m.id}: ${m.description}`) + .join("\n") + + return { + title: `Found ${matches.length} tools`, + metadata: { matches: matches.length, tools: matches.map((m) => m.id) }, + output: `Found ${matches.length} tool(s) matching "${params.query}":\n${output}`, + } + }, +})