anomalyco / anomalyco/opencode
websearch tool is never exposed to custom/local providers (hardcoded provider whitelist in ToolRegistry)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
[Bug] websearch tool is never exposed to custom/local providers (hardcoded provider whitelist in ToolRegistry)
Environment
- OpenCode Desktop 1.18.21, Windows 11
- Custom provider: Ollama via
@ai-sdk/openai-compatible(baseURL: http://localhost:11434/v1) - Models tested:
qwen3.6:35b-a3b-q4_K_M,nemotron-3.5-lightning— both have native tool calling (verified directly against Ollama's OpenAI-compatible API: they emit validtool_callsfor a websearch-like tool schema, both streaming and non-streaming)
Summary
The websearch tool is silently stripped from the tool list for any provider that is not opencode / opencode-go, unless undocumented experimental env flags are set. Custom providers (Ollama, LM Studio, llama.cpp, anything @ai-sdk/openai-compatible) therefore never receive the tool, regardless of model capabilities.
The model itself reports the confusion perfectly:
"I don't have a 'websearch' tool specifically among my available tools. I have: webfetch (requires URL), bash"
Evidence
MITM capture of the exact POST /v1/chat/completions payload that opencode sends to the local model (full sanitized captures: https://gist.github.com/4ebuRushka/06975027c42db32692fd0c2465dcca13).
Before workaround — 11 tools, no websearch:
{
"model": "qwen3.6:35b-a3b-q4_K_M",
"tool_count": 11,
"tools": ["bash", "edit", "glob", "grep", "question", "read", "skill", "task", "todowrite", "webfetch", "write"],
"websearch_present": false
}
Identical list for nemotron-3.5-lightning.
After setting OPENCODE_ENABLE_EXA=true — 12 tools, websearch present, and the local model successfully calls it (3 consecutive calls within one session, keyless Exa backend):
{
"model": "qwen3.6:35b-a3b-q4_K_M",
"tool_count": 12,
"tools": ["bash", "edit", "glob", "grep", "question", "read", "skill", "task", "todowrite", "webfetch", "websearch", "write"],
"websearch_present": true
}
Code path
ToolRegistry.tools filter:
const filtered = (yield* all()).filter((tool) => {
if (tool.id === WebSearchTool.id) {
return webSearchEnabled(input.providerID, { exa: flags.enableExa, parallel: flags.enableParallel });
}
// ...
});
function webSearchEnabled(providerID, flags = { exa: false, parallel: false }) {
return providerID === ID.opencode
|| providerID === ID.make("opencode-go")
|| flags.exa
|| flags.parallel;
}
Flags originate from env vars: OPENCODE_ENABLE_EXA / OPENCODE_EXPERIMENTAL_EXA / OPENCODE_ENABLE_PARALLEL / OPENCODE_EXPERIMENTAL_PARALLEL; deterministic backend selection via OPENCODE_WEBSEARCH_PROVIDER=exa|parallel.
Full sanitized captures (before/after)
{
"_comment": "Sanitized MITM captures of POST /v1/chat/completions payloads sent by OpenCode Desktop 1.18.21 to a local Ollama instance. Only structural fields are preserved; message contents removed.",
"captures": [
{
"phase": "before workaround (no env flags set)",
"model": "qwen3.6:35b-a3b-q4_K_M",
"provider": "ollama_local (@ai-sdk/openai-compatible)",
"max_tokens": 32000,
"stream": true,
"tool_choice": "auto",
"tool_count": 11,
"tools": ["bash", "edit", "glob", "grep", "question", "read", "skill", "task", "todowrite", "webfetch", "write"],
"websearch_present": false
},
{
"phase": "before workaround (no env flags set)",
"model": "nemotron-3.5-lightning",
"provider": "ollama_local (@ai-sdk/openai-compatible)",
"max_tokens": 32000,
"stream": true,
"tool_choice": "auto",
"tool_count": 11,
"tools": ["bash", "edit", "glob", "grep", "question", "read", "skill", "task", "todowrite", "webfetch", "write"],
"websearch_present": false
},
{
"phase": "after OPENCODE_ENABLE_EXA=true + OPENCODE_WEBSEARCH_PROVIDER=exa (full app restart)",
"model": "qwen3.6:35b-a3b-q4_K_M",
"provider": "ollama_local (@ai-sdk/openai-compatible)",
"max_tokens": 32000,
"stream": true,
"tool_choice": "auto",
"tool_count": 12,
"tools": ["bash", "edit", "glob", "grep", "question", "read", "skill", "task", "todowrite", "webfetch", "websearch", "write"],
"websearch_present": true,
"observed_behavior": {
"model_called_websearch": true,
"websearch_executions_in_session": 3,
"exa_api_key_required": false,
"note": "assistant turn contained valid tool_call for websearch followed by three tool-result messages"
}
}
]
}
- Local / custom-provider models cannot search the web at all, even when explicitly instructed to use websearch. They either answer from memory (hallucinating current data) or attempt awkward
webfetchworkarounds. - Confusing UX: docs/UI imply the tool exists; the model truthfully says it doesn't.
- A user-side plugin overriding the tool description ("use this FIRST for any search") makes local models try to comply with an instrument they don't have.
Reproduction
- Configure a custom provider (e.g., Ollama via
@ai-sdk/openai-compatible) with a tool-capable model - Ask something requiring fresh data, explicitly mentioning the websearch tool
- Model reports it has no such tool; capturing the outgoing request confirms
toolslacks websearch - Set
OPENCODE_ENABLE_EXA=true, fully restart opencode → websearch appears intoolsand works end-to-end
Expected behavior
websearch is a client-side, provider-independent tool backed by Exa/Parallel MCP. It should be available to all providers by default, or at least be configurable per provider (e.g., a config option) instead of a hardcoded whitelist plus undocumented env flags.
Related:
- #40568 / #42378 — the same whitelist problem for the
opencode-goprovider; resolved by adding yet another entry to the whitelist instead of addressing the root cause - #40028 — feature request to manage websearch via config/UI instead of undocumented env vars
- #39121 — websearch unexpectedly switches between Exa and Parallel providers
- #32600 — Firecrawl as an additional keyless search provider
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the ToolRegistry.tools filter and read webSearchEnabled, then reproduce the custom-provider request described in the issue with and without the experimental environment flags. Done means websearch is available to capable custom/local providers by default or through an explicit provider configuration, without relying on the hardcoded whitelist.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ollama, typescript
- Domain
- backend, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100