dsh-web-searxng-search
Self-hosted SearXNG web search provider for DeepSeek Harness.
English | 中文
Points the web_search tool at a SearXNG instance you operate.
No API key, no vendor account, no per-query attribution.
Motivation
DeepSeek Harness ships a web search provider backed by the DeepSeek API, so every search is
billed and attributed to an API key. This plugin registers a second provider on the same
ctx.web seam and switches the seam to it. Search then runs against infrastructure you control
and requires no vendor credential.
Features
- Drop-in
WebSearchProvideron thectx.webseam — the built-inweb_searchtool keeps working. - No runtime dependencies of its own; the official
@deepseek-ai/*packages are peer dependencies served by the host. - Configurable timeout, language, safe-search level, categories and engine allow-list.
- Results deduplicated by URL — meta-search engines return the same page more than once.
- Distinguishes "the instance returned HTML" (JSON format not enabled) from "no results", with an actionable error message.
- Registration is an effect (
ctx.effect), so unload and HMR roll back cleanly. - 14 offline unit tests — no network required.
Requirements
| Component | Version / note |
|---|---|
| DeepSeek Harness | @deepseek-ai/dsh-web 0.1.x |
| Node.js | >= 22.19 |
| SearXNG | JSON format enabled (see below) |
Enable the SearXNG JSON API
The plugin reads the SearXNG search API. JSON
output is disabled by default; without it the instance answers 200 with an HTML page, which
the plugin reports as a configuration error rather than as "no results".
# settings.yml
search:
formats:
- html
- json
Verify the endpoint before installing:
curl -fsS 'http://localhost:8888/search?q=test&format=json' | head -c 120
Installation
# from npm
dsh plugin --profile web add dsh-web-searxng-search
# from GitHub
dsh plugin --profile web add github:kkgace/dsh-web-searxng-search
# from a local checkout
dsh plugin --profile web add /path/to/dsh-web-searxng-search
Then restart the web app:
dsh web
Once listed, the plugin can also be installed from Settings → Plugin Market.
Configuration
The bundle ships a working default of http://localhost:8888. Point it at your own instance
with a profile overlay — patch the existing row, never insert a new one:
# ~/.dsh/profiles/web/cordis.patch.yml
- id: web-search-searxng
config:
baseURL: 'https://searxng.example.com'
Alternatively, set SEARXNG_BASE_URL in the environment dsh runs in.
| Option | Type | Default | Description |
|---|---|---|---|
baseURL |
string |
http://localhost:8888 |
Root URL of the instance, without a trailing /search. |
timeoutMs |
number |
15000 |
Per-request timeout, 1000–120000 ms. |
language |
string |
— | UI language forwarded to the instance, e.g. en-US. |
safeSearch |
0 | 1 | 2 |
0 |
Off · moderate · strict. |
categories |
string |
— | Comma-separated SearXNG categories, e.g. general,it. |
engines |
string |
— | Comma-separated engine names to narrow the search. |
apiKey |
string |
— | Bearer token, only if the instance requires one. |
How it works
- Everything in Harness is a Cordis plugin, and capabilities are exposed as seams. Web search
is the
ctx.webseam. - The plugin implements the
WebSearchProvidercontract —id,available(): boolean(local and cheap, never touches the network) andsearch(req, signal?)— and registers itself withctx.web.registerSearchProvider(...)insidectx.effect(...). - Registration alone is not enough:
cordis.patch.ymlswitchesweb.searchProvidertosearxng. Without that step the seam stays on the built-in provider. - Failures are raised as
WebError(message, code, { cause })so the host routes them instead of silently falling back to another provider.
Development
npm test # node --test tests/*.test.mjs
The tests mock globalThis.fetch and never touch the network. The official @deepseek-ai/*
packages are peer dependencies provided by the host; to run the tests outside a Harness profile,
make them resolvable from node_modules.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
web_search still uses another provider |
web.searchProvider does not point at searxng, or dsh web was not restarted. |
duplicate loader entry id: web-search-searxng at boot |
The profile overlay inserts a row the bundle already inserts. Use the - id: … + config: patch form instead. |
| "No results" while the instance returns data | JSON format is not enabled on the instance. |
WEB_PROVIDER_CONFIGURED_UNAVAILABLE |
available() returned false — baseURL is not an absolute http(s) URL. |
WEB_PROVIDER_TIMEOUT |
The instance did not respond within timeoutMs. |
No comments yet. Be the first to write one.