@tonydua/dsh-web-search-exa 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 dsh-anything contributors
3
+ Copyright (c) 2026 Tony Du
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.i18n.yaml CHANGED
@@ -1,5 +1,5 @@
1
1
  # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record.
4
- README.md: 43f37d9ed00f718982fecc2684e40f2ce1ea997c
5
- README.zh.md: c525e2ccac7569211e76321e43982bae1d38342c
4
+ README.md: 6d6ec687270295a227331625308475827065fe17
5
+ README.zh.md: ca4354b5aa7710b68e1717f1bc0c6209e1914bc6
package/README.md CHANGED
@@ -1,26 +1,41 @@
1
1
  # @tonydua/dsh-web-search-exa
2
2
 
3
- > **Built with [deepseek-v4-flash](https://api-docs.deepseek.com) inside [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh).**
4
-
5
- An Exa-backed `WebSearchProvider` for the DeepSeek Harness web capability seam
6
- (`ctx.web`), with an **anonymous** fallback: when no API key is configured,
7
- search routes through Exa's hosted MCP server (`https://mcp.exa.ai/mcp`) via
8
- JSON-RPC 2.0 with **no credentials at all** — Exa's documented unauthenticated
9
- public MCP fallback (rate-limited). With an `EXA_API_KEY`, the lighter REST
10
- endpoint is used instead.
11
-
12
- This is an implementation package: it registers a provider INTO `ctx.web`
13
- (`inject: ['web']`) and owns no model-facing tools (those belong to
14
- `@deepseek-ai/dsh-tool-web`). The model-facing `web_search` / `web_fetch` tools,
15
- their prompt sections, and the Web result cards are unchanged.
16
-
17
- ## Why this package exists (difference from the official one)
3
+ **English** | [简体中文](README.zh.md)
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@tonydua/dsh-web-search-exa)](https://www.npmjs.com/package/@tonydua/dsh-web-search-exa)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@tonydua/dsh-web-search-exa)](https://www.npmjs.com/package/@tonydua/dsh-web-search-exa)
7
+ [![License](https://img.shields.io/npm/l/@tonydua/dsh-web-search-exa)](LICENSE)
8
+ [![GitHub stars](https://img.shields.io/github/stars/TonyDua/dsh-web-search-exa)](https://github.com/TonyDua/dsh-web-search-exa)
9
+ [![GitHub issues](https://img.shields.io/github/issues/TonyDua/dsh-web-search-exa)](https://github.com/TonyDua/dsh-web-search-exa)
10
+ [![Node](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white)](package.json)
11
+
12
+ > Zero-config [Exa](https://exa.ai) web search for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh):
13
+ > **no API key required** — a `WebSearchProvider` for the `ctx.web` seam with an
14
+ > anonymous MCP fallback plus a keyed REST path.
15
+
16
+ Built with [deepseek-v4-flash](https://api-docs.deepseek.com) inside DeepSeek Harness (dsh).
17
+
18
+ ## Features
19
+
20
+ - 🆓 **Zero-config, keyless by default** — searches route through Exa's hosted MCP
21
+ server (`mcp.exa.ai/mcp`) with **no credentials at all** (Exa's documented
22
+ unauthenticated public MCP, rate-limited).
23
+ - 🔑 **Keyed REST upgrade** — set `EXA_API_KEY` and it automatically switches to
24
+ Exa's `POST /search` REST API (higher limits, no behavior change).
25
+ - 🔌 **Drop-in provider** — registers into the dsh `ctx.web` seam; the existing
26
+ model-facing `web_search` / `web_fetch` tools, prompt sections, and result
27
+ cards work unchanged.
28
+ - 🎛️ **`providerId` switch** — can coexist with the official
29
+ `@deepseek-ai/dsh-web-search-exa` package in one profile (no duplicate-id
30
+ collisions, no silent overrides).
31
+ - 📦 **npm-publishable** — MIT, ESM, bundled types, `files` limited to `lib/`.
32
+
33
+ ## Why this package exists (vs. the official one)
18
34
 
19
35
  The DeepSeek Harness ships an official Exa provider,
20
36
  [`@deepseek-ai/dsh-web-search-exa`](https://www.npmjs.com/package/@deepseek-ai/dsh-web-search-exa).
21
- This package is the **zero-config variant**: it adds the anonymous MCP fallback
22
- the official one does not have, and keeps the same keyed REST behavior when an
23
- API key is configured.
37
+ This package is its **zero-config variant**: it adds the anonymous MCP fallback
38
+ the official one does not have, and keeps the same keyed REST behavior.
24
39
 
25
40
  | | Official `@deepseek-ai/dsh-web-search-exa` | This package `@tonydua/dsh-web-search-exa` |
26
41
  |---|---|---|
@@ -32,21 +47,15 @@ API key is configured.
32
47
  | Cordis plugin name | `web-search-exa` | `web-search-exa` |
33
48
  | Config keys | `apiKey`, `baseURL`, `searchType`, `numResults`, `highlightsPerResult` | `apiKey`, `apiKeyEnv`, `apiURL`, `mcpURL`, `searchType`, `numResults`, `highlightsPerResult`, `providerId` |
34
49
 
35
- ## Acknowledgements
36
-
37
- The anonymous MCP integration follows the `web_search` implementation in
38
- [can1357/oh-my-pi](https://github.com/can1357/oh-my-pi) (`packages/coding-agent/src/web/search/providers/exa.ts`
39
- and `src/exa/mcp-client.ts`) and the
40
- [`@oh-my-pi/exa`](https://www.npmjs.com/package/@oh-my-pi/exa) plugin: same
41
- "REST when a key exists, credential-free `mcp.exa.ai/mcp` otherwise" strategy,
42
- same `x-exa-source` attribution header, same `Title:`-section response parsing.
43
- Thanks to the oh-my-pi (omp) project for pioneering the zero-config Exa
44
- integration.
50
+ ## Which one should I use?
45
51
 
46
- Thanks also to **[Exa](https://exa.ai)** for providing and operating the
47
- **free, unauthenticated hosted MCP server** (`mcp.exa.ai/mcp`) that makes this
48
- package's zero-config default possible. Exa's hosted MCP is an official Exa
49
- product; anonymous usage is rate-limited (see Known limitations).
52
+ - **You have an `EXA_API_KEY` and want the officially maintained package** →
53
+ use `@deepseek-ai/dsh-web-search-exa`. It is the canonical implementation.
54
+ - **You want to try Exa search with zero setup, no key, no cost commitment** →
55
+ use this package. It degrades gracefully: anonymous MCP by default, REST
56
+ automatically when a key appears.
57
+ - **You want both** → install both and use the `providerId` switch (see
58
+ [Coexistence](#coexistence-with-the-official-package)).
50
59
 
51
60
  ## How it works
52
61
 
@@ -62,6 +71,49 @@ enforces `maxResults` on the way back. Anonymous usage is rate-limited by Exa:
62
71
  an HTTP 429 surfaces as a `WEB_PROVIDER_ERROR` with a hint to configure an API
63
72
  key (which also switches to the REST path automatically).
64
73
 
74
+ ## Installation (into a dsh profile)
75
+
76
+ ```powershell
77
+ dsh plugin --profile web add ../plugins/dsh-web-search-exa
78
+ ```
79
+
80
+ Then enable the provider and select it. Either merge into
81
+ `$DSH_HOME/profiles/web/cordis.patch.yml` (persistent):
82
+
83
+ ```yaml
84
+ - id: web-search-exa
85
+ name: '@tonydua/dsh-web-search-exa'
86
+ config:
87
+ apiKeyEnv: EXA_API_KEY
88
+ - id: web
89
+ name: '@deepseek-ai/dsh-web'
90
+ config:
91
+ searchProvider: exa
92
+ ```
93
+
94
+ …or pass `patches/exa-anon-search.patch.yml` as an overlay:
95
+
96
+ ```powershell
97
+ dsh --profile web --patch patches/exa-anon-search.patch.yml
98
+ ```
99
+
100
+ Alternatively, select the provider at runtime with the environment variable
101
+ `$DSH_WEB_SEARCH_PROVIDER=exa` (no config edit needed).
102
+
103
+ Restart `dsh web` for changes to take effect. The existing model-facing
104
+ `web_search` tool then routes through this provider — no tool config changes.
105
+
106
+ ### Runtime singleton compatibility
107
+
108
+ `@deepseek-ai/dsh-tools` is a dsh runtime singleton and must resolve to one
109
+ physical package instance in a profile. This provider does not depend on it;
110
+ the requirement belongs to the host profile. If another third-party plugin
111
+ installs `@deepseek-ai/dsh-tools` as a nested regular dependency instead of a
112
+ peer dependency, fix that plugin's dependency declaration or make the profile
113
+ package manager resolve the shared instance before debugging search errors.
114
+ Otherwise dsh's agent loop can fail before the provider is called with an
115
+ error such as `Cannot read properties of undefined (reading 'prepare')`.
116
+
65
117
  ## Configuration
66
118
 
67
119
  | Key | Default | Meaning |
@@ -108,51 +160,6 @@ switch:
108
160
  Simplest alternative: install only one of the two packages per profile — the
109
161
  defaults then work as-is.
110
162
 
111
- ## Installation (into a dsh profile)
112
-
113
- From this repository:
114
-
115
- ```powershell
116
- dsh plugin --profile web add ../plugins/dsh-web-search-exa
117
- ```
118
-
119
- Then enable the provider and select it. Either merge into
120
- `$DSH_HOME/profiles/web/cordis.patch.yml` (persistent):
121
-
122
- ```yaml
123
- - id: web-search-exa
124
- name: '@tonydua/dsh-web-search-exa'
125
- config:
126
- apiKeyEnv: EXA_API_KEY
127
- - id: web
128
- name: '@deepseek-ai/dsh-web'
129
- config:
130
- searchProvider: exa
131
- ```
132
-
133
- …or pass `patches/exa-anon-search.patch.yml` as an overlay:
134
-
135
- ```powershell
136
- dsh --profile web --patch patches/exa-anon-search.patch.yml
137
- ```
138
-
139
- Alternatively, select the provider at runtime with the environment variable
140
- `$DSH_WEB_SEARCH_PROVIDER=exa` (no config edit needed).
141
-
142
- Restart `dsh web` for changes to take effect. The existing model-facing
143
- `web_search` tool then routes through this provider — no tool config changes.
144
-
145
- ### Runtime singleton compatibility
146
-
147
- `@deepseek-ai/dsh-tools` is a dsh runtime singleton and must resolve to one
148
- physical package instance in a profile. This provider does not depend on it;
149
- the requirement belongs to the host profile. If another third-party plugin
150
- installs `@deepseek-ai/dsh-tools` as a nested regular dependency instead of a
151
- peer dependency, fix that plugin's dependency declaration or make the profile
152
- package manager resolve the shared instance before debugging search errors.
153
- Otherwise dsh's agent loop can fail before the provider is called with an
154
- error such as `Cannot read properties of undefined (reading 'prepare')`.
155
-
156
163
  ## In the Web panel
157
164
 
158
165
  **Status: configuration is done in the profile patch layer, not the Web UI —
@@ -183,30 +190,46 @@ plugin namespaces. What is true today:
183
190
  fields above become editable live in Settings → Plugins (mirroring how the
184
191
  official cards work).
185
192
 
186
- ## Open-source notes
187
-
188
- - License: MIT (see `LICENSE`).
189
- - Repository: <https://github.com/TonyDua/dsh-web-search-exa>.
190
- - The package is npm-packable: `publishConfig.access: public`, `files` limited
191
- to `lib/`, ESM with an `exports` map and bundled type declarations. Run
192
- `npm test` and `npm pack --dry-run` before publishing.
193
- - To publish: `npm login` (against `registry.npmjs.org`, not the npmmirror
194
- default in your `.npmrc`), then
195
- `npm publish --registry https://registry.npmjs.org --cache <writable-cache>`.
196
- The `@tonydua` scope is claimed on first publish by the publishing account.
197
-
198
- ## Known limitations
199
-
200
- - **Anonymous MCP is rate-limited** (HTTP 429 → `WEB_PROVIDER_ERROR` with a
201
- hint). Configure `EXA_API_KEY` for higher limits; the provider then uses the
202
- REST path automatically.
203
- - **Snippet-less results are dropped** (seam rule: no portable snippet).
204
- Anonymous MCP usually returns full `Text` blocks, which are truncated to
205
- `MAX_SNIPPET_CHARS` (500) for the snippet.
206
- - **Only `query` + result-count controls are exposed**; recency, domain
207
- filters, and deep-search modes are not mapped (they can be added later, as
208
- the seam's provider-neutral fields evolve).
209
- - Anonymous MCP responses are parsed from the `Title:`-section text format;
210
- structural changes to Exa's hosted MCP output may require a parser update.
211
- - **No Web UI settings entry yet** — configuration goes through the profile
212
- patch layer (see "In the Web panel").
193
+ ## FAQ
194
+
195
+ **Q: Do I need an Exa API key?**
196
+ No. Without a key the provider uses Exa's free anonymous hosted MCP. With a key
197
+ it uses the REST API for higher limits.
198
+
199
+ **Q: I got HTTP 429 / rate limited.**
200
+ That's Exa's anonymous-MCP rate limit. Configure `EXA_API_KEY` (or the
201
+ `apiKey` field) and the provider switches to the REST path automatically.
202
+
203
+ **Q: Can I run this alongside the official Exa provider?**
204
+ Yes — give this package a distinct `providerId` and select it explicitly
205
+ (see [Coexistence](#coexistence-with-the-official-package)).
206
+
207
+ **Q: Why don't I see a settings entry in the Web UI?**
208
+ This version registers the `web-search-exa` settings namespace server-side
209
+ only; a UI card is planned for the next version. Configure through
210
+ `cordis.patch.yml` or environment variables for now (see
211
+ [In the Web panel](#in-the-web-panel)).
212
+
213
+ ## Acknowledgements
214
+
215
+ The anonymous MCP integration follows the `web_search` implementation in
216
+ [can1357/oh-my-pi](https://github.com/can1357/oh-my-pi) (`packages/coding-agent/src/web/search/providers/exa.ts`
217
+ and `src/exa/mcp-client.ts`) and the
218
+ [`@oh-my-pi/exa`](https://www.npmjs.com/package/@oh-my-pi/exa) plugin: same
219
+ "REST when a key exists, credential-free `mcp.exa.ai/mcp` otherwise" strategy,
220
+ same `x-exa-source` attribution header, same `Title:`-section response parsing.
221
+ Thanks to the oh-my-pi (omp) project for pioneering the zero-config Exa
222
+ integration.
223
+
224
+ Thanks also to **[Exa](https://exa.ai)** for providing and operating the
225
+ **free, unauthenticated hosted MCP server** (`mcp.exa.ai/mcp`) that makes this
226
+ package's zero-config default possible. Exa's hosted MCP is an official Exa
227
+ product; anonymous usage is rate-limited (see FAQ).
228
+
229
+ ## Changelog
230
+
231
+ See [CHANGELOG.md](CHANGELOG.md) for all notable changes.
232
+
233
+ ## License
234
+
235
+ MIT — see [LICENSE](LICENSE).
package/README.zh.md CHANGED
@@ -1,10 +1,26 @@
1
1
  # @tonydua/dsh-web-search-exa
2
2
 
3
- > **使用 [deepseek-v4-flash](https://api-docs.deepseek.com) 与 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)开发。**
3
+ [English](README.md) | **简体中文**
4
4
 
5
- 为 DeepSeek Harness web 能力 seam(`ctx.web`)提供的 Exa 搜索提供方(`WebSearchProvider`),内置**匿名兜底**:未配置 API key 时,搜索经由 Exa 官方托管的 MCP 服务器(`https://mcp.exa.ai/mcp`)以 JSON-RPC 2.0 发起,**完全不携带任何凭据** —— 即 Exa 官方提供的免认证公共 MCP fallback(有限流)。配置了 `EXA_API_KEY` 时则改用更轻量的 REST 端点。
5
+ [![npm version](https://img.shields.io/npm/v/@tonydua/dsh-web-search-exa)](https://www.npmjs.com/package/@tonydua/dsh-web-search-exa)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@tonydua/dsh-web-search-exa)](https://www.npmjs.com/package/@tonydua/dsh-web-search-exa)
7
+ [![License](https://img.shields.io/npm/l/@tonydua/dsh-web-search-exa)](LICENSE)
8
+ [![GitHub stars](https://img.shields.io/github/stars/TonyDua/dsh-web-search-exa)](https://github.com/TonyDua/dsh-web-search-exa)
9
+ [![GitHub issues](https://img.shields.io/github/issues/TonyDua/dsh-web-search-exa)](https://github.com/TonyDua/dsh-web-search-exa)
10
+ [![Node](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white)](package.json)
6
11
 
7
- 这是一个**实现**包:它向 `ctx.web` 注册提供方(`inject: ['web']`),不拥有面向模型的工具(后者属于 `@deepseek-ai/dsh-tool-web`)。模型侧的 `web_search` / `web_fetch` 工具、提示词区段与 Web 结果卡片均不受影响。
12
+ > 为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)提供**零配置**的 [Exa](https://exa.ai) 网页搜索:
13
+ > **无需 API key** —— 一个 `ctx.web` seam 的 `WebSearchProvider`,内置匿名 MCP 兜底 + 带 key 的 REST 路径。
14
+
15
+ 使用 [deepseek-v4-flash](https://api-docs.deepseek.com) 在 DeepSeek Harness(dsh)内开发。
16
+
17
+ ## 特性
18
+
19
+ - 🆓 **零配置、默认免 key** —— 搜索经由 Exa 官方托管的 MCP 服务器(`mcp.exa.ai/mcp`),**完全不携带凭据**(Exa 官方提供的免认证公共 MCP,有限流)。
20
+ - 🔑 **配 key 自动升级 REST** —— 设置 `EXA_API_KEY` 后自动切换到 Exa `POST /search` REST API(额度更高,行为不变)。
21
+ - 🔌 **即插即用** —— 注册进 dsh `ctx.web` seam;模型侧的 `web_search` / `web_fetch` 工具、提示词区段与结果卡片无需任何改动。
22
+ - 🎛️ **`providerId` 开关** —— 可与官方 `@deepseek-ai/dsh-web-search-exa` 在同一 profile 共存(不撞 id、无黑箱覆盖)。
23
+ - 📦 **可直接发布** —— MIT、ESM、内置类型声明、`files` 仅含 `lib/`。
8
24
 
9
25
  ## 为什么有这个包(与官方包的差异)
10
26
 
@@ -20,11 +36,11 @@ DeepSeek Harness 自带官方 Exa 提供方 [`@deepseek-ai/dsh-web-search-exa`](
20
36
  | Cordis 插件名 | `web-search-exa` | `web-search-exa` |
21
37
  | 配置键 | `apiKey`、`baseURL`、`searchType`、`numResults`、`highlightsPerResult` | `apiKey`、`apiKeyEnv`、`apiURL`、`mcpURL`、`searchType`、`numResults`、`highlightsPerResult`、`providerId` |
22
38
 
23
- ## 致谢(Acknowledgements)
24
-
25
- 匿名 MCP 接入方式参考了 [can1357/oh-my-pi](https://github.com/can1357/oh-my-pi) 的 `web_search` 实现(`packages/coding-agent/src/web/search/providers/exa.ts` 与 `src/exa/mcp-client.ts`)以及 [`@oh-my-pi/exa`](https://www.npmjs.com/package/@oh-my-pi/exa) 插件:同样的"有 key 走 REST、无 key 走免凭据 `mcp.exa.ai/mcp`"策略、同样的 `x-exa-source` 来源头、同样的 `Title:` 分节响应解析。感谢 oh-my-pi(omp)项目率先打通了零配置的 Exa 接入。
39
+ ## 我该用哪个?
26
40
 
27
- 同时感谢 **[Exa](https://exa.ai)** 提供并运营这个**免费、免认证的托管 MCP 服务器**(`mcp.exa.ai/mcp`)——正是它让本包的零配置默认路径成为可能。Exa 托管 MCP 是 Exa 的官方产品;匿名使用有限流(见"已知限制")。
41
+ - **你有 `EXA_API_KEY`,且想要官方维护的包** → 用 `@deepseek-ai/dsh-web-search-exa`,它是官方标准实现。
42
+ - **想零配置、免 key、无成本负担地试用 Exa 搜索** → 用本包。优雅降级:默认匿名 MCP,出现 key 自动走 REST。
43
+ - **两个都想要** → 一起装,用 `providerId` 开关(见[与官方包共存](#与官方包共存))。
28
44
 
29
45
  ## 工作原理
30
46
 
@@ -35,6 +51,39 @@ DeepSeek Harness 自带官方 Exa 提供方 [`@deepseek-ai/dsh-web-search-exa`](
35
51
 
36
52
  匿名 MCP 路径不发送任何凭据,来源标识通过 `x-exa-source: dsh-anything` 头携带。结果按 seam 的 `WebSearchSource` 形状规范化(`url`、`title`、`snippet`、`publishedAt`),`maxResults` 由 seam 在返回路径上强制执行。匿名使用受 Exa 限流:HTTP 429 会以 `WEB_PROVIDER_ERROR` 呈现,并提示配置 API key(配置后自动切换到 REST 路径)。
37
53
 
54
+ ## 安装(装入 dsh profile)
55
+
56
+ ```powershell
57
+ dsh plugin --profile web add ../plugins/dsh-web-search-exa
58
+ ```
59
+
60
+ 然后启用并选中该提供方。合并进 `$DSH_HOME/profiles/web/cordis.patch.yml`(持久生效):
61
+
62
+ ```yaml
63
+ - id: web-search-exa
64
+ name: '@tonydua/dsh-web-search-exa'
65
+ config:
66
+ apiKeyEnv: EXA_API_KEY
67
+ - id: web
68
+ name: '@deepseek-ai/dsh-web'
69
+ config:
70
+ searchProvider: exa
71
+ ```
72
+
73
+ 或把 `patches/exa-anon-search.patch.yml` 作为覆盖层传入:
74
+
75
+ ```powershell
76
+ dsh --profile web --patch patches/exa-anon-search.patch.yml
77
+ ```
78
+
79
+ 也可以不修改配置,直接用环境变量 `$DSH_WEB_SEARCH_PROVIDER=exa` 在运行时选中该提供方。
80
+
81
+ 重启 `dsh web` 生效。模型侧的 `web_search` 工具随即走该提供方,无需改任何工具配置。
82
+
83
+ ### 运行时单例兼容性
84
+
85
+ `@deepseek-ai/dsh-tools` 是 dsh 的运行时单例包,一个 profile 中必须解析到同一份物理包实例。本插件本身不依赖它;这是宿主 profile 的依赖约束。如果 profile 中的其他第三方插件把 `@deepseek-ai/dsh-tools` 错误声明成普通嵌套依赖,而不是 peer dependency,应先修正该插件的依赖声明,或让 profile 的包管理器统一解析到共享实例,再排查搜索错误。否则 dsh agent loop 可能在 provider 被调用前就因 `Cannot read properties of undefined (reading 'prepare')` 失败。
86
+
38
87
  ## 配置
39
88
 
40
89
  | 配置键 | 默认值 | 含义 |
@@ -72,63 +121,41 @@ DeepSeek Harness 自带官方 Exa 提供方 [`@deepseek-ai/dsh-web-search-exa`](
72
121
 
73
122
  最简单的替代方案:每个 profile 只装其中一个包,默认配置即可直接使用。
74
123
 
75
- ## 安装(装入 dsh profile)
76
-
77
- 在本仓库目录下:
78
-
79
- ```powershell
80
- dsh plugin --profile web add ../plugins/dsh-web-search-exa
81
- ```
82
-
83
- 然后启用并选中该提供方。合并进 `$DSH_HOME/profiles/web/cordis.patch.yml`(持久生效):
124
+ ## 在 Web 面板中的呈现
84
125
 
85
- ```yaml
86
- - id: web-search-exa
87
- name: '@tonydua/dsh-web-search-exa'
88
- config:
89
- apiKeyEnv: EXA_API_KEY
90
- - id: web
91
- name: '@deepseek-ai/dsh-web'
92
- config:
93
- searchProvider: exa
94
- ```
126
+ **状态:本版本的配置入口在 profile 补丁层,不在 Web UI —— 没有可编辑的界面入口。** Settings UI 只渲染客户端插件为固定命名空间(`shell`、`agent-loop`、`web-search-deepseek`)手工注册的卡片,对任意插件命名空间没有通用表单。当前实际情况:
95
127
 
96
- 或把 `patches/exa-anon-search.patch.yml` 作为覆盖层传入:
128
+ - **插件清单**(Settings → Plugins):启用后自动出现 `web-search-exa`(`@tonydua/dsh-web-search-exa`)条目 —— 清单直接读取 Cordis loader 的实时条目,无需额外代码。
129
+ - **设置命名空间**(服务端):插件通过 `installSettingsSection` 注册了 `web-search-exa` 段,数据层可写——但**没有任何客户端卡片绑定它**,所以界面上不显示。内置的 "Web search" 卡片编辑的是官方 `web-search-deepseek` 命名空间,与本插件无关。
130
+ - **现在怎么改配置**:编辑 `$DSH_HOME/profiles/web/cordis.patch.yml` 里本插件的 `config`(字段与默认值见上方配置表),重启 `dsh web`;或用环境变量 `EXA_API_KEY` / `$DSH_WEB_SEARCH_PROVIDER`。`apiKey` 标记了 `role('secret')`,任何 `describe()` 响应都不会暴露其值。
131
+ - **搜索结果卡片**:`web_search` 调用经 `dsh-tool-web` 照常渲染 `web` 结果卡片(来源、摘要、日期),与提供方无关 —— 匿名 Exa 的结果与 DeepSeek 搜索显示完全一致。
97
132
 
98
- ```powershell
99
- dsh --profile web --patch patches/exa-anon-search.patch.yml
100
- ```
133
+ **路线图(下一版本)**:新增注册到 `settings.plugin.item` slot 的客户端卡片,绑定 `web-search-exa` 命名空间,让上表所有字段可以在 Settings → Plugins 里实时编辑(与官方卡片同机制)。
101
134
 
102
- 也可以不修改配置,直接用环境变量 `$DSH_WEB_SEARCH_PROVIDER=exa` 在运行时选中该提供方。
135
+ ## 常见问题(FAQ)
103
136
 
104
- 重启 `dsh web` 生效。模型侧的 `web_search` 工具随即走该提供方,无需改任何工具配置。
137
+ **Q: 需要 Exa API key 吗?**
138
+ 不需要。无 key 时走 Exa 免费匿名托管 MCP;配 key 后走 REST API 获得更高额度。
105
139
 
106
- ### 运行时单例兼容性
140
+ **Q: 遇到 HTTP 429 / 限流怎么办?**
141
+ 这是 Exa 匿名 MCP 的限流。配置 `EXA_API_KEY`(或 `apiKey` 字段),提供方会自动切到 REST 路径。
107
142
 
108
- `@deepseek-ai/dsh-tools` 是 dsh 的运行时单例包,一个 profile 中必须解析到同一份物理包实例。本插件本身不依赖它;这是宿主 profile 的依赖约束。如果 profile 中的其他第三方插件把 `@deepseek-ai/dsh-tools` 错误声明成普通嵌套依赖,而不是 peer dependency,应先修正该插件的依赖声明,或让 profile 的包管理器统一解析到共享实例,再排查搜索错误。否则 dsh agent loop 可能在 provider 被调用前就因 `Cannot read properties of undefined (reading 'prepare')` 失败。
143
+ **Q: 能和官方 Exa 提供方一起装吗?**
144
+ 可以——给本包一个不同的 `providerId` 并显式选中即可(见[与官方包共存](#与官方包共存))。
109
145
 
110
- ## 在 Web 面板中的呈现
146
+ **Q: 为什么 Web UI 里没有设置入口?**
147
+ 本版本只在服务端注册了 `web-search-exa` 设置命名空间;UI 卡片计划在下一版本提供。现阶段通过 `cordis.patch.yml` 或环境变量配置(见[在 Web 面板中的呈现](#在-web-面板中的呈现))。
111
148
 
112
- **状态:本版本的配置入口在 profile 补丁层,不在 Web UI —— 没有可编辑的界面入口。** Settings UI 只渲染客户端插件为固定命名空间(`shell`、`agent-loop`、`web-search-deepseek`)手工注册的卡片,对任意插件命名空间没有通用表单。当前实际情况:
149
+ ## 致谢(Acknowledgements)
113
150
 
114
- - **插件清单**(Settings → Plugins):启用后自动出现 `web-search-exa`(`@tonydua/dsh-web-search-exa`)条目 —— 清单直接读取 Cordis loader 的实时条目,无需额外代码。
115
- - **设置命名空间**(服务端):插件通过 `installSettingsSection` 注册了 `web-search-exa` 段,数据层可写——但**没有任何客户端卡片绑定它**,所以界面上不显示。内置的 "Web search" 卡片编辑的是官方 `web-search-deepseek` 命名空间,与本插件无关。
116
- - **现在怎么改配置**:编辑 `$DSH_HOME/profiles/web/cordis.patch.yml` 里本插件的 `config`(字段与默认值见上方配置表),重启 `dsh web`;或用环境变量 `EXA_API_KEY` / `$DSH_WEB_SEARCH_PROVIDER`。`apiKey` 标记了 `role('secret')`,任何 `describe()` 响应都不会暴露其值。
117
- - **搜索结果卡片**:`web_search` 调用经 `dsh-tool-web` 照常渲染 `web` 结果卡片(来源、摘要、日期),与提供方无关 —— 匿名 Exa 的结果与 DeepSeek 搜索显示完全一致。
151
+ 匿名 MCP 接入方式参考了 [can1357/oh-my-pi](https://github.com/can1357/oh-my-pi) 的 `web_search` 实现(`packages/coding-agent/src/web/search/providers/exa.ts` 与 `src/exa/mcp-client.ts`)以及 [`@oh-my-pi/exa`](https://www.npmjs.com/package/@oh-my-pi/exa) 插件:同样的"有 key 走 REST、无 key 走免凭据 `mcp.exa.ai/mcp`"策略、同样的 `x-exa-source` 来源头、同样的 `Title:` 分节响应解析。感谢 oh-my-pi(omp)项目率先打通了零配置的 Exa 接入。
118
152
 
119
- **路线图(下一版本)**:新增注册到 `settings.plugin.item` slot 的客户端卡片,绑定 `web-search-exa` 命名空间,让上表所有字段可以在 Settings → Plugins 里实时编辑(与官方卡片同机制)。
153
+ 同时感谢 **[Exa](https://exa.ai)** 提供并运营这个**免费、免认证的托管 MCP 服务器**(`mcp.exa.ai/mcp`)——正是它让本包的零配置默认路径成为可能。Exa 托管 MCP 是 Exa 的官方产品;匿名使用有限流(见 FAQ)。
120
154
 
121
- ## 开源说明
155
+ ## 更新日志(Changelog)
122
156
 
123
- - 许可证:MIT(见 `LICENSE`)。
124
- - 仓库:<https://github.com/TonyDua/dsh-web-search-exa>。
125
- - 包已可执行 npm 打包:`publishConfig.access: public`、`files` 仅含 `lib/`、ESM 且带 `exports` 映射与类型声明。发布前请执行 `npm test` 和 `npm pack --dry-run`。
126
- - 发布:先 `npm login`(对着 `registry.npmjs.org`,不是 `.npmrc` 里的 npmmirror 默认源),再 `npm publish --registry https://registry.npmjs.org --cache <可写缓存目录>`。`@tonydua` scope 会在首次发布时自动归属到发布账号。
157
+ 所有变更见 [CHANGELOG.md](CHANGELOG.md)。
127
158
 
128
- ## 已知限制
159
+ ## 许可证
129
160
 
130
- - **匿名 MCP 有限流**(HTTP 429 → `WEB_PROVIDER_ERROR` 并附提示)。配置 `EXA_API_KEY` 可提额,提供方会自动切到 REST 路径。
131
- - **无 snippet 的结果会被丢弃**(seam 规则:没有可移植的摘要)。匿名 MCP 通常返回完整 `Text` 块,会截断到 `MAX_SNIPPET_CHARS`(500 字符)作为 snippet。
132
- - **只暴露查询与结果数控制**:新近程度、域名过滤、深度搜索等尚未映射(可等 seam 的提供方无关字段扩展后补充)。
133
- - 匿名 MCP 响应按 `Title:` 分节文本格式解析;Exa 托管 MCP 输出结构变化时可能需要更新解析器。
134
- - **暂无 Web UI 设置入口**——配置走 profile 补丁层(见"在 Web 面板中的呈现")。
161
+ MIT —— 见 [LICENSE](LICENSE)。
package/package.json CHANGED
@@ -1,14 +1,24 @@
1
1
  {
2
2
  "name": "@tonydua/dsh-web-search-exa",
3
- "description": "Exa-backed WebSearchProvider for the DeepSeek Harness web capability seam (ctx.web), with an anonymous MCP fallback — no API key required",
4
- "version": "0.1.0",
3
+ "description": "Zero-config Exa web search provider for DeepSeek Harness (dsh): keyless anonymous MCP fallback (mcp.exa.ai/mcp) plus keyed REST search — a drop-in WebSearchProvider for the ctx.web seam, no API key required.",
4
+ "version": "0.1.1",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",
8
8
  "cordis",
9
9
  "exa",
10
+ "exa-search",
10
11
  "web-search",
11
- "anonymous"
12
+ "websearch",
13
+ "search-provider",
14
+ "mcp",
15
+ "mcp-server",
16
+ "anonymous",
17
+ "zero-config",
18
+ "no-api-key",
19
+ "deepseek",
20
+ "llm",
21
+ "agent"
12
22
  ],
13
23
  "license": "MIT",
14
24
  "repository": {