dsh-exa-search 0.0.0-stage → 1.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ ## 1.0.1
4
+
5
+ First public release: Exa-backed `web_search` / `web_fetch`, ten additional tools, and three access modes.
6
+
7
+ ### Tools
8
+
9
+ - `exa_search` — domain / category / date / text / subpage filters, `additionalQueries`, and the same arguments as Exa's advanced search.
10
+ - `exa_deep_search` — Deep Search with a synthesized answer; a `text` output schema is sent by default so Exa actually produces one.
11
+ - `exa_fetch` — up to five URLs, `format: text | highlights | summary`, optional subpages, three-way concurrency, and per-URL failure isolation.
12
+ - `exa_find_similar`, `exa_answer` — REST-only helpers for expanding a known URL and citing an answer.
13
+ - `exa_research` — Exa Agent with `run` / `cancel` / `stop`, resumable by `runId`.
14
+ - `exa_monitor` — list / get / runs / create / update / trigger / delete, webhook events, manual-only monitors (`manual: true` or `period: "none"`).
15
+ - `exa_webset`, `exa_batch` — registered only when the account/team is entitled (probed with free GETs).
16
+ - `exa_status` — active mode, per-mode tool counts, upstream, entitlements, spend estimate.
17
+ - Per-group tool switches in Settings, persisted across restarts.
18
+
19
+ ### Access modes
20
+
21
+ - **Keyless** via Exa's public MCP endpoint (advanced search opted in; no `agent_run`).
22
+ - **API key** via the full REST surface (search, contents, find similar, answer, agent, monitors, websets, batch).
23
+ - **Exa account** via OAuth 2.1 + PKCE with dynamic client registration; the exposed MCP tools follow what the account actually offers.
24
+ - Settings shows the live mode × tool matrix and can run a minimal "Test connection" search.
25
+
26
+ ### Reliability
27
+
28
+ - Hard timeouts on every request, retries with backoff for 429/5xx, `Retry-After` support, and cancellation that interrupts immediately.
29
+ - One forced token refresh on a 401; a revoked refresh token drops the grant and falls back to a saved key or keyless mode.
30
+ - Credential records use the required `<scope>/<id>` shape, are resolved through an injection (late activation is safe), and are validated before every write.
31
+ - A dead refresh token, an invalid saved key, or an unentitled team feature can no longer leave web search silently broken.
32
+ - Process-level spend estimate from Exa's `costDollars`, plus a redacted diagnostics endpoint and "Copy diagnostics" button.
33
+
34
+ ### Privacy
35
+
36
+ - API key and OAuth tokens live in the Harness credential service, never in the package.
37
+ - Diagnostics exclude email, key fragments, tokens and file paths.
38
+ - The published tarball contains no machine paths, credentials or account identifiers.
39
+
40
+ ### Notes
41
+
42
+ - Unit tests (`node --test`), the CI workflow and the English README live in the repository; they are not part of the npm tarball.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-exa-search contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,136 @@
1
+ # Exa Search for DeepSeek Harness
2
+
3
+ This plugin provides web search and page fetching for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) through [Exa](https://exa.ai). It supports three access modes — keyless, API key and Exa account — and registers tools according to the active mode.
4
+
5
+ Once installed:
6
+
7
+ - `web_search` and `web_fetch` keep their existing arguments and behaviour; requests are served by Exa.
8
+ - The plugin registers ten additional `exa_*` tools. A tool is exposed to the model only when the active mode can call it.
9
+ - **Settings → Exa Search** switches modes and shows the availability of every tool under all three modes.
10
+
11
+ ## Tool catalog
12
+
13
+ | Group | Tool | Purpose | Keyless | API key | Exa account |
14
+ | --- | --- | --- | --- | --- | --- |
15
+ | Search | `web_search` | Harness web search, served by Exa | ✓ | ✓ | ✓ |
16
+ | Search | `exa_search` | Domain, category, date, text and subpage filters | ✓ | ✓ | ✓ |
17
+ | Search | `exa_deep_search` | Deep Search: multi-step retrieval with a cited answer | — | ✓ | — |
18
+ | Contents | `web_fetch` | Harness page fetch, served by Exa | ✓ | ✓ | ✓ |
19
+ | Contents | `exa_fetch` | Up to five pages; optional highlights, summary and subpages (API key required for those three), three-way concurrency | ✓ | ✓ | ✓ |
20
+ | Contents | `exa_find_similar` | Expands a known URL into similar pages | — | ✓ | — |
21
+ | Answer | `exa_answer` | `/answer`: a cited answer to a question | — | ✓ | — |
22
+ | Research | `exa_research` | Exa Agent: research, list building and structured output; resume, cancel, stop | — | ✓ | per account |
23
+ | Monitors | `exa_monitor` | Monitors: scheduled or manual searches; list, create, update, trigger, delete | — | ✓ | — |
24
+ | Websets | `exa_webset` | Websets: build and read verified datasets | — | ✓* | — |
25
+ | Batch | `exa_batch` | Batch: submit many `/search` or `/agent/runs` requests | — | ✓* | — |
26
+ | Status | `exa_status` | Reports the active mode, available tools and upstream tool list | ✓ | ✓ | ✓ |
27
+
28
+ Twelve tools in total: ten registered by the plugin and two native Harness tools redirected to Exa.
29
+
30
+ \* Websets requires a Pro plan; Batch must be enabled for the team. When an API key is active, the plugin performs one free probe for each. Features the team does not have are not registered as tools; the settings page marks them as unavailable instead of letting the model receive a 401 or 403.
31
+
32
+ ## Deprecated MCP tools
33
+
34
+ The Exa MCP repository keeps eight deprecated entries in `toolRegistry.ts` (disabled by default). Their capabilities are covered by the current tools, so the plugin does not register them again:
35
+
36
+ | Deprecated tool | Replacement |
37
+ | --- | --- |
38
+ | `get_code_context_exa` | `exa_search` with `category=github` |
39
+ | `company_research_exa` | `exa_search` with `category=company` |
40
+ | `people_search_exa` | `exa_search` with `category=people` |
41
+ | `linkedin_search_exa` | `exa_search` with `category=linkedin profile` |
42
+ | `deep_search_exa` | `exa_deep_search` |
43
+ | `crawling_exa` | `exa_fetch` |
44
+ | `deep_researcher_start` | `exa_research` |
45
+ | `deep_researcher_check` | `exa_research` with `runId` |
46
+
47
+ The same table appears at the bottom of the settings page.
48
+
49
+ ## Access modes
50
+
51
+ | Mode | Credential | Upstream | Notes |
52
+ | --- | --- | --- | --- |
53
+ | `keyless` | none | `https://mcp.exa.ai/mcp?tools=...` | Basic search, advanced search and fetching. Exa documents `web_search_advanced_exa` as usable without authentication, so it is enabled through the `tools` parameter. `agent_run` requires authentication and cannot be added to a keyless connection (the whole connection then returns 401). |
54
+ | `apiKey` | key saved in Settings, or `EXA_API_KEY` | `https://api.exa.ai` | The full REST surface: Search, Contents, Find Similar, Answer, Agent, Monitors and Websets. |
55
+ | `oauth` | browser sign-in | `https://mcp.exa.ai/mcp?tools=...` | MCP tools follow the account. `agent_run` is probed after sign-in; `exa_research` is registered only when it is present. |
56
+
57
+ “Per account” means the tool is registered according to the real `tools/list` response after sign-in, not a fixed setting.
58
+
59
+ ## Implementation notes
60
+
61
+ **MCP `tools` parameter.** The hosted Exa MCP endpoint exposes only `web_search_exa` and `web_fetch_exa` by default, and `agent_run` only on authenticated connections. The plugin builds the URL per mode:
62
+
63
+ - Keyless: `?tools=web_search_exa,web_fetch_exa,web_search_advanced_exa`
64
+ - Exa account: `?tools=...,agent_run`; if the probe fails, the request is retried without `agent_run`.
65
+
66
+ **Rate limits.** When the keyless channel is rate limited, Exa returns `_meta.ai.exa/rateLimited` or a plain “free MCP rate limit” message rather than a JSON-RPC error. The plugin converts either into an explicit error that asks for an API key or sign-in; the text is never returned as a search result.
67
+
68
+ **Agent continuation.** `exa_research` waits at most `timeoutSeconds` per call (default 120, maximum 600). On timeout it returns `status: "running"` and a `runId`; a later call with that `runId` continues waiting. API key mode uses `POST /agent/runs` with polling; account mode uses the MCP `agent_run` tool.
69
+
70
+ **Category mapping.** The REST API accepts category names such as `research paper` and `linkedin profile`, while advanced MCP search uses a strict enum. The plugin maps the former to `publication` and `people`; categories without a mapping are dropped rather than sent unchanged.
71
+
72
+ **Deep Search and `outputSchema`.** Exa synthesizes an answer only when the request contains `outputSchema`; without it, `type: deep` still returns only `results`. The plugin supplies `{ "type": "text" }` by default to obtain a prose answer and returns structured JSON when the caller passes an object schema.
73
+
74
+ **Websets mount point.** In the OpenAPI document, `/v0/websets` declares `servers: https://api.exa.ai/websets`; the effective path is `/websets/v0/websets…`. Without the prefix every request returns 404.
75
+
76
+ **Entitlement probes.** Batch returns 403 when disabled and Websets returns 401 without a Pro plan. Both probes are free GETs; the plugin runs them at startup and on mode changes and does not register tools that the team cannot use.
77
+
78
+ **Fetch formats.** `exa_fetch` supports `format: text`, `highlights` and `summary`. API key mode requests the corresponding Contents fields; keyless and account modes return body text only and report a clear error for other formats. Multiple URLs are fetched three at a time; a failed URL carries an `error` field and does not affect the remaining pages.
79
+
80
+ **Monitor updates.** `exa_monitor` with `action=update` can rename a monitor, pause or resume it (`status: active | paused`), and change its query or interval. `manual: true` or `period: "none"` creates a monitor without a schedule, which runs only through `trigger`.
81
+
82
+ **Credential handling.** The credential service is resolved through `ctx.inject`, so a service that activates after the plugin does not cause a fallback to plaintext storage. Every write validates that the record key follows `<scope>/<id>`, which prevents records that the service cannot load at the next start. When a refresh token is revoked (`invalid_grant`), the plugin drops the grant and falls back to an API key or keyless mode instead of returning 401 indefinitely.
83
+
84
+ **Request policy.** Every outbound request has a hard timeout (search 30s, contents 30s, MCP tools 60s, Agent 240s). 429/5xx responses and network failures are retried twice with exponential backoff, honouring `Retry-After`; a cancellation signal interrupts the wait immediately.
85
+
86
+ **OAuth token expiry.** On a 401 from MCP, the plugin force-refreshes the token and replays the request once; only a second failure is reported as a sign-in requirement.
87
+
88
+ **Cancelling Agent runs.** `exa_research` supports `action: cancel | stop` with a `runId` (`cancel` stops immediately; `stop` keeps the results collected so far). This is available only in API key mode; OAuth tokens can call MCP only, which exposes no cancellation endpoint.
89
+
90
+ **Spend estimate.** Exa reports `costDollars.total` on billed REST responses. The plugin accumulates it per process and shows it in `exa_status` and on the settings page. Agent runs are recorded once, when they reach a terminal status, so polling does not double-count. OAuth MCP search responses carry no cost field, so that mode estimates Agent runs only; the counter restarts at zero with the process.
91
+
92
+ ## Installation
93
+
94
+ Install this directory through the Harness plugin manager, or install `dsh-exa-search` from npm.
95
+
96
+ A local directory install stores a link rather than a copy in the profile; moving or deleting the source directory disables the plugin.
97
+
98
+ Installation pins the `web` row to the `exa` provider (see `cordis.patch.yml`). The pin is required: with two usable search providers, `ctx.web` fails closed with `WEB_PROVIDER_AMBIGUOUS` rather than choosing one.
99
+
100
+ ## Configuration
101
+
102
+ | Key | Default | Description |
103
+ | --- | --- | --- |
104
+ | `mode` | `keyless` | Initial access mode; the settings page overrides it. |
105
+ | `apiKeyEnv` | `EXA_API_KEY` | Environment variable read when no key is saved. |
106
+ | `numResults` | `8` | Default number of sources. |
107
+ | `searchType` | `auto` | REST search effort: `auto`, `lite` or `fast`. |
108
+ | `maxCharacters` | `12000` | Per-page fetch limit. |
109
+ | `locale` | `auto` | Message language: `auto` follows the system, or `en` / `zh`. |
110
+ | `mcpTools` | `null` | Overrides the MCP `tools` list (array or comma-separated string). |
111
+ | `toolGroups` | `null` | Initial per-group switches, e.g. `{ "answer": false, "webset": false }`. Changes made in Settings are stored in the state file and override this value. |
112
+
113
+ Each group on the settings page has a switch. Disabling a group removes its `exa_*` tools from the model's tool list immediately; the `web_search` / `web_fetch` redirection is unaffected. The **Test connection** button at the top performs one minimal search with the active mode and reports latency and a sample result (one search is consumed).
114
+
115
+ ## Data and credentials
116
+
117
+ - Access mode and OAuth client registration: `<DSH_HOME>/exa-search/state.json`. The Host process does not guarantee `DSH_PROFILE_DIR`, so this state is stored per user rather than per profile.
118
+ - API key and OAuth tokens: the Harness credential service (`ctx.credentials`), under the record keys `exa-search/api-key` and `exa-search/oauth`. They fall back to `state.json` (file mode 0600) only when the credential service is unavailable.
119
+ - No credential is written into the installation directory, and no state file is shipped with the package.
120
+
121
+ ## Routes
122
+
123
+ The settings page uses same-origin routes instead of generated remotes:
124
+
125
+ - `GET /exa-plugin/state` — active mode, credential status, upstream tool list, tool matrix and per-mode counts;
126
+ - `GET /exa-plugin/diagnostics` — a redacted snapshot for issue reports;
127
+ - `POST /exa-plugin/mode` / `api-key` / `login` / `logout` / `tools` / `test` — mutations; cross-origin requests are rejected;
128
+ - `GET /exa-plugin/oauth/callback` — OAuth callback.
129
+
130
+ ## Scope
131
+
132
+ Team management, webhooks and events, imports and Websets enrichments are account or event-delivery plumbing and are not exposed as model tools. The search, contents, answer, research, monitor, dataset and batch workflows are complete.
133
+
134
+ ## Uninstall
135
+
136
+ `dsh plugin remove dsh-exa-search`. After removal, the `web` row is decided by the underlying bundles again.
package/README.md CHANGED
@@ -1,3 +1,139 @@
1
- # Temporary Holding Version
1
+ # Exa Search for DeepSeek Harness
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > English documentation: [README.en.md](./README.en.md)
4
+
5
+ 本插件为 DeepSeek Harness 提供基于 [Exa](https://exa.ai) 的联网搜索与网页抓取能力,支持免登录、API Key 与 Exa 账号三种接入方式,并按接入方式注册工具。
6
+
7
+ 安装后:
8
+
9
+ - `web_search`、`web_fetch` 的调用方式与参数保持不变,实际请求由 Exa 提供;
10
+ - 插件额外注册 10 个 `exa_*` 工具;仅当当前接入方式可以调用某个工具时,该工具才会暴露给模型;
11
+ - 在「设置 → Exa 搜索」中可切换接入方式,并查看全部工具在三种方式下的可用性。
12
+
13
+ ## 工具清单(按功能分组)
14
+
15
+ | 分组 | 工具 | 作用 | 免登录 | API Key | Exa 账号 |
16
+ | --- | --- | --- | --- | --- | --- |
17
+ | 检索 | `web_search` | Harness 原生搜索,由 Exa 提供 | ✓ | ✓ | ✓ |
18
+ | 检索 | `exa_search` | 域名 / 分类 / 日期 / 文本 / 子页过滤 | ✓ | ✓ | ✓ |
19
+ | 检索 | `exa_deep_search` | Deep Search:多步检索与带引用的综合答案 | — | ✓ | — |
20
+ | 内容 | `web_fetch` | Harness 原生抓取,由 Exa 提供 | ✓ | ✓ | ✓ |
21
+ | 内容 | `exa_fetch` | 最多 5 个网页;可选取 highlights / summary / 子页(后三者需 API Key),3 路并发 | ✓ | ✓ | ✓ |
22
+ | 内容 | `exa_find_similar` | 由一篇已知网址扩展相似页面 | — | ✓ | — |
23
+ | 问答 | `exa_answer` | `/answer`:带引用的直接问答 | — | ✓ | — |
24
+ | 研究 | `exa_research` | Exa Agent:研究、名单构建、结构化输出(续等 / 取消 / 停止) | — | ✓ | 按账号 |
25
+ | 监控 | `exa_monitor` | Monitors:定时或手动搜索,列表 / 新建 / 修改 / 触发 / 删除 | — | ✓ | — |
26
+ | 数据集 | `exa_webset` | Websets:构建与读取经过验证的数据集 | — | ✓* | — |
27
+ | 批量 | `exa_batch` | Batch:批量提交 `/search` 或 `/agent/runs` | — | ✓* | — |
28
+ | 状态 | `exa_status` | 报告当前方式、可用工具与上游工具表 | ✓ | ✓ | ✓ |
29
+
30
+ 合计 12 个工具,其中 10 个由插件注册,2 个为 Harness 原生工具的 Exa 改道。
31
+
32
+ \* Websets 需要 Pro 计划,Batch 需要团队单独开通。启用 API Key 时,插件会分别执行一次免费探测;未开通的功能不会注册为工具,设置页会标注「当前团队未开通」,以避免模型调用后收到 401/403。
33
+
34
+ ## 旧版 MCP 工具映射
35
+
36
+ Exa MCP 仓库的 `toolRegistry.ts` 中保留了 8 个已弃用条目(默认关闭)。这些能力已由现有工具覆盖,因此插件不再重复注册:
37
+
38
+ | 旧工具 | 对应工具 |
39
+ | --- | --- |
40
+ | `get_code_context_exa` | `exa_search`,`category=github` |
41
+ | `company_research_exa` | `exa_search`,`category=company` |
42
+ | `people_search_exa` | `exa_search`,`category=people` |
43
+ | `linkedin_search_exa` | `exa_search`,`category=linkedin profile` |
44
+ | `deep_search_exa` | `exa_deep_search` |
45
+ | `crawling_exa` | `exa_fetch` |
46
+ | `deep_researcher_start` | `exa_research` |
47
+ | `deep_researcher_check` | `exa_research`,传入 `runId` |
48
+
49
+ 设置页底部同样列出该对照表,以便核对工具是否缺失。
50
+
51
+ ## 接入方式
52
+
53
+ | 方式 | 凭据 | 上游 | 说明 |
54
+ | --- | --- | --- | --- |
55
+ | 免登录 `keyless` | 不需要 | `https://mcp.exa.ai/mcp?tools=...` | 基础搜索、高级搜索与抓取。Exa 说明 `web_search_advanced_exa` 无需认证,因此通过 `tools` 参数启用;`agent_run` 需要认证,不能加入免登录连接(否则整个连接返回 401)。 |
56
+ | API Key `apiKey` | 设置页填入,或环境变量 `EXA_API_KEY` | `https://api.exa.ai` | 全部接口:Search、Contents、Find Similar、Answer、Agent、Monitors、Websets。 |
57
+ | Exa 账号 `oauth` | 浏览器授权 | `https://mcp.exa.ai/mcp?tools=...` | MCP 工具随账号开放;`agent_run` 在登录后探测,探测到才会注册 `exa_research`。 |
58
+
59
+ 表格中的「按账号」表示该工具是否注册取决于登录后 `tools/list` 的实际返回,而非固定配置。
60
+
61
+ ## 实现说明
62
+
63
+ **MCP 的 `tools` 参数**。Exa 托管 MCP 默认仅提供 `web_search_exa` 与 `web_fetch_exa`,`agent_run` 仅对有凭据的连接开放。插件按接入方式拼接 URL:
64
+
65
+ - 免登录:`?tools=web_search_exa,web_fetch_exa,web_search_advanced_exa`
66
+ - Exa 账号:`?tools=...,agent_run`;若探测失败,则去掉 `agent_run` 重试。
67
+
68
+ **限流识别**。免登录通道触发限流时,Exa 返回 `_meta.ai.exa/rateLimited` 或一段 "free MCP rate limit" 文本,而非 JSON-RPC 错误。插件会将其转换为明确错误,并提示配置 API Key 或登录,不会将限流文本作为搜索结果返回。
69
+
70
+ **Agent 续等**。`exa_research` 单次最多等待 `timeoutSeconds`(默认 120,上限 600);超时返回 `status: "running"` 与 `runId`,再次调用并传入该 `runId` 可继续等待。API Key 模式使用 `POST /agent/runs` 与轮询,Exa 账号模式使用 MCP 的 `agent_run`。
71
+
72
+ **分类映射**。REST 接口接受 `research paper`、`linkedin profile` 等分类名,MCP 高级搜索使用严格枚举。插件将前者映射为 `publication`、`people`;无法映射的分类会被丢弃,不会原样发送。
73
+
74
+ **Deep Search 的 `outputSchema`**。Exa 仅在请求包含 `outputSchema` 时生成综合答案;缺失时 `type: deep` 也只返回 `results`。插件默认补充 `{ "type": "text" }` 以获得散文答案;调用方传入对象 schema 时返回结构化 JSON。
75
+
76
+ **Websets 的挂载点**。OpenAPI 文档中 `/v0/websets` 的 `servers` 为 `https://api.exa.ai/websets`,实际地址为 `/websets/v0/websets…`;缺少该前缀会持续返回 404。
77
+
78
+ **功能开通探测**。Batch 未开通返回 403,Websets 未开通返回 401(Pro 计划)。两者均为免费 GET;插件在启动或切换接入方式时探测,未开通则不注册对应工具。
79
+
80
+ **抓取格式**。`exa_fetch` 的 `format` 支持 `text`、`highlights`、`summary`。API Key 模式下分别请求 Contents API 的对应字段;免登录与账号模式的 MCP 抓取仅返回正文,请求其他格式会返回明确错误。多 URL 抓取采用 3 路并发,单个 URL 失败不影响其余页面,失败信息以 `error` 字段返回。
81
+
82
+ **监控更新**。`exa_monitor` 的 `update` 操作支持修改名称、暂停 / 恢复(`status: active | paused`)、修改查询与周期;`manual: true` 或 `period: "none"` 可创建不排期的监控,仅能通过 `trigger` 手动执行。
83
+
84
+ **凭据读写**。凭据服务通过 `ctx.inject` 解析,即使其激活晚于插件也不会退回明文存储;写入前会校验记录键必须符合 `<scope>/<id>`,避免产生无法在下次启动时加载的记录。刷新令牌被吊销(`invalid_grant`)时,插件会丢弃授权并回退到 API Key 或免登录,而非持续返回 401。
85
+
86
+ **请求策略**。所有出站请求均设置硬超时(搜索 30s、内容 30s、MCP 工具 60s、Agent 240s);429/5xx 与网络失败按指数退避重试 2 次,并遵循 `Retry-After`;取消信号会立即中断等待。
87
+
88
+ **OAuth 令牌失效**。MCP 返回 401 时,插件先强制刷新令牌并使用新令牌重放请求;仅当刷新后仍失败时才提示重新登录。
89
+
90
+ **取消 Agent**。`exa_research` 的 `action: cancel | stop` 配合 `runId` 可中止运行(`cancel` 立即取消,`stop` 保留已收集结果),仅在 API Key 模式下可用;OAuth 令牌仅能通过 MCP 调用,没有取消入口。
91
+
92
+ **费用估算**。Exa 在 REST 计费响应中返回 `costDollars.total`,插件按进程累计,并在 `exa_status` 与设置页中显示;Agent 运行仅在到达终态时记录一次,轮询不会重复计数。OAuth 模式下搜索响应不包含费用字段,因此该模式的估算仅覆盖 Agent 运行;进程重启后从 0 开始。
93
+
94
+ ## 安装
95
+
96
+ 可通过 Harness 插件管理安装本目录,或从 npm 安装 `dsh-exa-search`。
97
+ 使用本地目录安装时,profile 中保存的是链接而非副本;移动或删除源目录会导致插件失效。
98
+
99
+ 安装后,`web` 行会被固定到 `exa` provider(见 `cordis.patch.yml`)。这是必要配置:当存在两个可用搜索 provider 时,`ctx.web` 会以 `WEB_PROVIDER_AMBIGUOUS` 拒绝选择,而不会自动猜测。
100
+
101
+ ## 配置
102
+
103
+ `cordis.patch.yml` 中的行配置:
104
+
105
+ | 键 | 默认 | 说明 |
106
+ | --- | --- | --- |
107
+ | `mode` | `keyless` | 首次运行的默认接入方式;设置页会覆盖它。 |
108
+ | `apiKeyEnv` | `EXA_API_KEY` | 未保存 Key 时读取的环境变量。 |
109
+ | `numResults` | `8` | 默认返回来源数。 |
110
+ | `searchType` | `auto` | 检索强度:`auto` / `lite` / `fast`。 |
111
+ | `maxCharacters` | `12000` | 单页抓取字符上限。 |
112
+ | `locale` | `auto` | 消息语言:`auto` 跟随系统,或强制 `en` / `zh`。 |
113
+ | `mcpTools` | `null` | 覆盖 MCP 的 `tools` 列表(数组或逗号分隔字符串)。 |
114
+ | `toolGroups` | `null` | 分组开关的初始值,例如 `{ "answer": false, "webset": false }`;设置页的改动存入 state 并覆盖它。 |
115
+
116
+ 设置页中每个分组右侧均有开关;关闭后,该分组下的 `exa_*` 工具会立即从模型工具表中移除,`web_search` / `web_fetch` 的 Exa 改道不受影响。设置页顶部的「测试连接」按钮会使用当前接入方式执行一次最小搜索,并返回耗时与示例结果(消耗一次搜索)。
117
+
118
+ ## 数据与凭据
119
+
120
+ - 接入方式与 OAuth 客户端注册信息:`<DSH_HOME>/exa-search/state.json`。Host 进程不保证存在 `DSH_PROFILE_DIR`,因此该状态按用户存储,而非按 profile。
121
+ - API Key 与 OAuth 令牌:Harness 凭据服务(`ctx.credentials`),记录键为 `exa-search/api-key`、`exa-search/oauth`;仅当凭据服务不可用时,才退回上述 `state.json`(文件权限 0600)。
122
+ - 插件不会将任何凭据写入安装目录,也不会随包分发状态文件。
123
+
124
+ ## 路由
125
+
126
+ 设置页通过同源路由读写 Host,避免为纯 JS 插件生成 Remote 命名空间:
127
+
128
+ - `GET /exa-plugin/state` — 当前方式、凭据状态、上游工具表、工具矩阵与各方式计数;
129
+ - `GET /exa-plugin/diagnostics` — 脱敏诊断快照,用于问题反馈;
130
+ - `POST /exa-plugin/mode` / `api-key` / `login` / `logout` / `tools` / `test` — 变更操作,拒绝跨源请求;
131
+ - `GET /exa-plugin/oauth/callback` — OAuth 回调。
132
+
133
+ ## 未覆盖范围
134
+
135
+ Team Management、Webhooks / Events、Imports 属于账号与事件投递的底层接口,不注册为模型工具;检索、内容、问答、研究、监控、数据集、批量这条链路已经完整。
136
+
137
+ ## 卸载
138
+
139
+ `dsh plugin remove dsh-exa-search`。卸载后 `web` 行恢复由下层 bundle 决定。