An OpenCode plugin that implements Claude's Tool Search Tool pattern. Reduces context usage by deferring tool descriptions and letting the model discover tools on demand via BM25 keyword search or regex matching.
Inspired by famitzsy8/opencode-tool-search-tool (a fork of opencode). This project achieves similar results as a standalone plugin — no core modifications needed.
- The
tool.definitionhook intercepts every tool before the LLM sees it - Tools not in
alwaysLoadget their descriptions replaced with a short[deferred]stub (parameters are kept — see OpenAI compatibility) - Two search tools (
tool_searchandtool_search_regex) are always available with full descriptions - The system prompt tells the model to use
tool_searchwhen it encounters deferred tools - When the model calls
tool_search("file operations"), it gets back full descriptions and parameter schemas of matching tools
| Setup | Total tools | Deferred | Savings per turn |
|---|---|---|---|
| Built-in only | ~32 | ~24 | ~8,400 tokens (88%) |
| 3 MCP servers | ~60 | ~52 | ~17,000 tokens (89%) |
| 6+ MCP servers | ~190 | ~182 | ~57,000 tokens (91%) |
npm install opencode-tool-searchAdd to your opencode.jsonc:
For local testing with file://:
{
"plugin": [
["file:///path/to/opencode-tool-search/dist/index.js", {
"alwaysLoad": ["read", "write", "edit", "bash", "glob", "grep"]
}]
]
}| Option | Type | Default | Description |
|---|---|---|---|
alwaysLoad |
string[] |
[] |
Tool IDs that keep full descriptions (never deferred) |
searchLimit |
number |
5 |
Max results per search query |
bm25.k1 |
number |
0.9 |
Term frequency saturation (0.5–2.0) |
bm25.b |
number |
0.4 |
Document length normalization (0–1) |
deferDescription |
string |
[d] |
Custom stub for deferred tools |
Defaults are optimized for smaller language models that send vague queries. For capable models writing specific queries, increase k1 toward 1.5.
["opencode-tool-search", {
"alwaysLoad": ["read", "write", "edit", "bash"],
"bm25": { "k1": 1.5, "b": 0.75 },
"searchLimit": 10
}]BM25 keyword search. Best for natural language queries.
tool_search({ query: "file" }) // → read, write, edit, glob
tool_search({ query: "search code" }) // → grep, ast_grep_search
tool_search({ query: "github issues" }) // → github_list_issues, github_create_issue
Regex search (case-insensitive). Best for specific patterns.
tool_search_regex({ pattern: "github.*issue" }) // → GitHub issue tools
tool_search_regex({ pattern: "^lsp_" }) // → all LSP tools
tool_search_regex({ pattern: "jenkins|build" }) // → Jenkins/CI tools
The fork modifies opencode's core to fully hide deferred tools from the LLM's tool list. This plugin uses the official plugin API:
- Tools are still listed (with a
[d]stub description; parameters are preserved) - The
tool.definitionhook strips descriptions; the system prompt guides the model - ~90% of the fork's benefit with zero core changes
- Works with any opencode version that supports
tool.definitionhook (v1.4.10+)
Each deferred tool still occupies a slot in the tool list with its name (~5-15 tokens), minimal stub (~5 tokens), and parameter schema (~20-50 tokens). With 180 deferred tools this adds up to ~5,400-12,600 tokens per turn. The fork eliminates these entirely by filtering tools in resolveTools() before they reach the LLM.
Fully closing the gap requires upstream changes to opencode's plugin API — see Scalability.
Earlier versions replaced deferred tool parameters with an empty schema (z.object({})). This breaks OpenAI models: when a ChatGPT model calls a deferred tool directly (ignoring the [d] stub), the empty schema can produce undefined arguments, which the OpenAI Responses API rejects with Missing required parameter: 'input[N].arguments'.
Since v0.4.3, deferred tools keep their original parameter schemas — only descriptions are stripped. Parameter schemas are small relative to descriptions, so the token savings impact is minimal (~3-5%). A provider-aware system prompt also tells non-Anthropic models explicitly not to call [d] tools without searching first.
This plugin works alongside RTK (Rust Token Killer) with no conflicts. RTK hooks into tool.execute.before to compress bash/shell output; this plugin hooks into tool.definition and experimental.chat.system.transform to defer tool descriptions. Different hooks, complementary token savings.
The tool.definition hook can modify tool descriptions and parameters but cannot remove tools from the list entirely. Two upstream proposals would close the remaining gap:
hiddenfield ontool.definitionoutput (opencode#23297) — let plugins suppress tools from the LLM tool list entirelydefer_loadingpassthrough to Anthropic API (opencode#23298) — pass Anthropic's nativedefer_loading: truethrough to the API, enabling server-side tool search with prompt cache preservation
npm install
npm run build # tsc + esbuild bundleMIT
{ "plugin": [ ["opencode-tool-search", { "alwaysLoad": ["read", "write", "edit", "bash", "glob", "grep"] }] ] }