dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
A configurable third-party web-search plugin for DSH (DeepSeek Harness)/一个用于DSH(DeepSeek Harness)的可配置第三方网络搜索插件
English | 简体中文
A third-party web search plugin for DSH (DeepSeek Harness). It replaces the built-in DeepSeek-only search with configurable search engines, gives each engine its own settings, and can fetch full pages so the model can both search and read.
Built for DeepSeek Harness.
Supports the latest DSH, and stays compatible with the older settings API (from 0.1.0-rc.6):
| DSH settings service | How to recognise it | What this plugin does |
|---|---|---|
≥ 0.1.7-alpha.1 | dsh-settings exports SettingsForms; the settings document serves each profile entry's own Config by entry id and projects only .volatile() fields | every editable Config field is .volatile(); the client uses ctx.configForms.get('web-search-thirdparty'); the namespace is the composed entry id |
0.1.2 – 0.1.6-alpha.2 | exports SettingsProvider with installSection | Config is not volatile; the client uses ctx.settingsScope.bind({ namespace: 'dsh-web-search-thirdparty' }) |
≤ 0.1.1-rc.2 | top-level installSettingsSection helper | same as above, registration goes through the helper |
The generation is detected at runtime (settingsGeneration()) and both paths are covered by tests.
Note: if your DSH is ≥ 0.1.7 while @deepseek-ai/schemastery is below 3.18.3 (.volatile()
only exists from 3.18.3), the plugin logs an explicit warning and the settings page cannot save.
Check the runtime status line under the key input (served by
GET /api/web-search-thirdparty/config):
schemastery version too old warning in the log; whether the plugin is actually listed in the
profile's dsh.profile.bundles; and whether dsh web was restarted as the installer requires.0.1.0-rc.6 or later, with a working dsh web profile.build:client).By default DSH mounts only the official DeepSeek search provider, mounts no fetch provider, and keeps
web_fetch disabled (upstream's rationale: the model picks the request target, and SSRF protection is
the fetch provider's job). This plugin lets you use the search source you actually want — a self-hosted
SearXNG, Tavily, Bing, Brave, Serper, or Google — and ships an SSRF-guarded fetch provider, so
web_search and web_fetch are both enabled. Everything is configured from the browser settings page.
Upstream also ships optional official providers:
@deepseek-ai/dsh-web-search-exa,@deepseek-ai/dsh-web-search-perplexity, and@deepseek-ai/dsh-web-fetch-http. If Exa/Perplexity is all you need, use those. This plugin's difference is "several engines (including keyless SearXNG) + per-engine settings + self-hosted instance / internal proxy support + circuit breaker and usage stats".
Retry-After).truncated flag.web_fetch provider (HTML cleaned to Markdown, every redirect hop re-checked against SSRF).GET /api/web-search-thirdparty/stats, including cache counters and circuit state).GET /api/web-search-thirdparty/config reports, per engine,
whether it is actually usable right now (through the same credential chain real searches use);
the settings page renders it as the runtime status line.| id | service | API key | config |
|---|---|---|---|
searxng (default) | SearXNG (self-hosted or public) | no | searxngBaseURL |
tavily | Tavily | yes | tavilyApiKey / TAVILY_API_KEY |
serper | Serper (Google SERP) | yes | serperApiKey / SERPER_API_KEY |
brave | Brave Search | yes | braveApiKey / BRAVE_API_KEY |
bing | Bing Web Search | yes | bingApiKey / BING_SEARCH_API_KEY |
google-cse | Google Custom Search | yes (key + cx) | googleApiKey + googleSearchEngineId |
Note 1: Microsoft retired the Bing Web Search API in 2025. The
bingengine is kept for endpoints that stay protocol-compatible (Azure Grounding gateways or self-hosted mirrors) viabingEndpoint.Note 2: the default
https://searx.beis only a public-instance example. Public instances commonly disable JSON output or run bot detection — a self-hosted SearXNG is recommended (addjsontosearch.formatsin itssettings.yml) withsearxngBaseURLpointed at it.
Keys can be set in the settings UI, stored in the DSH credentials service, or exported as environment variables; resolution order is literal → credentials service → launch environment.
Installing through the plugin-manager CLI (dshpm) is recommended: it runs a quality gate and health
checks (full dependency scan, bundle patch validation, rollback on failure). dshpm comes from
dsh-web-plugin-manager — install the manager
first if you have not.
# Option 1: from npm
dshpm install dsh-web-search-thirdparty --profile web
# Option 2: from GitHub (lib/ is committed, no local build needed)
dshpm install github:sunx16963-design/dsh-web-search-thirdparty --profile web
# Option 3: build locally (copy the build INTO the profile first — see the warning below)
git clone https://github.com/sunx16963-design/dsh-web-search-thirdparty.git
cd dsh-web-search-thirdparty
npm install
npm run build
# Crucial: the target must live inside the profile, or the link cannot reach the host packages
cp -r . "$DSH_HOME/profiles/web/vendor/dsh-web-search-thirdparty"
dshpm install "$DSH_HOME/profiles/web/vendor/dsh-web-search-thirdparty" --profile web
Restart dsh web after installing so the settings page appears. Upgrade / uninstall:
dshpm update dsh-web-search-thirdparty --profile web
dshpm remove dsh-web-search-thirdparty --profile web
1) git / local sources make the quality gate pause and ask for keys — just answer empty.
This plugin needs no keys at install or build time (keys are entered in the settings page). But the
manager scans READMEs for XXX_API_KEY / xxxApiKey-shaped names (capped at 8, which is why exactly
tavily/serper/brave/bing show up), treats them as install-time env requirements, and pauses. Re-run with
--env KEY= (empty = skip), e.g.:
dshpm install github:sunx16963-design/dsh-web-search-thirdparty --profile web \
--env tavilyApiKey= --env TAVILY_API_KEY= --env serperApiKey= --env SERPER_API_KEY= \
--env braveApiKey= --env BRAVE_API_KEY= --env bingApiKey= --env BING_SEARCH_API_KEY=
2) A local path outside the profile directory breaks the whole profile at boot.
dshpm install /abs/path/outside records link:/that/path; the plugin resolves @deepseek-ai/* from its
own location (the host provides them through profiles/node_modules), so an out-of-profile target cannot
reach them and boot fails with
failed to import loader entry … Cannot find package '@deepseek-ai/cordis' — the profile will not start.
Copy the build into profiles/<name>/vendor/ before installing (as option 3 does).
Also note: when the package name is published on npm, a github: source is mapped to the npm version
(the log says "installed from npm"), so a stale npm release wins over the repo; and a .tgz path is not
supported by this manager version.
What you save in the settings page lands in the current profile's patch document. The namespace depends on the DSH generation:
web-search-thirdparty.dsh-web-search-thirdparty
(written to settings.yaml).web-search-thirdparty: # dsh-web-search-thirdparty on ≤ 0.1.6
provider: searxng
searxngBaseURL: https://searx.be # or your own instance
maxResults: 8
mergeResults: false
maxPerDomain: 2
relevanceSort: false
cacheEnabled: true
cacheTtlMs: 60000
retryCount: 1
retryBackoffMs: 250
# default true: register the bundled web_fetch provider.
# Set false to hand web_fetch back to the host's provider
# (also remove the `fetchProvider` line from this plugin's bundle patch).
enableFetchProvider: true
On DSH ≥ 0.1.7 every configuration field is
volatile(), so a settings-page write takes effect on the next search without a restart.
The settings form covers the common options (provider, keys/instance URL, result count, merge, per-domain
cap, relevance sort, cache, and each engine's advanced params). These advanced options are not in the
form and only take effect from settings.yaml / the composition layer (they are also included in the
"restore defaults" reset list):
timeoutMs, snippetMaxLength, retryCount, retryBackoffMs, extraHeadersJson,
fallbackProviders, maxProviderQueries, maxProviderConcurrency,
circuitEnabled, circuitFailureLimit, circuitCooldownMs, statsEnabled,
fetchAllowPrivate, fetchMaxBodyChars, fetchTimeoutMs, fetchUserAgent.
web_fetch refuses private / loopback / link-local / cloud-metadata (169.254.169.254) targets and
special-purpose ranges: IPv4 0.0.0.0/8, 100.64.0.0/10 (CGNAT), 192.0.0.0/24, 192.88.99.0/24,
198.18.0.0/15, 240.0.0.0/4; IPv6 ::1, fc00::/7, fe80::/10, ff00::/8, 2002::/16 (6to4),
2001::/32 (Teredo), 2001:db8::/32, 64:ff9b::/96 (NAT64), and every IPv4-mapped form.follow does
not re-check Location).fetchAllowPrivate: true explicitly if you need to fetch from an internal network.fetchAllowPrivate with untrusted input.npm install
npm run build:host # compiles the host plugin (lib/index.js)
npm run build:client # bundles the browser UI (lib/client.js)
npm run typecheck
```sh
npm install
npm run build:host # compiles the host plugin (lib/index.js)
npm run build:client # bundles the browser UI (lib/client.js)
npm run typecheck
npm test
npm pack
Unit tests use the repo's own platform shims, so they cannot catch an upstream API removal.
This script loads the build into a real cordis + dsh-web + dsh-settings-file stack and asserts:
apply does not throw, the settings namespace registers, ctx.web.search() normalizes, and
ctx.web.fetch() retrieves:
mkdir -p /tmp/dsh-e2e && cd /tmp/dsh-e2e
npm init -y >/dev/null && npm pkg set type=module
npm i --legacy-peer-deps @deepseek-ai/cordis@4 @deepseek-ai/dsh-web@0.1.5-rc.2 \
@deepseek-ai/dsh-settings@0.1.5-rc.2 @deepseek-ai/dsh-settings-file@0.1.5-rc.2 \
@deepseek-ai/dsh-credentials@0.1.5-rc.2 @deepseek-ai/dsh-launch-environment@0.1.5-rc.2 \
@deepseek-ai/dsh-llm@0.1.5-rc.2 @deepseek-ai/schemastery@3
# peers are not installed with dependencies: add whatever @deepseek-ai/* package it reports
node ./scripts/e2e-real-host.mjs /tmp/dsh-e2e
Point it at 0.1.1-rc.2 and friends to exercise the legacy settings path (the script prints which
generation the stack uses).
Sources are split by responsibility (src/index.ts keeps engines, facade, fetch provider, routes, apply):
| file | role |
|---|---|
src/engine-spec.ts | single engine table (providers, labels, credential inputs, endpoints, advanced params, test-route mapping, reset fields) |
src/config.ts | config shape and schemastery schema |
src/text.ts, src/html.ts | result normalization, snippet cleaning, entity decoding, HTML→Markdown (pure functions) |
src/net.ts | SSRF validation and hop-by-hop redirects |
src/state.ts | result cache, concurrency, circuit breaker, usage stats |
src/settings-compat.ts | two-generation settings-registration compatibility layer |
A fresh clone builds with public npm packages only: @deepseek-ai/* compile-time types come from the
repo's ambient shims, while the runtime packages are provided by DSH (linked through the profile, so no
second copy appears).
Engine metadata lives in a single shared table, src/engine-spec.ts (it drives the provider list,
labels, credential inputs, endpoints, advanced-params form, test-route mapping and reset fields on both
host and browser sides). Adding an engine takes two steps:
src/index.ts and add it to ENGINES;src/engine-spec.ts.tests/engine-spec.test.ts cross-checks both directions to prevent drift.
Other Cordis plugins can inject the web-search-thirdparty service to register their own search source
(built-in engine ids are a reserved namespace — third parties should use their own id):
export const inject = ['web-search-thirdparty']
function apply(ctx) {
ctx.get('web-search-thirdparty').register({
id: 'my-source',
label: 'My Source',
search: async ({ query, maxResults, config }, signal) => ({
sources: [{ url, title, snippet }],
content: 'optional answer',
}),
})
}
BSD-3-Clause. See LICENSE.