Skills Plugins MCP Prompt Model 导航 博客 资讯 我的中心

dsh-web-search

为 ctx.web 接入多家 web_search / web_fetch 后端(Tavily、Firecrawl,可扩展),不绑定任何 LLM 供应商

cbalaa @cbalaa ⬇ 2 ★ 1 main

安装

dsh plugin --profile web add github:cbalaa/dsh-web-search
下载安装清单

需要可复现安装时,可在仓库后追加 #commit 固定提交。

为 ctx.web 接入多家 web_search / web_fetch 后端(Tavily、Firecrawl,可扩展),不绑定任何 LLM 供应商

该插件未提供要点说明,请参考仓库 README。

  1. 安装并启动 DeepSeek Harness:npx @deepseek-ai/dsh web
  2. 在终端执行上面的安装命令(CLI 会解析插件并核验来源)
  3. 用 dsh plugins list 确认已安装,必要时重启 Harness 生效

插件以当前 dsh 进程的权限运行,安装时可能执行代码。请先通读仓库源码与许可证,确认无破坏性命令与越权访问;本站只做索引,不对第三方插件安全性作担保。

代码仓库github.com/cbalaa/dsh-web-search
许可证MIT
主要语言main
下载量2
GitHub 星标1
最近推送2026-09-07
收录日期2026-09-19
分类工具与能力

事实信息来自公开插件目录快照(2026-10-01),介绍文案由本站再加工。

以下为插件仓库 README 全文(原始内容,由公开目录抓取整理)。

# dsh-web-search

DSH web plugin: **multi-vendor `web_search` / `web_fetch` backends** for the
`ctx.web` seam. Works with any LLM provider — you no longer need DeepSeek's
native search for `web_search` to work.

