DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

nedzen /

nedzen/dsh-web-search-brave

Verified

Brave Search provider for DeepSeek Harness (dsh) — independent dsh plugin hardened against web_search hangs, missing request timeouts, and HTTP 429 rate-limit storms.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@8bac0856

License: MIT DeepSeek Harness plugin dsh

dsh-web-search-brave

Brave Search provider plugin for DeepSeek Harness (dsh) — hardened so web_search cannot hang your session, and multi-query searches stop drowning in HTTP 429 rate limits.

An independent implementation of the DeepSeek Harness ctx.web search-provider seam. It targets the official provider interface and is modeled on the official @deepseek-ai/dsh-web-search-deepseek provider's structure, which is the documented way to write a seam provider. It is not a fork of the separately developed dsh-web-search-brave packages — see Compatibility.

Not affiliated with or endorsed by DeepSeek. The @deepseek-ai/... package name follows DeepSeek's first-party naming convention for seam providers; it is not an official DeepSeek package and is never published to npm. The provider interface and module scaffolding derive from the official @deepseek-ai/dsh-web-search-deepseek (MIT, © 2026 DeepSeek); the Brave integration is original to this package.

  • Keywords: DeepSeek Harness, dsh, dsh plugin, Brave Search API, web search provider, web_search tool, HTTP 429 rate limit, request timeout, agent tool call hang

The bug this fixes

If your dsh session ever froze when the web_search tool was called — spinner forever, nothing in the transcript, sometimes needing a full restart — this is why.

BraveSearchProvider.search() in 0.1.1 armed no timer of its own and handed fetch only the caller's AbortSignal. DeepSeek Harness enforces tool timeouts through @deepseek-ai/dsh-tool-call-timeout-policy, which is explicitly cooperative: it awaits the tool promise without racing it. So when a request never settles, no tool result is ever recorded, the turn waits indefinitely, and the UI looks frozen.

Observed live, with a 60 s tool budget configured and never applied:

Call Result
web_search, 1 query returned in 1.1 s
web_search, 4 queries never returned — 8 m 29.7 s, until the user interrupted it

The second problem compounds it. One multi-query web_search call fans out as N parallel requests (the fan-out lives in dsh-tool-web), while Brave rate-limits per second. On the free tier that spends the whole budget on 429s:

4 concurrent queries, unspaced Outcome
upstream 0.1.1 0 of 4 succeeded (WEB_PROVIDER_ERROR, HTTP 429), in 731 ms

What this version changes

Three configurable knobs, all bounded by the same hard deadline so nothing can hang:

Setting Default What it does
searchTimeoutMs 15000 Hard provider-level deadline. Fuses your AbortSignal with AbortSignal.timeout(). This is the freeze fix. Created before queueing, so queue wait counts against the budget.
minRequestIntervalMs 1500 Serializes the fan-out through a per-process queue, one request at a time, spaced. 1100 measured 3/4 successes; 1500 measured 4/4.
maxRetries 2 Bounded retries on HTTP 429, honouring Retry-After (capped at 3 s). Bounded by searchTimeoutMs, so retrying cannot reintroduce a hang.

Deadline expiry is reported as a clear WEB_PROVIDER_ERROR timeout; caller cancellation is still reported as WEB_ABORTED. Nothing else about the provider contract changed.

After the fix, on the same live API:

before after
stalled server, no caller signal never settles rejects in 4.0 s
4 concurrent queries 0/4 4/4 in 5.5 s
1 query 903 ms 843 ms

Install

dsh plugin --profile web add github:nedzen/dsh-web-search-brave

Or from this local checkout:

dsh plugin --profile web add file:$HOME/Sites/dsh-web-search-brave

Then pin the seam to it in your profile's cordis.patch.yml. A patch replaces the whole config, so restate every key the web row owns — omitting fetchProvider silently drops it and breaks web_fetch:

- id: web
  config:
    searchProvider: brave-search
    fetchProvider: http          # do not omit

