返回目录
文件与数据 插件

dsh-web-search-crw

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.

Stars
0
Forks
0
Issues
0
更新
7 天前

PROJECT TOPICS

项目标签

INSTALL REFERENCE

安装参考

未验证
dsh plugin --profile web add github:JanisKroja/dsh-web-search-crw

该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。

PROJECT README

README

dsh-web-search-crw

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.

What it calls

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.

Requirements

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:

  • CRW — the reference backend this plugin was built for. A Rust, Firecrawl-compatible search/scrape/crawl server whose /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).
  • Any Firecrawl-compatible implementation of the search endpoint — self-hosted or hosted. Consult the Firecrawl search API reference; note that hosted Firecrawl also requires a real API key (set apiKey or CRW_SEARCH_API_KEY below).

Other requirements:

  • dsh web profile, Node ≥ 22.19 (same floor as dsh itself)
  • No API key needed while the server runs without configured keys (the default for local compose stacks). Once the server has API keys configured, set apiKey (or CRW_SEARCH_API_KEY) to one of them — the plugin then sends Authorization: Bearer <key>.

Install

# 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.

Configuration

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.

Development

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.

The copy that actually loads (read this if edits seem to vanish)

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

Reload vs restart

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

License

MIT

CLASSIFICATION EVIDENCE

分类依据

项目类型插件
功能分类文件与数据
规则置信度

系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: ai-search、search-provider、web-search。