dsh-web-search-tavily
Tavily web search provider plugin for DeepSeek Harness (dsh)
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 25, 2026
- Updated
- Aug 29, 2026
Introduction
dsh-web-search-tavily
A DeepSeek Harness (dsh) plugin that provides web search through the Tavily Search API.
It replaces the default DeepSeek web search — which spends a full auxiliary model call
per search (Anthropic-compatible Messages API + web_search_20250305 server tool) —
with a plain Tavily REST call: ~1s, 1 credit at basic depth, no model tokens.
The plugin registers a WebSearchProvider (id: "tavily") with the official
ctx.web capability seam,
so everything around the tool stays exactly as shipped: the model still calls the same
web_search tool, with the same citation formatting, searchMaxResults bound, and
timeout budget. A provider swap changes how the harness reaches the web, nothing else.
Requirements
- dsh
0.1.1-rc.x(peer deps resolve to the profile's shared@deepseek-aiclosure) - A Tavily API key (
tvly-…, free tier available)
Install
One command:
dsh plugin --profile <name> add @aiwayds/dsh-web-search-tavily
dsh plugin installs the package into the profile (via pnpm) and registers it in
dsh.profile.bundles automatically. Restart dsh — the provider is registered and ready.
Local development: dsh plugin --profile <name> add link:/path/to/dsh-web-search-tavily
(relative path specs anchor to your invoking directory). The manual route — adding the
dependency and the bundle entry to ~/.dsh/profiles/<name>/package.json yourself and
running pnpm install there — works identically.
Unlike the anysearch sibling plugin, this one does not claim the web_search
default on install — Tavily needs an API key, and a selected-but-unconfigured provider
would fail every search. After storing the key, select it with one patch entry (below).
@deepseek-ai/*packages are peer dependencies by design: they must resolve to the profile's single shared dsh closure (link-dsh-closure). Putting them independenciesinstalls a second cordis instance and crashes the loader.
Configure
1. API key. Store the key in dsh's managed credentials document
(~/.dsh/.credentials.yaml) — resolved per search, never written to config files:
version: 1
refs:
TAVILY_API_KEY: tvly-xxxxxxxx
Fallbacks, in resolution order: TAVILY_API_KEY in the launching environment, then a
literal apiKey in the plugin config (discouraged).
2. Select the provider. The base bundle pins web.searchProvider to
deepseek-official, and a configured id always wins — so add the override to the
profile's cordis.patch.yml (or the home-level ~/.dsh/cordis.patch.yml for every
profile):
- id: web
config:
searchProvider: tavily
Optionally retire the DeepSeek search plugin entirely:
- id: web-search-deepseek
disabled: true
Restart dsh. Verify the composed tree:
dsh --profile <name> --dump-config | grep -A3 'id: web$' # searchProvider: tavily
Rollback: delete the patch entries and restart — web_search returns to the
shipped DeepSeek provider.
Plugin config (all optional; via a patch layer, read at startup):
| Field | Default | Notes |
|---|---|---|
apiKeyEnv | TAVILY_API_KEY | Credential reference resolved per search |
apiKey | — | Literal key; wins over the reference |
baseURL | https://api.tavily.com | Env override: TAVILY_BASE_URL |
searchDepth | basic | basic / advanced (2 credits) / fast / ultra-fast |
topic | general | general / news / finance |
maxResults | 8 | Default when the tool sends no bound; API caps at 20 |
includeAnswer | true | Tavily's generated answer becomes the result's content |
Example patch entry with config:
- insert:
- id: web-search-tavily
name: '@aiwayds/dsh-web-search-tavily'
config:
searchDepth: advanced
topic: news
Behavior
results[].content→ the portablesnippet; Tavily'sanswer→ the result'scontent; the API returns no publication dates, sopublishedAtis never set.- Follows the
ctx.webseam contract: cancellation asWEB_ABORTED, a missing key asWEB_PROVIDER_CREDENTIAL_MISSING(namingTAVILY_API_KEY), provider failures asWEB_PROVIDER_ERRORwith Tavily's own error detail surfaced; a rejected key readsTavily rejected the API key (HTTP 401). available()is a local check only (credential source present, base URL parseable) — it never makes network calls.- Result-count requests are clamped to Tavily's
max_resultscap (20); the seam additionally enforces the tool'ssearchMaxResultsbound (default 8).
Develop
npm install
npm run check # tsc --noEmit
npm test # build + node --test (14 tests)
After editing src/, run npm run build and restart dsh — the plugin loads from
lib/. Unit tests cover response mapping, availability, the request shape, and the
HTTP/abort/credential error branches with mocked fetch.
License
MIT