| Vendor    | Config name | Search provider      | Fetch provider                 | Key lookup (in order)                                                        |
| --------- | ----------- | -------------------- | ------------------------------ | ---------------------------------------------------------------------------- |
| Tavily    | `tavily`    | `POST /search`       | `POST /extract` (clean text)   | `apiKey` → credentials service(`apiKeyEnv`) → `$TAVILY_API_KEY` → Tavily CLI config file |
| Firecrawl | `firecrawl` | `POST /v2/search`    | `POST /v2/scrape` (markdown)   | `apiKey` → credentials service(`apiKeyEnv`) → `$FIRECRAWL_API_KEY`            |
| *yours*   | —           | see [Adding a vendor](#adding-a-vendor) | —                 | —                                                            |

The plugin registers under the **fixed** ids `dsh-web-search` (search) and
`dsh-web-fetch` (fetch), so switching vendors never touches the web-seam row —
one config line does it.

## Install

Published to npm — install straight from the DSH CLI:

```sh
# latest (or pin a version: @balababa/dsh-web-search@0.2.0)
dsh plugin --profile web add @balababa/dsh-web-search
```

`dsh plugin add` forwards its argument to `pnpm add` inside the profile
directory, then reconciles `dsh.profile.bundles`: because this package declares
`dsh.bundle.patch`, `dsh-web-search` is appended to the bundle stack
automatically — no manual `cordis.patch.yml` edit to mount it.

Restart the web profile to take effect (`dsh web`, or `dsh --profile web`).

> **Prerequisites** — Node.js >= 20. The plugin's only runtime npm dependency is
> `@deepseek-ai/schemastery`. The `@deepseek-ai/dsh-web`, `@deepseek-ai/dsh-settings`,
> and `@deepseek-ai/dsh-credentials` peers — plus `react` / `react-dom` /
> `@deepseek-ai/cordis` — are provided by the DSH web runtime, no separate
> install. The settings and credentials peers are optional: without them the
> plugin still works composition-only / env-only.

### From a local checkout (development)

Run these from the checkout root:

```sh
npm install        # tsdown / react / playwright-core (dev) + schemastery (runtime)
npm run build      # bundles src/client/settings-card.tsx → lib/client.js
dsh plugin --profile web add file:.
```

The repo ships a pre-built `lib/client.js`, so installation works without a
build; but **after editing `src/client/`, re-run `npm run build`** or the
settings card will still serve the old bundle. The `file:.` spec is anchored
to your invoking directory, so run it from inside the checkout.

### Verify

```sh
dsh web --port 9000
```

After restarting, the plugin is live: open **Settings → Plugins → Plugin
configuration** to see the "Web search" card, or run a `web_search` /
`web_fetch` to confirm. The bundle patch mounts the plugin and points
`searchProvider` at it (`fetchProvider` stays on the built-in anonymous `http`
provider until you opt in — free, no vendor credits).

## Configure (two layers)

Configuration resolves through two layers, **both hot-applied**:

1. **Composition** — the plugin row's cordis config in your profile's
   `cordis.patch.yml`, applied at boot.
2. **Settings** (optional) — the `web-search:` section of the settings
   document (`settings.yaml`), layered **over** the composition config
   (`schema defaults < composition < user document`; user edits win). Editing
   the document — or saving the **Settings → Plugins → Plugin configuration →
   Web search** card — takes effect **without a restart**: vendor switches
   dispose/re-register the fixed-id providers, and key/endpoint/parameter
   edits reach the very next request.

```yaml
# your profile's cordis.patch.yml
- id: web-search
  config:
    search: tavily          # vendor for web_search
    fetch: tavily           # vendor for web_fetch  ("off" = none)
    providers:
      tavily: {}            # key from credentials / $TAVILY_API_KEY / Tavily CLI config file
      # firecrawl:
      #   apiKey: 'fc-...'  # or store/export FIRECRAWL_API_KEY

- id: web
  config:
    searchProvider: dsh-web-search
    fetchProvider: dsh-web-fetch   # or keep the built-in "http"
```

The equivalent in the settings document (`settings.yaml`), saved hot:

```yaml
web-search:
  search: firecrawl          # switches web_search to Firecrawl immediately
  providers:
    firecrawl:
      maxResults: 10
```

Switching search vendor = change `search:` and save. Done.

> **`fetch` migration**: the composition config used to mean "omit `fetch:` =
> no plugin fetch provider". The schema now spells that case explicitly as
> `"off"`. A profile patch that sets `fetch: tavily` keeps working unchanged;
> a user document that wrote an empty `fetch:` should now read `off`.

### The settings card

When a settings provider is mounted, the plugin registers the `web-search`
namespace and contributes a card to **Settings → Plugins → Plugin
configuration**. The card edits vendor selection and per-vendor options, plus
**capability-scoped API keys**: a **Search API key** (always shown, for the
search vendor) and a **Fetch API key** (shown only when the fetch vendor
differs from the search vendor — a shared vendor needs one key). Keys are
written through the **credentials** domain (never into `settings.yaml` or
hardcoded in the plugin), addressed by each vendor's `apiKeyEnv` reference,
and show a configured/unconfigured badge. A blank key box leaves the stored
key untouched.

> The card appears only while a settings service is running. Without one, the
> plugin still works exactly as composed (`cordis.patch.yml`), and edits to
> the settings document (`settings.yaml`) simply have no effect on it.

### Per-vendor options

Common to every vendor (all optional):

| Field         | Meaning                                                            |
| ------------- | ------------------------------------------------------------------ |
| `apiKey`      | inline key (prefer credentials/env/files for secrets)              |
| `apiKeyEnv`   | credential reference / env var name to read, per-vendor default    |
| `baseURL`     | endpoint root override                                             |
| `transport`   | `"auto"` (default), `"curl"`, or `"fetch"` — `auto` picks curl when a `*_proxy` env var is set (Node fetch ignores proxy env unless `NODE_USE_ENV_PROXY=1`) |
| `timeoutSec`  | per-request budget, default 30                                     |

Tavily adds `maxResults` (default 8), `searchDepth` (`basic`|`advanced`),
`includeAnswer` (default `true`). Firecrawl adds `limit` (default result count).

> Keys are resolved **per request**, so rotating a key in the credentials
> service, the env var, or the Tavily CLI config file needs no restart. A
> request with no key anywhere fails with a readable
> `WEB_PROVIDER_CREDENTIAL_MISSING`. The curl transport passes requests via
> `-K -` stdin config: keys never appear in the process argv.

## Adding a vendor

1. Create `lib/providers/.js`:

   ```js
   export const name = "myvendor";

   export function create(configOrThunk, h) {
     const readConfig = () => (typeof configOrThunk === "function" ? configOrThunk() : configOrThunk) ?? {};
     const options = () => ({
       apiKey: h.firstNonBlank(readConfig().apiKey) ?? h.envValue("MYVENDOR_API_KEY") ?? "",
       baseURL: readConfig().baseURL ?? "https://api.myvendor.example",
     });
     const search = {
       id: "myvendor",
       available: () => options().apiKey.length > 0 && URL.canParse(options().baseURL),
       async search(request, signal) {
         const o = options();
         const { status, bodyText } = await h.postJson(
           `${o.baseURL}/search`,
           { authorization: `Bearer ${o.apiKey}` },
           { q: request.query, limit: request.maxResults },
           { transport: readConfig().transport, timeoutSec: readConfig().timeoutSec, signal },
         );
         if (status < 200 || status >= 300) throw h.errors.providerError(`myvendor HTTP ${status}`);
         const data = h.parseJson(bodyText) ?? {};
         // → { content?, sources: [{url, title?, snippet?, publishedAt?}], truncated: false }
         return { sources: data.results ?? [], truncated: false };
       },
     };
     // Optional fetch provider: { id, available(), fetch(request, signal) }
     // → { url, statusCode, body: { kind: "text", content }, truncated }
     return { search };
   }
   ```

2. Register one line in `lib/registry.js` (`import` + array entry).
3. Add a `VENDOR_SCHEMAS` entry in `lib/config-schema.js` (built with
   `vendorSchema()` + per-vendor extras) so the settings namespace validates
   and the card can render it.
4. Add a row to the README vendor table.

That's the whole contract — helpers (`postJson`, `envValue`, `readHomeJson`,
`firstNonBlank`, `cleanSnippet`, `parseJson`, `positiveInteger`, `errors`,
`resolveCredential`) are injected, so vendor modules need no harness imports
and share the proxy-aware transport and the credentials-service key chain.

