dsh-web-tavily
English · 中文
Built for dsh-TUI.
A host-plane Cordis plugin for dsh: it registers the tavily search provider
on dsh's web seam (ctx.web), so the web_search tool goes to the Tavily
Search API (POST https://api.tavily.com/search).
- It does not replace the
web_searchtool itself: the tool, its system prompt and its result card are untouched — only the search backend changes. - DeepSeek's native search (
web-search-deepseek) stays available as a fallback you can switch to; setweb.searchProviderback todeepseek-official(or delete the key) to do so. - The key is always resolved as a reference (default
TAVILY_API_KEY): the credentials service (refs:in~/.dsh/.credentials.yaml) first, then the process environment, and only then a literalapiKeyin the configuration. The provider never stores a key.
How it works
- The provider (id
tavily) registers onctx.web;web.searchProvider: tavilymakes it the default choice. - When
web_searchruns, the seam hands{ query, maxResults }to this provider. - The provider resolves the key (a snapshot per operation, so one search never
mixes two configuration versions) and calls
{baseURL}/search. - The response is mapped:
results[] → sources[](title/url/content→snippet, pluspublished_date → publishedAtwhen present), and a top-levelanswer(Tavily's AI summary) becomes the tool's optionalcontent. - Cancellation (signal) →
WEB_ABORTED; an HTTP failure or an unparsable body →WEB_PROVIDER_ERROR; a missing key →WEB_PROVIDER_CREDENTIAL_MISSING(the message names the configuration step).
Defaults
| Field | Default | Meaning |
|---|---|---|
apiKeyEnv |
TAVILY_API_KEY |
Credential reference (environment-variable name). |
apiKey |
(none) | A literal key, only when you do not want a reference. |
baseURL |
https://api.tavily.com |
Overridable with TAVILY_BASE_URL. |
searchDepth |
basic |
basic / advanced (deeper, higher credit use). |
topic |
general |
general / news. |
includeAnswer |
true |
Ask for Tavily's AI summary (mapped to content). |
maxResults |
10 |
Fallback cap; web_search passes 8 per call, Tavily's ceiling is 20. |
Install
Install it into your profile — it is published on npm as
dsh-web-tavily:dsh plugin --profile dsh-tui add dsh-web-tavily # from a local checkout instead: # dsh plugin --profile dsh-tui add file:<path to this package>Make tavily the default search backend. This has to be written in the profile's user patch layer (the
web:section ofsettings.yamldoes not affect service configuration) — edit~/.dsh/profiles/dsh-tui/cordis.patch.ymland add:- id: web name: '@deepseek-ai/dsh-web' config: searchProvider: tavily fetchProvider: http-trustedThe
webrow belongs to thedsh-baselayer, so do not add a new row withinsert:— the duplicate id crashes the boot withduplicate loader entry id: web. Override the existing row by id instead, and note that an override replaces the whole config, which is whyfetchProvideris restated above.Restart dsh-tui (
/restart).
Configuration
The plugin's own section lives in ~/.dsh/settings.yaml (it also appears in the
settings screen under “Web search Tavily”):
dsh-web-tavily:
apiKeyEnv: TAVILY_API_KEY
searchDepth: basic
topic: general
includeAnswer: true
maxResults: 10
TAVILY_BASE_URL overrides the API origin; the credential reference defaults to
TAVILY_API_KEY.
The settings screen renders this namespace as a bilingual card (Web search (Tavily) / 联网搜索(Tavily)) with a description for every field, so the reference is the one control you normally touch. The key itself never enters the settings document.
Where the key lives (local only — never in a repository)
Add it under refs: in ~/.dsh/.credentials.yaml (that file holds machine-local
credentials; do not commit or share it):
refs:
TAVILY_API_KEY: 'tvly-...'
Switching back to DeepSeek's native search
Set web.searchProvider to deepseek-official (or delete the key and let the
seam use the only provider available). The seam does not fall back
automatically: a broken Tavily configuration fails loudly rather than quietly
using DeepSeek.
Proxy-compatible fetch (http-trusted)
The plugin also registers an http-trusted fetch provider, which works around
web_fetch being refused by the official http provider's safety preflight in
fake-IP proxy environments (Clash and friends resolving domains to
198.18.0.0/15): WEB_BLOCKED_URL: resolves to a non-public IP address.
- It reuses the official
@deepseek-ai/dsh-web-fetch-httptransport and relaxes exactly one check — the “resolved address must be public” preflight additionally allows198.18.0.0/15, the proxy fake-IP range. Everything else is unchanged: private, loopback and reserved ranges are still refused (WEB_BLOCKED_URL), IPv6 answers are still dropped, and address pinning, same-origin redirects, the content-type allowlist, byte/character caps and the no-credentials cookie rule all stay in place. - ⚠️ Security boundary: SSRF protection moves from “enforced inside the host”
to “trust the local proxy's DNS decision”, so the last word on reachability
belongs to the proxy's routing rules. Intended for a single-user machine with
a trusted self-hosted proxy; for shared or public deployments go back to the
official
httpprovider (below). - Addresses that are special by construction (
http://192.168.x.x/and friends) are still refused.
# the web row in ~/.dsh/profiles/dsh-tui/cordis.patch.yml
config:
searchProvider: tavily
fetchProvider: http-trusted
Rolling back to the official http: set fetchProvider back to http. The
cost is that web_fetch is refused again in a fake-IP proxy environment, in
exchange for the complete safety preflight.
Troubleshooting
duplicate loader entry id: webon boot — the profile'scordis.patch.ymlusedinsert:for a row that already exists; rewrite it as the by-id override shown above.web_searchanswersWEB_PROVIDER_CONFIGURED_MISSING— the plugin did not load: check the bundles list and the patch above.web_fetchanswersWEB_BLOCKED_URL ... non-public IP— the proxy fake-IP preflight refused it: switch tohttp-trusted, or turn the proxy's fake-IP mode off.
Limitations
- A missing or invalid Tavily key makes
web_searchfail (WEB_PROVIDER_CREDENTIAL_MISSING/WEB_PROVIDER_ERROR); the message names what to configure. max_resultsis clamped to Tavily's ceiling of 20.- An invalid
searchDepth/topicis an error, never a silent fallback to the default.
Publishing
- Repository: https://github.com/VviLliAm-qwq/dsh-web-tavily (public)
- Release:
v*tags drive.github/workflows/release.yml, which publishes to npm through trusted publishing (OIDC) — no token is stored in the repository.
Usage
Once installed and configured, let the model call web_search as usual: the
sources list it returns (and the optional summary) now comes from Tavily.
License
MIT — see LICENSE.
No comments yet. Be the first to write one.