# dsh-web-search-tavily English | [中文](README.md) A Tavily web-search plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness). The plugin registers a Tavily-backed search provider into the `web` capability seam (`ctx.web`) and switches the `web_search` tool's search selection to it. Once registered, every model `web_search` call executes a live web retrieval through Tavily's REST API (`POST /search`) and returns titles, links, and page excerpts. ## Why this plugin DSH's built-in search provider is `deepseek-official`, which performs retrieval through the DeepSeek API's server-side search and bills against your model API quota. Tavily is an independent search API with its own free/cheap tier (`basic` costs 1 credit per call), useful for separating chat billing from search usage or for capping search spend independently. ## Install You need a web-surface profile (the default profile behind `dsh web` is named `web`). Both `git` and `pnpm` must be on `PATH`. ```sh # Install straight from GitHub (needs git and pnpm on PATH; use an existing web-surface profile named e.g. `web`) dsh plugin --profile web add github:my-dsh/dsh-web-search-tavily # Or from npm once published dsh plugin --profile web add @my-dsh/dsh-web-search-tavily ``` The package declares `dsh.bundle`, so installing it joins the profile's bundle layer stack automatically; restart DSH to take effect. ### Prerequisites - DSH `0.1.2-alpha.2` or newer (the plugin registers its config section through the settings service; earlier releases lack that service-side API). - A web-surface profile (e.g. the default `web` profile behind `dsh web`). - A Tavily API key, either: - **written into the DSH credential file** (recommended): edit `~/.dsh/.credentials.yaml` and add a line `TAVILY_API_KEY: tvly-...` under its `refs:` section (the store reloads the file automatically); or - exported in the environment launching DSH: `export TAVILY_API_KEY=tvly-...`. The key resolves **per search** (credentials service first, falling back to the launch environment), never cached, never written into configuration files. ### What the bundle patch does `cordis.patch.yml` makes two changes: 1. Inserts `@my-dsh/dsh-web-search-tavily` — registers a search provider under the stable id `tavily`. 2. Retargets the `web` row's `searchProvider` to `tavily`. A patch replaces the targeted row's whole `config`, so the `fetchProvider: http` shipped by dsh-base is restated here (dropping it would disable anonymous fetch). ## Configuration All fields are optional. Override through the user patch layer (`~/.dsh/profiles//cordis.patch.yml`): ```yaml - id: web-search-tavily name: '@my-dsh/dsh-web-search-tavily' config: # Search depth: basic (1 credit, default) or advanced (2 credits) searchDepth: advanced # Result count requested when a search carries no maxResults (default 8) maxResults: 5 # Per-request timeout in milliseconds (default 30000) timeoutMs: 20000 # Literal API key. Leave empty to resolve via credentials/env (recommended); # a literal key wins and lands in your configuration file. # apiKey: tvly-... ``` | Field | Default | Meaning | |---|---|---| | `apiKey` | — | Literal key; wins over credential resolution. Leave empty | | `apiKeyEnv` | `TAVILY_API_KEY` | Credential / environment variable name | | `baseURL` | `https://api.tavily.com` | REST endpoint; `/search` is appended | | `searchDepth` | `basic` | Tavily `search_depth` | | `maxResults` | `8` | Default result-count bound | | `timeoutMs` | `30000` | Per-request timeout | Settings-page edits take effect immediately: the provider snapshots the current config section per search, so one search never mixes two versions of the configuration. ## Behavior details - **Missing credentials, Tavily API errors, network failures, and timeouts** surface as standard DSH `WebError`s carrying structured codes (`WEB_PROVIDER_CREDENTIAL_MISSING` / `WEB_PROVIDER_ERROR` / `WEB_ABORTED`); the plugin throws the same `WebError` class as the built-in providers, so tool results retain the `{name, code}` error metadata. - **Cancellation**: the caller's signal (tool timeout, model-initiated abort) is fused with this plugin's `timeoutMs` via `AbortSignal.any` — whichever fires first wins, so a cancel actually terminates the in-flight HTTP request instead of letting it run to completion. - **Timeout classification**: this plugin's own timeout surfaces as `WEB_PROVIDER_ERROR` (message includes `timed out after Nms`), matching the built-in search providers; a caller cancel classifies as `WEB_ABORTED`. - **Literal `apiKey` wins**: when `apiKey` is configured it is used directly, bypassing credential resolution. - **Dedup**: Tavily results with duplicate URLs are deduplicated in order. - **Portable source shape**: results map to `{ url, title?, snippet?, publishedAt? }`, identical to the other `web`-seam providers. ## Source layout ``` dsh-web-search-tavily/ ├── src/ │ ├── index.js # Cordis function plugin: Config schema + ctx.web registration │ └── provider.js # TavilySearchProvider: REST mapping, error codes, dedup, timeout ├── cordis.patch.yml # bundle patch: insert provider + switch web.searchProvider └── package.json # dsh.bundle declaration + peer dependencies ``` Plain ESM JavaScript with no build step; TypeScript types are expressed through JSDoc. ## Publishing Publishing this package to npm requires an npm account that owns (or can write to) the `@my-dsh` scope and an npm auth token on this machine. The scoped-package conventions are already set in `package.json` (`publishConfig.access: public`), so a maintainer runs: ```sh npm login # needs the @my-dsh-scope account npm publish ``` The repo-root `.npmrc` pins the registry to `https://registry.npmjs.org/`, so a publishing machine needs no manual registry switch; npm reads it instead of any mirror configuration. `npm pack --dry-run` previews the exact tarball (the `files` allowlist ships `src/`, the bundle patch, both READMEs, and `LICENSE`). After a successful publish, users install with `dsh plugin --profile add @my-dsh/dsh-web-search-tavily`. Both the GitHub and npm commands install the same package; the npmjs package page records which release is live. ## License [MIT](LICENSE)