{"_id":"@creait/dsh-web-search-searxng","_rev":"2-2e5b83664a66cb593e753fbfc9756a5d","name":"@creait/dsh-web-search-searxng","dist-tags":{"rc":"0.1.0-rc.7","latest":"0.1.0"},"versions":{"0.1.0-rc.7":{"name":"@creait/dsh-web-search-searxng","version":"0.1.0-rc.7","keywords":["dsh","deepseek-harness","cordis","plugin","searxng","web-search","self-hosted"],"author":{"name":"Francesco G","email":"francesco@creait.nl"},"license":"MIT","_id":"@creait/dsh-web-search-searxng@0.1.0-rc.7","maintainers":[{"name":"creait","email":"francesco@creait.nl"}],"homepage":"https://github.com/CREAIT-nl/dsh-plugins/tree/main/web-search-searxng#readme","bugs":{"url":"https://github.com/CREAIT-nl/dsh-plugins/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"dist":{"shasum":"4476d7ba7312bfee31ede601cd9d36a38dcf92ed","tarball":"https://registry.npmjs.org/@creait/dsh-web-search-searxng/-/dsh-web-search-searxng-0.1.0-rc.7.tgz","fileCount":5,"integrity":"sha512-xtjSJoqpR5ZidL07hmbOVpe6ziXs/zOAah7ppDpkDQBoM7mgo+wXbmu/KZ1Verhxxw8FtxcAZBXR//foF9sOBQ==","signatures":[{"sig":"MEUCIQDMu/pxcdDuUDH0sgkW5TZsOE+aDYiMmGjoUJQY38l1ZgIgBF0LtS9qORgkKyNR8pNmTXk/c5oRkWtg9Mw9ZoKG4Do=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":30051},"main":"lib/index.js","type":"module","engines":{"node":">=20"},"exports":{".":{"default":"./lib/index.js"},"./package.json":"./package.json"},"gitHead":"488ad11d2a6582854dc98b84feacf32e0a2d2337","scripts":{"test":"node --test test/*.test.js"},"_npmUser":{"name":"creait","email":"francesco@creait.nl"},"repository":{"url":"git+https://github.com/CREAIT-nl/dsh-plugins.git","type":"git","directory":"web-search-searxng"},"_npmVersion":"11.11.0","description":"SearXNG-backed search provider for the DeepSeek Harness web capability seam (ctx.web)","directories":{},"_nodeVersion":"25.8.1","dependencies":{"@deepseek-ai/schemastery":"^3.18.1"},"_hasShrinkwrap":false,"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-web":"^0.1.0-rc.7"},"_npmOperationalInternal":{"tmp":"tmp/dsh-web-search-searxng_0.1.0-rc.7_1787577876595_0.14603706192945975","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@creait/dsh-web-search-searxng","description":"SearXNG-backed search provider for the DeepSeek Harness web capability seam (ctx.web)","version":"0.1.0","type":"module","main":"lib/index.js","exports":{".":{"default":"./lib/index.js"},"./package.json":"./package.json"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"license":"MIT","engines":{"node":">=20"},"peerDependencies":{"@deepseek-ai/dsh-web":"^0.1.0-rc.7","@deepseek-ai/cordis":"^4.0.1"},"dependencies":{"@deepseek-ai/schemastery":"^3.18.1"},"keywords":["dsh","deepseek-harness","cordis","plugin","searxng","web-search","self-hosted"],"repository":{"type":"git","url":"git+https://github.com/CREAIT-nl/dsh-plugins.git","directory":"web-search-searxng"},"homepage":"https://github.com/CREAIT-nl/dsh-plugins/tree/main/web-search-searxng#readme","bugs":{"url":"https://github.com/CREAIT-nl/dsh-plugins/issues"},"author":{"name":"Francesco G","email":"francesco@creait.nl"},"scripts":{"test":"node --test test/*.test.js"},"gitHead":"11846431d4a2751b4712f547f2b91e45aedc7884","_id":"@creait/dsh-web-search-searxng@0.1.0","_nodeVersion":"24.19.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-sP2iORYCONnORwXi2ELwnRK0s/WybYkyUmyTG4JR4xK24lvE1FcRVSgc6f2OLE+RE51O+vZn0g79U8a8gTo2FQ==","shasum":"e6a8c9d4dddb99c5d437b0bcc2d16e80697ae242","tarball":"https://registry.npmjs.org/@creait/dsh-web-search-searxng/-/dsh-web-search-searxng-0.1.0.tgz","fileCount":5,"unpackedSize":30046,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@creait%2fdsh-web-search-searxng@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCMWII9CeN4242DGYG/7FoUJu+hp5caMDUG/7GNAozrNAIgbQZLaMc7RdfkJkvUocqPDmQDSzqpM3QzI3VrXGPtJ4s="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:5cf08833-6168-4c40-8b42-64ca7dcb0f3a"}},"directories":{},"maintainers":[{"name":"creait","email":"francesco@creait.nl"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-web-search-searxng_0.1.0_1787584424082_0.20225958337947003"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-24T13:24:36.348Z","modified":"2026-08-24T15:13:44.528Z","0.1.0-rc.7":"2026-08-24T13:24:36.742Z","0.1.0":"2026-08-24T15:13:44.224Z"},"bugs":{"url":"https://github.com/CREAIT-nl/dsh-plugins/issues"},"author":{"name":"Francesco G","email":"francesco@creait.nl"},"license":"MIT","homepage":"https://github.com/CREAIT-nl/dsh-plugins/tree/main/web-search-searxng#readme","keywords":["dsh","deepseek-harness","cordis","plugin","searxng","web-search","self-hosted"],"repository":{"type":"git","url":"git+https://github.com/CREAIT-nl/dsh-plugins.git","directory":"web-search-searxng"},"description":"SearXNG-backed search provider for the DeepSeek Harness web capability seam (ctx.web)","maintainers":[{"name":"creait","email":"francesco@creait.nl"}],"readme":"# @creait/dsh-web-search-searxng\n\nSearXNG-backed search provider for the DeepSeek Harness `ctx.web` capability\nseam. It replaces the stock DeepSeek search route, so the model-facing\n`web_search` tool queries a self-hosted SearXNG instance instead.\n\n## Install\n\n```sh\ndsh plugin --profile web add @creait/dsh-web-search-searxng\n```\n\nThat mounts the provider — and mounting it changes nothing yet. `baseURL` has\nno default, so an unconfigured row reports itself unavailable and `ctx.web` goes\non selecting the shipped DeepSeek provider exactly as it did before. Two keys\nswitch the search over, in your profile patch —\n`$DSH_HOME/profiles/<profile>/cordis.patch.yml`, where `$DSH_HOME` defaults to\n`~/.dsh`:\n\n```yaml\n- id: web\n  config:\n    searchProvider: searxng\n\n- id: web-search-searxng\n  config:\n    baseURL: http://localhost:8080   # your SearXNG instance\n    maxResults: 10\n    engines: bing,google,wikipedia\n```\n\nBoth are required, and they fail differently. Without `baseURL` the provider\nstays unavailable, so naming it costs you every search with\n`WEB_PROVIDER_CONFIGURED_UNAVAILABLE`. Without `searchProvider` two usable\nproviders are registered and nothing chose between them, which is\n`WEB_PROVIDER_AMBIGUOUS`.\n\nDisabling the shipped provider is a separate decision: naming one in\n`searchProvider` is what resolves the ambiguity, so `web-search-deepseek` can\nstay mounted as a fallback you can switch back to by editing one line.\n\n```yaml\n- id: web-search-deepseek\n  disabled: true\n```\n\nIf you installed this before it shipped a bundle patch, your profile patch\ninserts the row by hand. Drop that `- insert:` block and keep the\n`- id: web-search-searxng` config override above: `insert` appends\nunconditionally, and a second row would register the same provider id twice,\nwhich `ctx.web` rejects with `WEB_DUPLICATE_PROVIDER`.\n\n### How the peers resolve\n\nThe plugin imports `@deepseek-ai/dsh-web`, `@deepseek-ai/cordis` and\n`@deepseek-ai/schemastery` as bare specifiers, and nothing installs them beside\nit. They resolve by Node's parent-directory walk from the package's **realpath**\ninto `$DSH_HOME/profiles/node_modules` — the flat closure dsh maintains (one\nsymlink per package in the installation's dependency graph, re-pointed on every\nboot by `healProfilesModuleFallback`).\n\nNode resolves a symlinked package from its real location, not from the link, so\nthat walk starts wherever the package really lives. Installing from the registry\nputs it under the profile and the walk lands where it should. Running a checkout\ninstead — a `link:` into a working tree outside `$DSH_HOME` — means the walk\nstarts in that tree, so the tree needs the peers reachable from it; the repo root\nREADME has the symlink shim that arranges exactly that.\n\nInstalling the peers locally instead is not a fix: a second copy of\n`@deepseek-ai/dsh-web` means a second `WebError` class and a second `ctx.web`\nservice identity. That is what `peerDependencies` is preventing.\n\n## Config\n\n| Key | Default | Meaning |\n| --- | --- | --- |\n| `baseURL` | none | SearXNG instance, trailing slashes stripped. Empty means unavailable: the row mounts, the seam ignores it |\n| `maxResults` | `10` | Sources returned; reached by paging, see below |\n| `maxPages` | `3` | Ceiling on result pages fetched for one search |\n| `engines` | `''` | Comma-separated engine pin, e.g. `bing,google` |\n| `timeRange` | `''` | `day` \\| `week` \\| `month` \\| `year` — freshness window |\n| `categories` | `''` | e.g. `news`, `science` |\n| `language` | `''` | e.g. `en` |\n| `userAgent` | `deepseek-harness/0.0.1 (searxng search)` | Sent on every request |\n\n### `maxResults` costs round-trips\n\nSearXNG has **no result-count parameter**. It accepts `count` and silently\nignores it — a request for three results still returns ten. The only lever is\n`pageno`, so `maxResults` above one page's worth is reached by paging, bounded\nby `maxPages`. A page that contributes nothing new ends the loop rather than\npaging into repeats, and the common case (`maxResults` ≤ one page) costs exactly\none request.\n\n### `timeRange` needs an engine that implements it\n\nA general-web engine that does not support time filtering returns **nothing**\nfor a filtered query rather than ignoring the filter — and does not report\nitself unresponsive. bing gives ten results for `nvidia gpu` and zero for the\nsame query with `time_range=week`. Freshness works in the `news` category, whose\nengines implement it and populate `publishedDate`:\n\n```yaml\nconfig:\n  categories: news\n  timeRange: week\n```\n\nWhen a windowed search comes back empty the provider says so in `content`,\nrather than letting a filter artefact read as an empty web.\n\n## When a search returns nothing\n\nA public-engine SearXNG instance degrades quietly. Engines get CAPTCHA'd,\nrate-limited and suspended individually, and SearXNG answers with an empty\n`results` array plus an `unresponsive_engines` list that the seam has nowhere to\nput. The search tool renders that as `No results found.` — which the model reads\nas *the web holds nothing*, and it is nothing of the kind.\n\nOn 2026-08-23 every engine in one instance's enabled general category was down\nat once (duckduckgo and startpage on CAPTCHA, brave suspended for too many\nrequests, google returning nothing while not even reporting itself\nunresponsive). Every general web search on it returned zero results, silently.\n\nSo this provider distinguishes the two:\n\n- **No results, no failed engines** → an empty result. A genuine no-hit.\n- **No results, some failed engines** → `WEB_PROVIDER_ERROR` naming them. The\n  provider cannot tell an empty web from a broken search, and says so rather\n  than letting the model conclude the stronger thing.\n- **Some results, some failed engines** → the results, led by a\n  degraded-coverage warning naming the failed engines.\n\n`engines` is the lever: pin the ones your instance can actually reach, and add\nothers back as they recover.\n\n## The `content` block\n\n`WebSearchResult.content` is rendered by the web tool **above** the source list,\nwhich makes it the only channel a provider has for saying something about the\nsearch rather than about one result. Every line is conditional, because it is\npaid for on every call:\n\n- an answer SearXNG resolved outright, when there is one;\n- the degraded-coverage warning above;\n- the escalation to `web_fetch` — snippets are index summaries, and a question\n  about live state (a price, availability, a count, a status) cannot be answered\n  from them. `web_search`'s own guidance says this only when `web_fetch` is\n  mounted in the *same* composition, which is false on the web surface, where\n  the search tool is per-preset and the fetch half is host-plane;\n- spelling corrections and query suggestions, only when nothing was found.\n\n## Verify\n\n`scripts/smoke.sh` in the repo checkout (it is not in the published tarball)\ndrives the real provider class against a real instance and checks that all three\npatch rows survived composition:\n\n```sh\nDSH_PROFILE=web SEARXNG_URL=http://localhost:8080 ./scripts/smoke.sh\n```\n\n## What breaks this\n\nThe seam this binds to (`ctx.web.registerSearchProvider`, `WebError` with\n`WEB_PROVIDER_ERROR` / `WEB_ABORTED`, `search() -> { content?, sources, truncated }`) is a\npre-1.0 internal API with no compatibility guarantee. `peerDependencies` pins\n`@deepseek-ai/dsh-web` to `^0.1.0-rc.7`, but the profile sets\n`autoInstallPeers: false`, so a mismatch is a pnpm *warning*, not a guard — at\nruntime the plugin resolves whatever `$DSH_HOME/profiles/node_modules` points at\nand loads against it. Treat that warning as the canary and run the smoke test\nafter every dsh update.\n\nNote that `dsh`'s own dependencies float on `^0.1.0-rc.N` ranges, so a fresh\n`npm i -g @deepseek-ai/dsh` can pull newer internals than the CLI's own version\nsuggests. Verified working against dsh CLI 0.1.0-rc.7 with rc.8 internals.\n","readmeFilename":"README.md"}