Bundle
dsh-web-searxng-search
Self-hosted SearXNG web search provider for DeepSeek Harness (ctx.web). No API key, no vendor account.
- Source
- kkgace
- License
- MIT
- Updated
- Updated 9 hours ago
Readme
# dsh-web-searxng-search
**Self-hosted SearXNG web search provider for DeepSeek Harness.**
[](LICENSE)


English | [中文](README.zh.md)
Points the `web_search` tool at a [SearXNG](https://docs.searxng.org/) 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 `WebSearchProvider` on the `ctx.web` seam — the built-in `web_search` tool 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](https://docs.searxng.org/dev/search_api.html). 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".
```yaml
# settings.yml
search:
formats:
- html
- json
```
Verify the endpoint before installing:
```bash
curl -fsS 'http://localhost:8888/search?q=test&format=json' | head -c 120
```
## Installation
```bash
# 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:
```bash
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**:
```yaml
# ~/.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
1. Everything in Harness is a Cordis plugin, and capabilities are exposed as *seams*. Web search
is the `ctx.web` seam.
2. The plugin implements the `WebSearchProvider` contract — `id`, `available(): boolean` (local
and cheap, never touches the network) and `search(req, signal?)` — and registers itself with
`ctx.web.registerSearchProvider(...)` inside `ctx.effect(...)`.
3. Registration alone is not enough: `cordis.patch.yml` switches `web.searchProvider` to
`searxng`. Without that step the seam stays on the built-in provider.
4. Failures are raised as `WebError(message, code, { cause })` so the host routes them instead of
silently falling back to another provider.
## Development
```bash
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 `insert`s 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`. |
## License
[MIT](LICENSE)
Install
dsh plugin --profile web add github:kkgace/dsh-web-searxng-search#82cd8bd2ea355c0065298d32f3acc8e8fa8129cf
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-web-searxng-search from the hub