WeKnora
Tencent
Open-source LLM knowledge platform: turn raw documents into a queryable RAG, an autonomous reasoning agent, and a self-maintaining Wiki.
JanisKroja/dsh-web-search-crw
Self-hosted web search for DeepSeek Harness (dsh): a ctx.web provider plugin that routes web_search to your own CRW/Firecrawl-compatible server — no third-party API, no keys, no LLM tokens per search. Bring your own backend: CRW (Google via Camoufox) or any POST /v1/search implementation.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:JanisKroja/dsh-web-search-crw
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
Self-hosted web search for DeepSeek Harness (dsh):
a WebSearchProvider plugin for the harness web seam (ctx.web) that points
the model-facing web_search tool at your own
CRW-compatible
(Firecrawl-compatible) server instead
of a hosted search API. Single package — no companion plugins, no harness fork,
no build step. It is a client: a running backend is required, see
Requirements.
It registers one WebSearchProvider (id crw) into the harness web seam
(ctx.web) via the official provider convention — inject: ["web"] +
ctx.settings.installSection + ctx.web.registerSearchProvider — mirroring
the structure of the stock @deepseek-ai/dsh-web-search-deepseek plugin.
No stock harness code is modified; selection happens purely through the seam's
documented searchProvider config.
POST {baseURL}/v1/search {"query": "...", "limit": N}
and normalizes the Firecrawl-shaped response — {success, data: {results, answer?}},
where results is a flat array or a grouped {web, news, images} object — into
the seam's sources[] (snippet || description, publishedDate → publishedAt,
data.answer → content). Transport failures map to WEB_PROVIDER_ERROR (with
endpoint recovery hints), caller cancellation to WEB_ABORTED.
You need a running CRW/Firecrawl-compatible search server — this plugin ships
no search backend of its own; it is a client. Concretely, it requires a
server that implements POST /v1/search with the Firecrawl v1 search contract
(see What it calls for the exact request/response shapes
accepted).
Recommended backends:
/v1/search drives Google through
camofox-browser, a REST
wrapper around the Camoufox
anti-detect Firefox fork. The quick start is the repo's compose stack:
docker compose up -d (publishes the server on localhost:3000).apiKey or
CRW_SEARCH_API_KEY below).Other requirements:
apiKey (or CRW_SEARCH_API_KEY) to one of them — the plugin then sends
Authorization: Bearer <key>.# from this repo
npm run install:dsh # = bash scripts/install-to-dsh.sh
The script copies the package into $DSH_HOME/profiles/node_modules/dsh-web-search-crw
— the harness's shared module-resolution anchor (see @deepseek-ai/dsh-app-boot
profile docs). A copy is required, not a symlink: Node resolves through a
symlink's realpath, so a linked plugin would resolve its @deepseek-ai/* bare
imports by walking up from this repo instead of the harness's hoisted closure.
Then register the provider in your profile patch ($DSH_HOME/profiles/web/cordis.patch.yml):
- id: web
name: '@deepseek-ai/dsh-web'
config:
searchProvider: crw
fetchProvider: http
- insert:
- id: web-search-crw
name: dsh-web-search-crw
config:
baseURL: http://localhost:3000
limit: 5
timeoutMs: 60000
A web row patch replaces the whole config, so fetchProvider must be restated.
The web profile hot-reloads this file (patchReload: live), so config changes
need no restart — but a change to this plugin's lib/index.js does; see
Reload vs restart.
To switch back to the stock provider, set searchProvider: deepseek-official.
Settings namespace web-search-crw (also editable live under
Settings → Plugins → Plugin configuration → Web search (CRW)):
| Key | Default | Meaning |
|---|---|---|
baseURL |
http://localhost:3000 |
CRW base; /v1/search is appended. Env fallback: CRW_SEARCH_BASE_URL |
apiKey |
— | Optional bearer key. Env fallback: CRW_SEARCH_API_KEY |
limit |
5 |
Result-count fallback when the tool passes no maxResults |
timeoutMs |
60000 |
Per-search deadline. One Camofox Google SERP leg measures ~45 s cold, so the old 30000 default aborted real searches |
resolveRedirects |
true |
Unwrap Google's click-redirect wrappers (/url?q=, /goto?url=, /aclk, /imgurl) into the real destination |
resolveTimeoutMs |
4000 |
Per-wrapper deadline; a row that cannot be resolved keeps its original URL rather than being dropped |
timeoutMs is nested inside the host tool layer's own budget,
tool-web.searchTimeoutMs (default 30000), which bounds the same
web_search call first. Raise both together — a provider deadline of 60 s behind
a 30 s host deadline still aborts at 30 s.
Why resolveRedirects exists: Google's SERP anchors point at
google.com/goto?url=<opaque token>, not at the destination, and the token
base64-decodes to opaque protobuf bytes — the target URL only exists in the
redirect's Location. The harness fetch client refuses cross-origin redirects by
design (an SSRF guard), so an unwrapped link is the difference between a citable
source and an extra resolve round trip per result. Turn it off only if CRW itself
starts returning clean URLs.
Verify the click-redirect unwrap (stubbed fetch, no network — asserts the
/url?q= and /goto?url= paths, the Location and meta-refresh recovery
routes, second-hop rejection, dupe collapse, and the timeout fallback):
node scripts/verify-unwrap.mjs # offline assertions
node scripts/verify-unwrap.mjs --live # against the real CRW server; exits 1 on any leaked wrapper URL
Two install modes:
npm run install:dsh # copy — deploy-safe (default)
npm run install:dsh -- --link # symlink — local dev, live-edit
Copy mode puts the package physically under
$DSH_HOME/profiles/node_modules/dsh-web-search-crw, the harness's shared
module-resolution anchor, so its bare @deepseek-ai/* imports resolve through
Node's parent-walk to the harness's hoisted closure.
--link mode replaces that anchor entry with a symlink into this repo.
Because Node resolves through a symlink's realpath, a naive symlink would resolve
the plugin's bare host imports from this repo — so the script additionally links
@deepseek-ai/dsh-web and @deepseek-ai/schemastery into node_modules/ here,
pointing at the same realpaths the running harness loaded (Node dedupes by
realpath, preserving class/service identity — never npm install your own copies
of host packages; a shadowing second copy breaks Cordis service identity). Switch
back any time with plain copy mode. If this repo moves or dsh is reinstalled,
re-run the script to repair the links.
If the profile's package.json lists this plugin as a dependency — normally
"dsh-web-search-crw": "file:/path/to/this/repo", which is how the repo gets into
a profile in the first place — the package manager also materializes it at
$DSH_HOME/profiles/<profile>/node_modules/dsh-web-search-crw/ as a real
directory of hardlinked files, not a symlink into the repo.
Node resolves that profile-local copy before the anchor, so it shadows
$DSH_HOME/profiles/node_modules/dsh-web-search-crw. Editing this repo — or
installing into the anchor alone — then changes nothing the harness can see, and
restarting reloads the same stale copy. scripts/install-to-dsh.sh syncs lib/
into every profile-local copy it finds and tells you when the anchor is the one
being resolved; pnpm install --force inside the profile dir is the package-manager
native equivalent (it also picks up package.json, and needs the registry
reachable for the profile's other dependencies).
You can confirm which file is loaded:
grep -c classifyRedirectWrapper ~/.dsh/profiles/web/node_modules/dsh-web-search-crw/lib/index.js
patchReload: live on the web profile re-applies cordis.patch.yml config in
the running process. Nothing in cordis busts a plugin's module cache, so
lib/index.js is imported once at startup:
| Changed | Takes effect |
|---|---|
cordis.patch.yml rows, or this plugin's config (baseURL, limit, timeoutMs, resolveRedirects, resolveTimeoutMs) |
live, no restart |
lib/index.js (any code change) |
after restarting dsh web |
MIT
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: ai-search、search-provider、web-search。