Back to home

chinng-inta

dsh-web-search-searxng

No description

Stars
1
Language
TypeScript
Created
Aug 14, 2026
Updated
Aug 14, 2026

Introduction

dsh-web-search-searxng

A SearXNG-backed WebSearchProvider for the DeepSeek Harness web capability seam (ctx.web).

SearXNG is a self-hosted metasearch engine. One search here is a plain retrieval call against the instance's /search?format=json endpoint, so unlike the shipped DeepSeek provider it needs no API key and costs no model turn — the shipped provider issues a full Messages request with the native web_search server tool, paying latency and generated tokens for every search.

This is an implementation package: it registers a provider into ctx.web and does not register a model-facing tool. @deepseek-ai/dsh-tool-web owns web_search, its schema, its prompt guidance, and the result card. Installing this package makes that existing tool work against your own instance.

Install

dsh plugin --profile web add dsh-web-search-searxng

The package declares dsh.bundle, so this single command also activates it: the bundle patch inserts the provider row and selects it on the web row. Point it at your instance and restart:

export SEARXNG_URL=http://searxng.internal:8888

Verify the composition before booting:

dsh --profile web --dump-config | grep -A3 'id: web'

Your instance must serve JSON

SearXNG does not enable the JSON API by default. In the instance's settings.yml:

search:
  formats:
    - html
    - json

Without it the endpoint answers with the HTML result page, and this provider fails with a message naming the fix rather than a parse error.

A public instance is a poor backend: most refuse programmatic access (HTTP 403 from a bot filter) or rate-limit aggressively. Run your own.

Configuration

All keys are optional.

KeyDefaultMeaning
baseURL$SEARXNG_URLInstance root; /search is appended. Missing or non-http(s) makes the provider report unavailable rather than fail every search.
categoriesinstance defaultcategories= filter, e.g. ['news'].
enginesinstance defaultengines= filter, e.g. ['duckduckgo', 'brave'].
languageinstance defaultlanguage= filter, e.g. ja, en-US.
timeRangeunsettime_range= filter: day / week / month / year.
safesearchinstance defaultsafesearch=: 0 off, 1 moderate, 2 strict.
timeoutMs10000Resource backstop for one search.
maxSnippetChars500Per-source snippet cap.
headersnoneExtra request headers, e.g. for an instance behind an authenticating proxy.
- id: web-search-searxng
  name: 'dsh-web-search-searxng'
  config:
    baseURL: http://searxng.internal:8888
    language: ja
    categories:
      - general
      - news

Every search-shaping knob is a deployment setting, not a model argument. The seam's WebSearchRequest is deliberately just query + maxResults; provider-neutral controls (recency, domain filters, search depth) are named deferred work upstream. Keeping them in config is what makes this provider substitutable for the shipped ones.

timeoutMs is a resource backstop, not the model-facing tool-call budget — @deepseek-ai/dsh-tool-call-timeout-policy owns that via tool-web's searchTimeoutMs. Leave this below the tool budget so a slow instance surfaces as a provider failure rather than a tool timeout.

Provider selection

The bundle patch sets web.searchProvider: searxng. This is required, not opinionated.

The seam auto-selects only when exactly one registered provider is usable, and @deepseek-ai/dsh-web-search-deepseek reports usable whenever a credential resolver exists — which its own apply() always supplies — so it answers available() === true on a stock composition even with no key configured. Registering a second provider without naming one would make every search fail with WEB_PROVIDER_AMBIGUOUS.

Bundle layers apply before your profile's cordis.patch.yml, the home patch, and any --patch overlay, so you can always override the choice. But note that a patch replaces the targeted row's whole config: if you patch the web row yourself for anything else, restate searchProvider: searxng there too.

Mapping

SearXNGSeam
results[].urlsources[].url (required; results without one are dropped)
results[].titlesources[].title
results[].contentsources[].snippet, capped at maxSnippetChars
results[].publishedDatesources[].publishedAt
answers[]content, newline-joined; omitted when empty

Sources are deduplicated by URL, because a metasearch merges engines that routinely return the same page. Blank strings are treated as absent rather than emitted as empty fields — the seam's optional fields exist so an adapter never has to invent them.

truncated is always false from this provider: the seam owns maxResults enforcement, and reporting our own truncation would misattribute whose bound cut the list.

Errors

Failures are WebErrors the tool layer turns into a readable tool result.

SituationCode
Caller cancelledWEB_ABORTED
timeoutMs elapsedWEB_PROVIDER_ERROR
Non-2xx from the instance (403 carries a bot-filter hint)WEB_PROVIDER_ERROR
Response was not JSON (usually formats misconfiguration)WEB_PROVIDER_ERROR
Unparseable bodyWEB_PROVIDER_ERROR

available() is a cheap synchronous check — a parseable http(s) base URL — as the seam requires; it never touches the network.

Redirects are refused (redirect: 'error'). A self-hosted instance has no reason to redirect a search, and following one would send the query to a host the deployment never configured.

Known limitations

  • maxResults is not pushed down. SearXNG exposes no result-count parameter, so the instance returns its full first page and the seam truncates. This bounds tokens, not the instance's work.
  • publishedDate is usually absent. General web engines rarely date results; news engines usually do. Filter with categories: ['news'] if you need dates.
  • Infoboxes are not surfaced. They are structured entity cards rather than an answer to the query, so flattening them into content would present them as one.
  • No per-engine failure reporting. SearXNG reports unresponsive_engines[] on partial failures; the seam's result shape has nowhere to put it, so a degraded search looks like a thin one.

Compatibility

This packageDeepSeek Harness
0.1.x0.1.0-rc.6

The harness is a developer preview with breaking changes between release candidates, and its packages publish the active line under the next dist-tag (latest still points at the older 0.0.1-rc.1). Pin your harness version.

License

MIT