anomalyco / anomalyco/opencode

websearch tool is never exposed to custom/local providers (hardcoded provider whitelist in ToolRegistry)

Open
#44,307 2 comments 0 reactions 0 assignees View on GitHub

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 valid tool_calls for 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"
      }
    }
  ]
}
## Impact
  • 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 webfetch workarounds.
  • 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

  1. Configure a custom provider (e.g., Ollama via @ai-sdk/openai-compatible) with a tool-capable model
  2. Ask something requiring fresh data, explicitly mentioning the websearch tool
  3. Model reports it has no such tool; capturing the outgoing request confirms tools lacks websearch
  4. Set OPENCODE_ENABLE_EXA=true, fully restart opencode → websearch appears in tools and 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-go provider; 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.