Skip to content
dsh.fish
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: MIT](https://img.shields.io/github/license/kkgace/dsh-web-searxng-search)](LICENSE)
![Node](https://img.shields.io/badge/node-%3E%3D22.19-informational)
![dsh plugin](https://img.shields.io/badge/dsh-plugin-4b5563)

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

Source