English · 简体中文
One fallback chain. Eight providers.
dsh-web-search is a static Cordis plugin for the DeepSeek Harness that adds a configurable, multi-provider web search back-end. Each query walks the configured provider order and falls back to the next on failure or empty results. DuckDuckGo needs no API key and acts as the final fallback, so the chain always has a working link.
The plugin routes the harness's native web_search tool through its own provider chain — replacing the built-in deepseek-official backend — so the built-in web card rendering delivers multi-provider fallback, configured API keys, and DuckDuckGo as a keyless last resort.
Design reference: this multi-provider web-search approach is adapted from Oh My Pi (OMP).
Why dsh-web-search?
| Capability | What it changes |
|---|---|
| 8 providers, one chain | Tavily, Brave, Exa, Firecrawl, Jina, Kagi, SearXNG, DuckDuckGo — any order, any subset. |
Native web_search integration |
Patch override routes the harness's native web_search tool through this plugin's multi-provider fallback chain, replacing the built-in deepseek-official backend. Access your configured API keys and DuckDuckGo as a keyless last resort — all through the native web card UI. |
| In-app credential management | API keys and SearXNG endpoints live in harness credential records, managed from a dedicated "Web Search Providers" settings page — save, clear, test, and drag-to-reorder. |
| Fail-loud | When the patch is not applied, the native web_search reports WEB_PROVIDER_AMBIGUOUS rather than silently degrading. |
| No build step | Pure ESM source loaded directly; the browser half ships as a hand-written factory bundle. |
Table of Contents
- Requirements
- Installation / Loading
- Quick Start
- Configuration
- Providers
- Architecture / Project Layout
- Development / Testing
- License
Requirements
- Node.js
^22.19or>=24 - deepseek-harness source workspace (for local
--patchloading) or an installeddshCLI (for the published npm package)
Host: DeepSeek Harness 0.1.2 alpha train (required)
This plugin calls the host over the Typert Remote protocol and imports RemoteError from @deepseek-ai/dsh-typert-protocol (present since 0.1.2-alpha.2), so the host must run a 0.1.2 alpha build — pin it explicitly:
npm install --global @deepseek-ai/dsh@0.1.2-alpha.2
Warning: npm's
latesttag for@deepseek-ai/dshis currently0.1.1-rc.2— the old RC train, which lacksRemoteError. A barenpx @deepseek-ai/dsh webornpm install --global @deepseek-ai/dshinstalls that old build and crashes withRemoteError. Pin@0.1.2-alpha.2explicitly — the new train lives on thealphatag (currently0.1.2-alpha.3).
Peer dependencies (all optional, installed with the package):
Package Version @deepseek-ai/dsh-api-remotes^0.1.2-alpha.2@deepseek-ai/dsh-tools^0.1.2-alpha.2@deepseek-ai/dsh-typert-protocol^0.1.2-alpha.2@deepseek-ai/dsh-web^0.1.2-alpha.2@deepseek-ai/cordis^4.0.2
Installation / Loading
Local development (--patch, loads source directly)
Run inside the deepseek-harness source workspace (where the dsh launcher and @deepseek-ai/* packages live):
pnpm dsh web --patch D:/development/dsh-web-search/patch.web.yml
web is a hard-coded alias for --profile web in the dsh launcher. The patch overlay (patch.web.yml) inserts src/index.js as a plugin row in the web profile and sets searchProvider: dsh-web-search on the native web row so its web_search tool routes through this plugin. No build or bundling is required:
- The relative row path is anchored to the patch file's directory and resolved to a
file://URL, which Node's native ESM loads directly. - The browser half is discovered via
dsh.clientmanifest +exports["./client"]in this project'spackage.json, pointing atsrc/client/bundle.js(a hand-written__ModuleLoader__factory, no bundler).
Once published, install it persistently into the web profile with dsh plugin --profile web add @deepseek-ai/dsh-web-search (the package declares dsh.bundle.patch, so dsh plugin add activates it as a profile bundle); keep using --patch for local development.
Published (installs from npm)
Host first, then plugin — the host must be pinned to the 0.1.2 alpha train (see Requirements):
npm install --global @deepseek-ai/dsh@0.1.2-alpha.2 # host, 0.1.2 alpha train (required)
dsh --version
dsh plugin --profile web add @deepseek-ai/dsh-web-search # resolves @latest
dsh plugin add resolves the plugin's @latest dist-tag and, because the package declares dsh.bundle.patch, activates it as a profile bundle.
Quick Start
Once loaded, the harness's native web_search tool is backed by this plugin's multi-provider fallback chain (replacing the built-in deepseek-official backend). The patch override (or the equivalent config on the web row) sets searchProvider: dsh-web-search. Results render with the native web card UI, and the chain falls back to deepseek-official only when no plugin provider returns usable results.
No separate tool is needed — the native web_search tool is the sole entry point. It supports site: domain filtering in the query string (passed through to the provider) and respects the configured result count. The settings page lets you manage API keys, reorder providers, and test connections.
Configuration
Credentials
All provider secrets live in harness credential records under the dsh-web-search/ scope, managed from the settings page — no environment variables required.
- API key providers — stored as an
api-keyrecord, e.g.dsh-web-search/tavily. - SearXNG — stored as a
grantrecord carrying the instanceendpoint. - DuckDuckGo — no key; always available.
Note: do not reference environment variable names (e.g.
TAVILY_API_KEY) as credential refs for keys — the launching environment treats them as read-only and would shadow any saved value. Records must be{kind: 'api-key'}or{kind: 'grant'}, and keys must contain a/, otherwise credential parsing fails and all set/unset operations throw.
Provider order and limits
- Order — provider fallback order is configurable; the settings page provides drag-to-reorder, stored in a
grantrecord (dsh-web-search/config). - Results per query — 5 by default (stays under the native
maxResultscap so no truncation warning is triggered).
Settings page
The plugin registers an isolated settings section, Web Search Providers (id web-search-providers), separate from the native web search config page. From there you can:
- save or clear a provider's API key / endpoint
- test the connection to a provider
- reorder the fallback chain by dragging
The page talks to the host over the plugin's websearch Remote namespace (list / setKey / unsetKey / setOrder / testProvider).
Providers
| ID | Label | Kind | How to activate |
|---|---|---|---|
tavily |
Tavily | API key | Set a Tavily API key |
brave |
Brave | API key | Set a Brave API key |
exa |
Exa | API key | Set an Exa API key |
firecrawl |
Firecrawl | API key | Set a Firecrawl API key |
jina |
Jina | API key | Set a Jina API key |
kagi |
Kagi | API key | Set a Kagi API key |
searxng |
SearXNG | Endpoint | Set a SearXNG instance endpoint |
duckduckgo |
DuckDuckGo | None | Always available (default final fallback) |
Architecture / Project Layout
dsh-web-search/
├── patch.web.yml # --patch overlay: inserts src/index.js into the web profile
├── src/
│ ├── index.js # Static plugin host entry: ctx.web provider / remote ops / fetch transport
│ ├── host-core.js # Host-side pure functions (credentials, query parsing, per-provider request/response normalization)
│ ├── interaction.js # Settings-page interaction state machine (pure reducer, unit-tested)
│ ├── remote.js # websearch Remote namespace host (WebSearchController)
│ └── client/
│ └── bundle.js # Browser half: hand-written __ModuleLoader__ factory bundle (no bundler)
├── tests/
│ ├── host-core.test.mjs # Host-core pure function tests
│ ├── interaction.test.mjs # Reducer interaction tests
│ ├── remote-contract.test.mjs # Remote RPC contract tests
│ └── client-bundle.smoke.mjs # Client bundle factory contract smoke test
└── package.json # exports["./client"] + dsh.client manifest
Design highlights:
- Provider registry — declared in
PROVIDER_SPECS;resolveCandidates()orders providers by configured order/excludes. - Fallback chain —
executeSearch()tries providers in order, checks credential availability first, falls back on failure/empty results, and returns an error result (does not throw) when everything fails. - HTTP transport — native
fetchin the host realm. ctx.webinjection — registers a provider with iddsh-web-searchunconditionally; selection is decided by the web row config'ssearchProvider. When the patch is not applied, the nativeweb_searchthrowsWEB_PROVIDER_AMBIGUOUS(fail-loud).- Host ↔ client RPC — the static plugin uses the Typert Remote protocol: host methods are registered via
ctx.typert.registerwithsrc-jsoncodecs; the client bundle mounts thewebsearchnamespace itself viactx.remote.$mount.
Development / Testing
Fresh clone: git clone → pnpm install (installs the @deepseek-ai/* peer/dev deps from the registry, see .npmrc) → the commands below.
npm test # 127 pure-function tests (zero dependencies, standalone clone)
npm run test:rpc # 12 environment-dependent tests (resolves independently installed @deepseek-ai/*)
npm run prepublishOnly # full 139 before publishing
139 tests split into two tiers:
| Tier | Suite | File | Count |
|---|---|---|---|
| Pure | Host core | tests/host-core.test.mjs |
90 |
| Pure | Interaction | tests/interaction.test.mjs |
37 |
| Env | Remote contract | tests/remote-contract.test.mjs |
11 |
| Env | Client bundle smoke | tests/client-bundle.smoke.mjs |
1 |
npm test runs the 127 pure-function tests. These import only node:* and ../src/host-core.js / ../src/interaction.js — both of which are zero-dependency pure ESM modules. A standalone clone without the deepseek-harness workspace can run npm test with no setup.
npm run test:rpc runs the 12 environment-dependent tests. These import @deepseek-ai/dsh-typert-protocol and react, resolved through the independently installed @deepseek-ai/* packages (pnpm install pulls them from the registry, no harness junction needed). A new clone can install and run the full test suite without the deepseek-harness workspace.
npm run prepublishOnly runs both tiers (all 139 tests) before publishing. All tests are plain Node scripts — no test framework.
License
MIT © DeepSeek
No comments yet. Be the first to write one.