- id: web-search-brave
  name: "@deepseek-ai/dsh-web-search-brave"
  config:
    apiKeyEnv: BRAVE_SEARCH_API_KEY
    baseURL: https://api.search.brave.com/res/v1
    maxResults: 10
    searchType: web
    # searchTimeoutMs: 15000      # optional overrides
    # minRequestIntervalMs: 1500
    # maxRetries: 2

The API key resolves from the credentials service first, then the launching environment (.env files are layered in automatically). Store it as BRAVE_SEARCH_API_KEY, or point apiKeyEnv at whatever name you export.

Verify it works

test/freeze-regression.mjs reproduces the freeze with a server that accepts the TCP connection and then never responds. Cases A (stalled server, no caller signal) and C (fan-out spacing) go RED on 0.1.1 and GREEN here.

cd ~/.dsh/profiles/web
set -a; . ~/.dsh/.env; set +a
node ~/Sites/dsh-web-search-brave/test/freeze-regression.mjs \
  ./node_modules/@deepseek-ai/dsh-web-search-brave/lib/index.js "0.1.3"

Node rather than Bun is deliberate: dsh runs this provider under Node's fetch, and the abort/timeout semantics being asserted are undici's.

Self-check in a live session — the timing tells you which build is loaded:

4-query web_search Build
returns in under 1 s, usually HTTP 429 old 0.1.1
takes 4.5 s or more, normally succeeds this build

Tuning

  • Many parallel queries time out. searchTimeoutMs also covers queue wait. If the model fires several web_search calls at once (e.g. maxParallelToolCalls above ~3 with 4 queries each), the tail can exceed 15 s and fail fast rather than hang. Raise searchTimeoutMs to 30000.
  • Paid Brave plan. Set minRequestIntervalMs: 0 and maxRetries: 0 to trade robustness for speed.
  • Still rate-limited. Raise minRequestIntervalMs, or run a multi-provider fallback chain instead of a single backend.

Troubleshooting

I edited the source and nothing changed. A file: dependency is copied into the profile by pnpm, and pnpm install reports "Already up to date" while keeping the stale copy. Remove it and reinstall:

rm -rf ~/.dsh/profiles/web/node_modules/@deepseek-ai/dsh-web-search-brave
dsh plugin --profile web install

I reinstalled but the running session is unchanged. Only config hot-reloads. Plugin JavaScript is ESM-cached and dsh-hmr ships with no watch roots (root: []), so restart dsh to load new provider code.

WEB_DUPLICATE_PROVIDER. Another provider already registered the brave-search id — check for two rows declaring this bundle.

Compatibility

Targets the 0.1.x DeepSeek Harness line: @deepseek-ai/dsh-*@^0.1.1-rc.2 peers. The timeout/spacing/retry settings are additive, so dsh 0.2.x should also work — verify against your own build rather than assuming.

Same name, different plugin. dsh-web-search-brave is also the name of unrelated, separately developed packages, e.g. cnChenKai/dsh-web-search-brave (npm dsh-web-search-brave, 0.2.4). They are different implementations with incompatible configuration: that one registers the provider id brave and configures count/safesearch/textDecorations; this one registers brave-search and configures maxResults/searchType. Pick one, don't wire both into the same profile, and don't expect a config to port across.

Related

  • deepseek-ai/deepseek-harness — the harness, and the official provider template this follows
  • 0xsline/awesome-deepseek-harness — ecosystem index
  • GitHub topic dsh-plugin — more dsh plugins
  • Other web_search backends: multi-provider, Exa, SearXNG
  • Separately developed, same name: cnChenKai/dsh-web-search-brave

License

MIT. Implementation © 2026 nedzen. The provider interface and module scaffolding derive from the official @deepseek-ai/dsh-web-search-deepseek (MIT, © 2026 DeepSeek). See LICENSE.

—/ 5

No ratings yet

Verified DSH bundle

Commit 8bac0856a715

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout