korosu/dsh-web-search-ddg ↗★ 0
dsh-web-search-ddg
Zero-token DuckDuckGo HTML-scrape search provider for the DeepSeek Harness web capability seam (ctx.web): no API key, no auxiliary model request. 适合需要零Token消耗、无需API密钥即可进行网页搜索和内容检索的场景。
Other repositories with this package name
Install
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:korosu/dsh-web-search-ddgREADME
Read the full README ↗Configuration
All keys are optional; write them in the insert row's config.
| Key | Default | Description |
|---|---|---|
endpoint | https://html.duckduckgo.com/html/ | Results endpoint; ?q= is appended. Must be an absolute http(s) URL; anything else makes the provider unavailable |
maxResults | (unset) | Row bound applied while parsing, and only when the request carries no maxResults. Must be a positive integer; anything else makes the provider unavailable. It counts parsed rows, so junk rows early in the page can push valid ones past the cut |
failureThreshold | 3 | Consecutive transient failures after which the provider reports itself unavailable and lets the seam pick another backend. Must be a positive integer |
cooldownMs | 300000 (5 min) | How long that unavailability lasts, in milliseconds. Must be a positive integer |
There is no credential of any kind — the provider is inherently keyless, so there is no settings card, no API key field, and no credential-resolution step.
Selecting a provider
The bundle's web row override pins searchProvider: ddg, which is what makes the one-command install switch the backend. The seam also honors an environment variable with the same meaning — setting
export DSH_WEB_SEARCH_PROVIDER=ddg
pins the provider without touching any patch row; the config key wins when both are set. Pinning matters when more than one search backend is registered: with the pin (either form) the seam resolves to ddg, without it two usable backends raise WEB_PROVIDER_AMBIGUOUS. The same applies to fetching through DSH_WEB_FETCH_PROVIDER, which this bundle leaves at the shipped http backend.
What a search returns
Each row of the results page maps to a WebSearchSource: url, title, and, when non-blank, snippet. A row with a blank title or an unusable URL is dropped, and duplicates collapse to their first-seen URL, so a call can return fewer sources than requested.
DDG result links are protocol-relative redirect hops (//duckduckgo.com/l/?uddg=&rut=...). The provider decodes uddg back to the real destination, so the tool cites the actual page rather than the intermediate URL. This is a pure string operation — a result hop is never fetched.
The provider always reports truncated: false. When a request carries maxResults, the provider caps its own parse at that number, so it never over-returns and the seam has nothing to truncate — the flag stays false even when the page held more rows than you asked for. A configured maxResults applies the same way, while parsing, and is invisible to the caller. DDG's static markup carries no generated answer and no publication date, so neither content nor publishedAt is ever emitted.
Failures and recovery
| Situation | Outcome |
|---|---|
| Network failure, unreadable body | WebError WEB_PROVIDER_ERROR, counted toward the failure budget |
| HTTP 403 / 429 / 5xx | WebError WEB_PROVIDER_ERROR, counted toward the failure budget |
| Any other HTTP non-2xx | WebError WEB_PROVIDER_ERROR, not counted — a configuration-style failure retries identically |
| Challenge / anomaly page (HTTP 202, no result markup) | WebError WEB_PROVIDER_ERROR naming the anomaly page, counted toward the failure budget |
| Aborted request | WebError WEB_ABORTED, never counted |
Failure budget spent (failureThreshold consecutive transient failures) | The provider reports itself unavailable for cooldownMs. A pinned deployment sees WEB_PROVIDER_CONFIGURED_UNAVAILABLE; an unpinned one falls back to any other registered backend, or gets WEB_PROVIDER_UNAVAILABLE if there is none |
endpoint not an absolute http(s) URL, or a numeric config not a positive integer | WebError WEB_PROVIDER_CONFIGURED_UNAVAILABLE — registered, but refuses to run |
| Pin kept, bundle removed | WebError WEB_PROVIDER_CONFIGURED_MISSING |
| Pin removed while both search backends are registered | WebError WEB_PROVIDER_AMBIGUOUS |
HTTP redirects are followed by the native fetch, because the endpoint moves between the bare domain and the html/ path. An abort — a DOMException named AbortError, or an already-aborted signal — becomes WEB_ABORTED; anything else becomes WEB_PROVIDER_ERROR.
The failure budget is the throttling signal. A challenge page, a rate limit, or a transport failure is distinguishable from a genuinely empty query: the empty query returns an empty result, throttling raises a structured error, and once the same kind of failure repeats failureThreshold times in a row the provider stops offering itself for cooldownMs so the seam can route around it. Any success — including a genuinely empty result — clears the counter, and when the cooldown elapses the provider comes back with a fresh budget rather than staying tripped.
There is no silent fallback inside the provider: it never substitutes another engine itself. Routing around a cooled-down provider is the seam's selection job, and it only happens for deployments that did not pin ddg. A pinned deployment gets the structured error instead — pinning is a deliberate choice to hear about every failure.