Tools & Capabilities
dsh-web-search-firecrawl
elves-ai/dsh-web-search-firecrawl · ★ 3
Firecrawl-backed web search provider for the built-in web_search tool, with a settings card for the API key and result limits.
Manifest details
安装
dsh plugin --profile web add github:elves-ai/dsh-web-search-firecrawlGitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
Screenshots

README
Table of contents
English | 中文
A Firecrawl-backed search provider for the DeepSeek Harness web capability seam (ctx.web): it makes the built-in web_search tool run on Firecrawl's search API instead of the shipped DeepSeek route.
The plugin is a Cordis function plugin that registers a WebSearchProvider (id: firecrawl) into the seam — it does not own ctx.web and does not register a model-facing tool (the tool schema stays with @deepseek-ai/dsh-tool-web). It calls Firecrawl's POST /v1/search endpoint and maps the flat data[] into the seam's normalized WebSearchResult.
The API key is configured in Plugins → Firecrawl plugin details, not a separate Settings navigation entry. The form follows Harness semantic colors in both light and dark themes; $FIRECRAWL_API_KEY remains available as a compatibility fallback.
Requirements
- DeepSeek Harness official packages on the
0.2.1-rc.5line, Cordis4.0.5, Schemastery3.18.5, and Node.js^22.19.0 || >=24.0.0. The old0.1.xconfiguration API is not supported; see the upgrade guide. Do not mix Harness release lines in one profile; check withnpm ls @deepseek-ai/dsh-web. - A Firecrawl API key. Get one at firecrawl.dev.
Install
Choose one of the following:
Option 1 — global dsh CLI
dsh plugin --profile web add @elves-ai/dsh-web-search-firecrawl@latest
Option 2 — deepseek-harness source checkout (there dsh is a pnpm workspace script, not a global binary; run it from the workspace root):
pnpm dsh plugin --profile web add @elves-ai/dsh-web-search-firecrawl@latest
dsh plugin forwards to pnpm inside the profile directory and appends the bundle to the profile's layer stack automatically. The bundle patch inserts the web-search-firecrawl plugin row and switches the web seam row to searchProvider: firecrawl (replacing the base bundle's deepseek-official), while preserving fetchProvider: http for page retrieval.
After installing, restart dsh web, then hard-refresh the page (Cmd/Ctrl+Shift+R) so the browser half and its settings page load:
dsh web
Verify: Plugins → Firecrawl plugin details should show the configuration form, and asking the agent for a web search should route through Firecrawl. Without a key, a search fails with WEB_PROVIDER_CONFIGURED_UNAVAILABLE.
To keep DeepSeek search as the default and enable Firecrawl per instance instead, restate the web row in your profile's own cordis.patch.yml afterwards — the last write wins per row. The base bundle pins searchProvider: deepseek-official, so $DSH_WEB_SEARCH_PROVIDER selects the provider only when the web row sets no searchProvider: restate web with only its fetch selection (- id: web + config: { fetchProvider: http }) for env selection, or with searchProvider: deepseek-official to pin DeepSeek.
Update
Global dsh CLI:
dsh plugin --profile web update --latest @elves-ai/dsh-web-search-firecrawl
deepseek-harness source checkout — run from the workspace root:
pnpm dsh plugin --profile web update --latest @elves-ai/dsh-web-search-firecrawl
dsh plugin update forwards to pnpm inside the profile directory; --latest upgrades to the latest published version regardless of the currently installed range. After updating, restart dsh web, then hard-refresh the page (Cmd/Ctrl+Shift+R). If the plugin is installed in another profile, replace web with that profile name.
Uninstall
Global dsh CLI:
dsh plugin --profile web remove @elves-ai/dsh-web-search-firecrawl
deepseek-harness source checkout — run from the workspace root:
pnpm dsh plugin --profile web remove @elves-ai/dsh-web-search-firecrawl
dsh plugin remove forwards to pnpm inside the profile directory and also removes the package from dsh.profile.bundles. Once the bundle layer is gone, the web seam row reverts to the base bundle's deepseek-official provider. Restart dsh web for the removal to take effect in the running process.
The command removes the package and its mount, but not settings-page values. If you also want to delete the saved API key, clear it on the Firecrawl page before uninstalling. If the plugin was installed into another profile, replace web with that profile name.
Switching search providers
Installing the plugin switches the web seam's searchProvider to firecrawl, so Firecrawl replaces the original web search by default. In Plugins → Firecrawl plugin details, the Use Firecrawl switch at the top selects between the two providers live:
- On (default): Firecrawl
POST /v1/searchis used. - Off: the registered
firecrawlprovider delegates to the original DeepSeek search implementation, so the model keeps using the shipped DeepSeek search route; DeepSeek's endpoint/model still resolve from the originalweb-search-deepseeksettings section.
The fallback retains the official implementation's account authentication, credential resolution, and web/deepseek-search-llm-request Session logging. The switch applies immediately without a restart. Firecrawl credentials and request settings are preserved while off.
Configuring the API key
Open Plugins → Firecrawl plugin details:
- Paste
fc-...into the API Key field and save. The eye button beside the field toggles plain-text visibility for the current draft. - The key is written to the profile's
cordis.patch.ymlwithrole('secret'): the saved literal never rides a response; the page only shows whether a key is configured. - Saving with the field blank keeps the current key; use Clear to remove the user override and restore the composition value. A key supplied by the composition or environment remains available.
- Changes apply immediately — the Loader updates
Volatilereferences and the registered provider uses the new values on the next search, no restart required.
If the settings-page key is blank, the provider still falls back to $FIRECRAWL_API_KEY:
export FIRECRAWL_API_KEY=fc-...
dsh --profile web
Config
| Key | Default | Meaning |
|---|---|---|
useFirecrawl |
true |
Whether to use Firecrawl; false switches back to the original DeepSeek web search. |
apiKey |
(blank) → $FIRECRAWL_API_KEY |
Firecrawl API key. Written as a secret by the settings page and never echoed back. Blank falls back to the env var; with neither present the provider is unavailable. |
baseURL |
https://api.firecrawl.dev |
Endpoint base; /v1/search is appended. An unparseable value makes the provider unavailable. |
limit |
(unset) | Default result count when a request carries no maxResults. Unset sends no default. Must be a positive integer. |
maxSnippetChars |
600 |
Upper bound on characters kept from one description when mapping snippet. Must be a positive integer. |
Every field is edited through Harness's standard authenticated settings Remote API; the plugin no longer mounts a separate /firecrawl/api HTTP endpoint. The Config schema is projected automatically, and every field uses .volatile() so saves do not remount the provider. Bundle config is inherited beneath the profile's cordis.patch.yml overrides:
# profile cordis.patch.yml (optional; usually not needed)
- id: web-search-firecrawl
name: '@elves-ai/dsh-web-search-firecrawl'
config:
limit: 10
Mapping
Firecrawl returns a flat data[] and no generated answer, so content is omitted. Each entry maps to a WebSearchSource: url ← url, title ← title, snippet ← description trimmed and bounded to maxSnippetChars (an entry without a non-blank description keeps its citeable URL-only source). A request's maxResults wins over the configured limit default and is sent as Firecrawl's limit for a cost/latency optimization; the final bound is enforced by the seam. Provider failures (HTTP errors, network failure, unparseable or wrong-shape bodies, a success: false envelope) surface as WebError WEB_PROVIDER_ERROR; an aborted request surfaces as WEB_ABORTED. HTTP redirects are rejected before the Location target is contacted.
Model Experience
Indirect, through @deepseek-ai/dsh-tool-web: the model sees the maxResults-bounded URLs, titles, and bounded descriptions, plus the consumer's error wrapper around Firecrawl search aborted, Firecrawl search request failed: <error>, and Firecrawl returned an unprocessable response body: <error>. Generated answers and provider-private fields stay out of context.
Known Limitations
- A snippet is Firecrawl's
descriptionexcerpt, not a search-engine summary — it can be long page markdown, so the provider bounds it tomaxSnippetChars; entries without a description carry no snippet at all (URL-only). - Only
limit/maxSnippetCharsare exposed — Firecrawl's other controls (language, country, recency, location, targeting, scrape options) wait on provider-neutral Service Definition fields in@deepseek-ai/dsh-web. - Abort classification is error-shape-based — only a
DOMExceptionnamedAbortErrormaps toWEB_ABORTED; an abort carrying a custom reason surfaces asWEB_PROVIDER_ERROR.
Development
pnpm install
pnpm run typecheck
pnpm test # unit suite (no network)
FIRECRAWL_API_KEY=... pnpm run test:e2e # live-API smoke, self-skips without the key
pnpm run build # emits lib/ (ESM host) + lib/client.js (browser) + lib/types/ (d.ts)
pnpm run test:built # smoke-test built Host entries and the browser module factory
License
MIT