Runs the built-in web_search tool server-side on an OpenRouter-compatible gateway, so search uses the same endpoint and key as the chat models: three request protocols, every citation shape parsed (including snippets recovered from the cited span), and a settings card with a live connection test.
Install
# from npm (prebuilt)
dsh plugin --profile web add @samebits/dsh-web-search-openrouter
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:vitas/dsh-web-search-openrouter
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
Grounded web search for DeepSeek Harness, on the gateway you already pay for.
@samebits/dsh-web-search-openrouter registers a search provider on the harness's
web seam (ctx.web). The built-in web_search tool then runs server-side on
an OpenRouter-compatible gateway — the same endpoint and the same API key as your
chat models. Nothing about the model-facing tool surface changes: same tool, same
arguments, same citeable results. Only the engine behind it moves off the
harness's bundled DeepSeek search and onto your own gateway budget.
- Three protocols, one provider. Native OpenAI
web_search, OpenRouter'sopenrouter:web_searchserver tool, and the deprecatedplugins: [{ id: 'web' }]field. Pick the one your gateway accepts. - Reads every citation shape.
web_search_call.action.sources[],openrouter:web_search.action.sources[], andurl_citationannotations — including snippets sliced out of the cited span when the gateway sends no excerpt. - Editable at runtime. A card on the Plugins settings tab (English, 中文, Русский) with a Test search button that runs one real query through the saved configuration.
- No servers, no telemetry. The only outbound request is the search itself, to the endpoint you configured. Nothing is sent anywhere else.
- Dependency-free host. No
@deepseek-ai/*runtime imports, so it works in a linked development checkout as well as from the registry.

Install
dsh plugin add @samebits/dsh-web-search-openrouter --profile web
The package ships a bundle patch (dsh.bundle.patch), so that one line composes
everything: it points the web seam at this provider and inserts the provider row.
From GitHub, tracking the default branch:
dsh plugin add github:vitas/dsh-web-search-openrouter --profile web
From a local checkout (useful while editing the plugin):
git clone https://github.com/vitas/dsh-web-search-openrouter.git
cd dsh-web-search-openrouter && npm install && npm run build
dsh plugin add link:$PWD --profile web
Then store the key and restart once:
dsh credential set OPENROUTER_API_KEY # or export it before launching dsh
The credential reference is resolved per search, through the DSH credentials
service (Settings → Models) and then the process environment — so rotating a key
never needs a restart. The bundle patch is applied at boot; a linked checkout's
host entry is imported at boot too, so the first run after installing needs one
dsh web restart. After that, every settings edit from the card is live.
Quick start
- Install the package (above).
- Restart
dsh web, then open Settings → Plugins → Web search (OpenRouter). - Set Endpoint, Search model, and paste your API key.
- Press Test search — you should get a source count and the first hits.
- Ask the agent something time-sensitive; it will call
web_searchas usual.
Verified gateways
Web search is a server tool: the gateway must implement it for the model you name. The plugin reports that honestly — if the gateway answers without searching, the search fails with an explanatory error instead of silently returning the model's memory.
api.b.ai (protocol openai)
Verified working on /v1/responses with tools: [{ type: 'web_search' }], hits
returned as url_citation annotations. Cheapest first:
| Model | Citations | Notes |
|---|---|---|
gpt-5.4-nano |
2 | cheapest verified; ~8.5k input tokens per query |
gpt-5.4-mini |
4 | |
gpt-5.5-instant |
8 | |
gpt-5-mini |
4 | large input token count |
gpt-6-astra, gpt-6-sol, gpt-5.6-sol, gpt-5.5 |
1–4 |
Not supported on this gateway: gpt-5-nano, glm-*, deepseek-*, the
Anthropic and Gemini routes, :online suffixes, plugins: [{ id: 'web' }], and
openrouter:web_search.
openrouter.ai (protocol openrouter)
Use the server tool:
protocol: openrouter
baseURL: https://openrouter.ai/api/v1
model: openai/gpt-5.2 # any model with the web-search badge
engine (auto, native, exa, firecrawl, parallel),
searchContextSize (low, medium, high), maxUses, maxTotalResults, and
the domain filters are passed through as server-tool parameters. On a
non-OpenRouter gateway this protocol is rejected — that is the gateway talking,
not the plugin.
OpenAI proper (protocol openai)
https://api.openai.com/v1 + tools: [{ type: 'web_search' }]; sources arrive as
web_search_call.action.sources[] and are parsed by the same code path.
Protocols
protocol |
Wire shape | Use for |
|---|---|---|
openai (default) |
tools: [{ type: 'web_search' }] |
OpenAI, Azure, and aggregators that proxy the native tool (api.b.ai) |
openrouter |
tools: [{ type: 'openrouter:web_search', parameters }] |
openrouter.ai and gateways that adopted the server tool |
plugin (deprecated) |
plugins: [{ id: 'web', … }] |
older gateways that never adopted the server tool |
Settings reference
Every field is editable from the settings card and can be seeded from the
composition entry; a field set in settings.yaml is marked overridden in the
card and can be reset there.
| Field | Default | Meaning |
|---|---|---|
protocol |
openai |
Enablement surface (see above) |
baseURL |
https://openrouter.ai/api/v1 |
Gateway base; /responses is appended |
model |
openai/gpt-5.2 |
Model that exposes web search on that gateway |
apiKeyEnv |
OPENROUTER_API_KEY |
Credential reference, resolved per search |
apiKey |
— | Literal key; wins over apiKeyEnv (keep it out of shared profiles) |
maxResults |
5 |
Requested results, 1–25; lower is cheaper |
maxOutputTokens |
1024 |
max_output_tokens for one search turn |
includeAnswer |
false |
Return the search model's prose as content too |
engine |
— | openrouter protocol only |
searchContextSize |
— | openrouter protocol only: low/medium/high |
maxUses |
— | openrouter protocol only: cap server-tool invocations |
maxTotalResults |
— | openrouter protocol only |
allowedDomains |
— | Restrict results to these hostnames |
excludedDomains |
— | Drop results from these hostnames |
referer, title |
— | HTTP-Referer / X-Title attribution headers |
Cost
web_search is billed as tokens on the search model — not per query. A single
tool call can fan out: dsh-tool-web's searchMaxQueries (default 4) turns one
web_search into up to four gateway searches, each with its own input context.
Keep maxResults low, choose the cheapest model your gateway serves with search,
and raise searchMaxQueries only when you actually need breadth. See
docs/gateways.md for measured numbers.
Troubleshooting
| Symptom | Cause |
|---|---|
WEB_PROVIDER_CREDENTIAL_MISSING |
No key stored under apiKeyEnv; set it in the card or export it before launching dsh. |
| "the gateway ran no server-side search for model …" | The model does not expose web search on that gateway. Pick one from the verified table. |
| "Invalid value: 'openrouter:web_search'" | The gateway does not implement OpenRouter's server tool; switch protocol to openai. |
| "node only allows access to inference API paths" | A gateway-side proxy restriction; check baseURL (a stray trailing slash used to produce //responses). |
HTTP 404 on /responses |
The gateway is chat-completions-only; this provider needs a Responses endpoint. |
Development
npm install # esbuild + typescript
npm run check # host syntax check + client typecheck
npm test # 35 tests, no network, no credentials (1 skipped: the live one)
npm run check-locales
npm run build # rebuild the committed lib/client.js (also runs on npm pack)
A live smoke test is opt-in and never runs in CI:
DSH_WEB_SEARCH_LIVE=1 \
OPENROUTER_API_KEY=sk-... \
DSH_WEB_SEARCH_BASE_URL=https://api.b.ai/v1 \
DSH_WEB_SEARCH_MODEL=gpt-5.4-nano \
node --test test/live.test.mjs
Architecture, the seam contracts, and the release process live in docs/.
Privacy
The plugin stores no data and runs no background work. Per search it sends one
HTTPS request to baseURL containing your query, the model name, and the
configured parameters; the gateway's own terms govern what happens to it. The
API key is read from the DSH credential store at call time and is never written
to settings, logs, or the browser.
License
Links
More in this category
Tencent/BrowserSkill#dsh-plugin-browserskill★ 7257
BrowserSkill bridge for controlling visible Chrome and Edge Agent Windows from DeepSeek Harness, with native browser tools, accessibility and VOM observations, screenshots, owned multi-session control, and a live Web UI overlay.
omdsh-dev/dsh-browser#packages/browser/bridge-browser★ 726
Chrome sidebar extension that lets DSH operate your browser directly, no vision capabilities required.
liustack/modsearch★ 548
Web search bridge for text-only agents: ask the web or X, get structured JSON evidence (search, fetch, citations).
DDDMUC/dsh-free-search★ 249
Free, keyless web search for DSH: 7 engines (DuckDuckGo/Bing/SearXNG free + Exa/Perplexity/DeepSeek paid), auto-failover, settings-page UI with API key inputs and official links, web_fetch, and an engine test tool.
Tabbit-Browser/dsh-tabbit★ 101
Gives DeepSeek Harness control of the Tabbit Browser: auto-loads the tabbit-browser skill on install, detects official Tabbit and Tabbit Browser releases (>= 1.9.0), checks the tabbit-cli persistent runtime, diagnoses the per-platform DSH sandbox mode needed to call the CLI, and downloads the region-matched official installer via a background job when no qualifying version is present.
wqty123/dsh-browser★ 81
Shared real browser for DSH: a native Electron window the human can watch and take over, driven by the agent over CDP with 20 browser_* tools (open/snapshot/execute/fill/screenshot/download/auth), per-task session isolation, cookie persistence, CAPTCHA detection; self-hosts on plain dsh web without a desktop shell.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.