## Development & tests

```sh
npm test                        # node:test unit suite (mock ctx.web + settings service)
node tests/integration.mjs      # real cordis + dsh-web + file settings hot-switch
node tests/verify-client.mjs    # loads lib/client.js through a stub module table
```

## Notes

- Fully independent of your conversation LLM provider — custom
  OpenAI-compatible gateways work out of the box.
- `web_fetch` stays on the built-in anonymous `http` provider unless you set
  `fetch:` + `fetchProvider: dsh-web-fetch`; switch when you want JS rendering
  / PDF parsing (Firecrawl scrape) or clean article text (Tavily extract).
- Errors surface as typed `WebError`s (`WEB_PROVIDER_ERROR` /
  `WEB_PROVIDER_CREDENTIAL_MISSING` / `WEB_ABORTED`) when
  `@deepseek-ai/dsh-web` is importable, plain coded Errors otherwise.

数据来源:公开的 DeepSeek Harness 插件目录与各插件 GitHub 仓库。本站为独立第三方目录,与 DeepSeek、幻方(High-Flyer)及插件作者均无隶属或背书关系。

每日精选 Skill 推荐,免费送到你邮箱

输入邮箱,每天接收一个精选 AI Agent 技能推荐。完全免费,持续更新。

提交后我们会发送一封确认邮件,点击邮件里的链接才会开始收信。

完全免费,取消任意时间。我们不会发送垃圾邮件。