dsh-exa-search 0.0.0-stage → 1.0.0
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 +43 -0
- package/LICENSE +21 -0
- package/README.en.md +103 -0
- package/README.md +150 -2
- package/client.js +1010 -0
- package/cordis.patch.yml +32 -0
- package/icon.svg +15 -0
- package/index.js +2633 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +50 -4
- package/scripts/repair-credentials.mjs +101 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
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
|
+
- Early local builds stored credentials as `exa-api-key` / `exa-oauth`, which the credential service rejects at load time and can prevent Harness from starting. If you ran one, run `node scripts/repair-credentials.mjs --dry-run` and then `node scripts/repair-credentials.mjs` before starting Harness.
|
|
43
|
+
- Tests (`node --test`), the CI workflow and the English README are 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,103 @@
|
|
|
1
|
+
# Exa Search for DeepSeek Harness
|
|
2
|
+
|
|
3
|
+
Exa-backed `web_search` / `web_fetch` for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness), with three access modes and per-mode tool registration.
|
|
4
|
+
|
|
5
|
+
- The stock `web_search` / `web_fetch` tools keep their schemas but run on Exa.
|
|
6
|
+
- Ten additional `exa_*` tools are registered **only when the active mode can call them**.
|
|
7
|
+
- **Settings → Exa Search** switches modes, shows which tools each mode exposes, and can test the connection.
|
|
8
|
+
|
|
9
|
+
## Tool catalog
|
|
10
|
+
|
|
11
|
+
| Group | Tool | What it does | Keyless | API key | Exa account |
|
|
12
|
+
| --- | --- | --- | --- | --- | --- |
|
|
13
|
+
| Search | `web_search` | Harness web search, served by Exa | ✓ | ✓ | ✓ |
|
|
14
|
+
| Search | `exa_search` | Domain / category / date / text / subpage filters | ✓ | ✓ | ✓ |
|
|
15
|
+
| Search | `exa_deep_search` | Deep Search: multi-step retrieval with a cited answer | — | ✓ | — |
|
|
16
|
+
| Contents | `web_fetch` | Harness page fetch, served by Exa | ✓ | ✓ | ✓ |
|
|
17
|
+
| Contents | `exa_fetch` | Up to 5 pages, optional highlights / summary / subpages, 3-way concurrency | ✓* | ✓ | ✓* |
|
|
18
|
+
| Contents | `exa_find_similar` | Expand a known-good URL into similar pages | — | ✓ | — |
|
|
19
|
+
| Answer | `exa_answer` | `/answer`: cited answer to a question | — | ✓ | — |
|
|
20
|
+
| Research | `exa_research` | Exa Agent: research, list building, structured output; resume / cancel / stop | — | ✓ | per account |
|
|
21
|
+
| Monitors | `exa_monitor` | Recurring or manual searches; list / create / update / trigger / delete | — | ✓ | — |
|
|
22
|
+
| Websets | `exa_webset` | Verified datasets (Pro plan required) | — | ✓* | — |
|
|
23
|
+
| Batch | `exa_batch` | Submit many search / agent requests (team feature) | — | ✓* | — |
|
|
24
|
+
| Status | `exa_status` | Active mode, tool counts, spend estimate | ✓ | ✓ | ✓ |
|
|
25
|
+
|
|
26
|
+
\* `exa_fetch` highlights/summary/subpages need an API key. Websets needs a Pro plan; Batch must be enabled for the team. The plugin probes both with free GETs and simply does not register tools the team cannot use.
|
|
27
|
+
|
|
28
|
+
## Access modes
|
|
29
|
+
|
|
30
|
+
| Mode | Credential | Upstream | Notes |
|
|
31
|
+
| --- | --- | --- | --- |
|
|
32
|
+
| `keyless` | none | `mcp.exa.ai/mcp` | Free, rate-limited per IP. Advanced search is opted in through `?tools=` (Exa allows it unauthenticated); `agent_run` is not available. |
|
|
33
|
+
| `apiKey` | saved key or `EXA_API_KEY` | `api.exa.ai` | Full REST surface: search, contents, find similar, answer, agent, monitors, websets, batch. |
|
|
34
|
+
| `oauth` | browser sign-in | `mcp.exa.ai/mcp` with a bearer token | MCP tools follow the account; `agent_run` appears when the account exposes it. |
|
|
35
|
+
|
|
36
|
+
The matrix is live, not marketing: the OAuth research row is resolved from the server's real `tools/list` after sign-in.
|
|
37
|
+
|
|
38
|
+
## Implementation notes
|
|
39
|
+
|
|
40
|
+
- **MCP tool list**: keyless sends `?tools=web_search_exa,web_fetch_exa,web_search_advanced_exa`; OAuth adds `agent_run` and retries without it if the connection is rejected.
|
|
41
|
+
- **Rate limits**: keyless answers with `_meta.ai.exa/rateLimited` or a plain text notice; both are translated into a clear error instead of being returned as results.
|
|
42
|
+
- **Deep Search** only synthesizes when `outputSchema` is present. The tool sends a `{ "type": "text" }` schema by default and returns structured JSON if you pass your own.
|
|
43
|
+
- **Websets** lives on its own mount point: `api.exa.ai/websets/v0/websets…`.
|
|
44
|
+
- **Credentials** are stored under `exa-search/api-key` / `exa-search/oauth` (the service requires `<scope>/<id>`), resolved through an injection, and writes are validated before touching the vault.
|
|
45
|
+
- **Request policy**: hard timeouts (search 30s, MCP 60s, Agent 240s), retries with backoff for 429/5xx and `Retry-After`, and one forced token refresh on a 401.
|
|
46
|
+
- **Spend estimate**: accumulated from Exa's `costDollars`; Agent runs are counted once at a terminal status. OAuth MCP search responses carry no cost field, so the estimate covers REST calls and Agent runs.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
dsh plugin add dsh-exa-search
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or install this directory from the plugin manager. A local directory install links the folder; moving or deleting it breaks the plugin.
|
|
55
|
+
|
|
56
|
+
Installing pins the `web` row to the `exa` providers (`cordis.patch.yml`). That pin is required: with two usable search providers, `ctx.web` fails closed with `WEB_PROVIDER_AMBIGUOUS` instead of guessing.
|
|
57
|
+
|
|
58
|
+
## Configuration
|
|
59
|
+
|
|
60
|
+
| Key | Default | Meaning |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `mode` | `keyless` | First-run mode; the settings page overrides it. |
|
|
63
|
+
| `apiKeyEnv` | `EXA_API_KEY` | Environment fallback when no key is saved. |
|
|
64
|
+
| `numResults` | `8` | Default number of sources. |
|
|
65
|
+
| `searchType` | `auto` | REST search effort: `auto` / `lite` / `fast`. |
|
|
66
|
+
| `maxCharacters` | `12000` | Per-page fetch cap. |
|
|
67
|
+
| `mcpTools` | `null` | Overrides the MCP `tools` list (array or comma string). |
|
|
68
|
+
| `toolGroups` | `null` | Initial per-group tool switches, e.g. `{ "answer": false }`. |
|
|
69
|
+
| `locale` | `auto` | Message language: `auto` / `en` / `zh`. |
|
|
70
|
+
|
|
71
|
+
## Data
|
|
72
|
+
|
|
73
|
+
- Mode and OAuth client registration: `<DSH_HOME>/exa-search/state.json`.
|
|
74
|
+
- API key and OAuth token: the Harness credential service, under `exa-search/api-key` / `exa-search/oauth`. Only when the vault is unavailable do they fall back to `state.json` (mode 0600).
|
|
75
|
+
- Nothing is written into the install directory, and no state ships with the package.
|
|
76
|
+
|
|
77
|
+
### Repair after a failed start
|
|
78
|
+
|
|
79
|
+
If Harness refuses to start with `credential key "..." must be "<scope>/<id>"` (written by a pre-release local build), quit the app and run:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
node scripts/repair-credentials.mjs --dry-run
|
|
83
|
+
node scripts/repair-credentials.mjs
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The script only renames those two record keys, backs the file up first, and refuses to guess if both forms exist.
|
|
87
|
+
|
|
88
|
+
## Routes
|
|
89
|
+
|
|
90
|
+
The settings page uses same-origin routes instead of generated remotes:
|
|
91
|
+
|
|
92
|
+
- `GET /exa-plugin/state` — mode, credentials (masked), upstream tools, matrix, counts;
|
|
93
|
+
- `GET /exa-plugin/diagnostics` — redacted snapshot for bug reports;
|
|
94
|
+
- `POST /exa-plugin/mode` / `api-key` / `login` / `logout` / `tools` / `test` — mutations, cross-origin requests rejected;
|
|
95
|
+
- `GET /exa-plugin/oauth/callback` — OAuth callback.
|
|
96
|
+
|
|
97
|
+
## Not covered
|
|
98
|
+
|
|
99
|
+
Team management, webhooks/events, imports and Websets enrichments are account plumbing or plan-gated; they are not exposed as model tools.
|
|
100
|
+
|
|
101
|
+
## Uninstall
|
|
102
|
+
|
|
103
|
+
`dsh plugin remove dsh-exa-search`. The `web` row falls back to the shipped default.
|
package/README.md
CHANGED
|
@@ -1,3 +1,151 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Exa Search for DeepSeek Harness
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **English** — full README in [README.en.md](./README.en.md). Short version: Exa-backed `web_search` / `web_fetch` with three access modes (keyless, API key, OAuth) and per-mode tool registration; install `dsh-exa-search`, then open **Settings → Exa Search**.
|
|
4
|
+
|
|
5
|
+
把 Harness 的联网搜索交给 [Exa](https://exa.ai)。安装后:
|
|
6
|
+
|
|
7
|
+
- `web_search`、`web_fetch` 的 schema 不变,实际请求全部走 Exa;
|
|
8
|
+
- 另外注册 10 个 Exa 工具,**按接入方式注册**:只有当前方式真能调用的工具才会出现在模型面前;
|
|
9
|
+
- 「设置 → Exa 搜索」里可以切换接入方式,并按功能分组看到全部工具在三种方式下的可用性。
|
|
10
|
+
|
|
11
|
+
## 工具清单(按功能分组)
|
|
12
|
+
|
|
13
|
+
| 分组 | 工具 | 作用 | 免登录 | API Key | Exa 账号 |
|
|
14
|
+
| --- | --- | --- | --- | --- | --- |
|
|
15
|
+
| 检索 | `web_search` | Harness 原生搜索,已改走 Exa | ✓ | ✓ | ✓ |
|
|
16
|
+
| 检索 | `exa_search` | 域名 / 分类 / 日期 / 文本 / 子页过滤 | ✓ | ✓ | ✓ |
|
|
17
|
+
| 检索 | `exa_deep_search` | Deep Search:多步检索 + 带引用的综合答案 | — | ✓ | — |
|
|
18
|
+
| 内容 | `web_fetch` | Harness 原生抓取,已改走 Exa | ✓ | ✓ | ✓ |
|
|
19
|
+
| 内容 | `exa_fetch` | 抓取最多 5 个网页;可选取 highlights / summary / 子页(后三者需 API Key),并发 3 | ✓ | ✓ | ✓ |
|
|
20
|
+
| 内容 | `exa_find_similar` | 由一篇已知好文扩展相似页面 | — | ✓ | — |
|
|
21
|
+
| 问答 | `exa_answer` | `/answer`:带引用的直接问答 | — | ✓ | — |
|
|
22
|
+
| 研究 | `exa_research` | Exa Agent:研究、建名单、结构化输出(续等 / 取消 / 停止) | — | ✓ | 登录后按账号 |
|
|
23
|
+
| 监控 | `exa_monitor` | Monitors:定时或手动搜索,列表 / 新建 / 修改 / 触发 / 删除 | — | ✓ | — |
|
|
24
|
+
| 数据集 | `exa_webset` | Websets:构建、读取经过验证的数据集 | — | ✓* | — |
|
|
25
|
+
| 批量 | `exa_batch` | Batch:批量提交 `/search` 或 `/agent/runs` | — | ✓* | — |
|
|
26
|
+
| 状态 | `exa_status` | 报告当前方式、可用工具与上游工具表 | ✓ | ✓ | ✓ |
|
|
27
|
+
|
|
28
|
+
合计 12 个,其中 10 个由插件注册、2 个是 Harness 原生的改道。
|
|
29
|
+
|
|
30
|
+
\* Websets 需要 Pro 计划,Batch 需要团队单独开通。启用 API Key 时插件会各发一次免费探测;团队没有的功能不会注册成工具,设置页注明「当前团队未开通」,而不是让模型调用后拿到 401/403。
|
|
31
|
+
|
|
32
|
+
## 旧版 MCP 工具的去向
|
|
33
|
+
|
|
34
|
+
Exa MCP 仓库的 `toolRegistry.ts` 里还有 8 个已弃用条目(默认关闭)。它们的能力已被现代工具覆盖,所以不重复注册:
|
|
35
|
+
|
|
36
|
+
| 旧工具 | 现在用 |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `get_code_context_exa` | `exa_search`,`category=github` |
|
|
39
|
+
| `company_research_exa` | `exa_search`,`category=company` |
|
|
40
|
+
| `people_search_exa` | `exa_search`,`category=people` |
|
|
41
|
+
| `linkedin_search_exa` | `exa_search`,`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`,传 `runId` |
|
|
46
|
+
|
|
47
|
+
设置页底部也列出了这张对照表,避免“看起来少了一个工具”。
|
|
48
|
+
|
|
49
|
+
## 三种接入方式
|
|
50
|
+
|
|
51
|
+
| 方式 | 凭据 | 上游 | 能覆盖到哪 |
|
|
52
|
+
| --- | --- | --- | --- |
|
|
53
|
+
| 免登录 `keyless` | 不需要 | `https://mcp.exa.ai/mcp?tools=...` | 基础搜索、高级搜索、抓取。Exa 明确说明 `web_search_advanced_exa` 不需要认证,所以用 `tools` 参数把它打开;`agent_run` 需要认证,不能放进免登录连接(否则整个连接 401)。 |
|
|
54
|
+
| API Key `apiKey` | 设置页填入,或 `EXA_API_KEY` | `https://api.exa.ai` | 全部:Search、Contents、Find Similar、Answer、Agent、Monitors、Websets。 |
|
|
55
|
+
| Exa 账号 `oauth` | 浏览器授权 | `https://mcp.exa.ai/mcp?tools=...` | MCP 工具随账号开放;`agent_run` 在登录后探测,能探测到才注册 `exa_research`。 |
|
|
56
|
+
|
|
57
|
+
工具矩阵里 `·` 表示“登录后才确定”,不是写死的宣传语:`exa_research` 在 OAuth 下是否出现,取决于登录后 `tools/list` 的真实返回。
|
|
58
|
+
|
|
59
|
+
## 实现要点
|
|
60
|
+
|
|
61
|
+
**MCP 的 tools 参数**。Exa 的托管 MCP 默认只给 `web_search_exa`、`web_fetch_exa`;`agent_run` 默认只对有凭据的连接开放。插件按模式拼 URL:
|
|
62
|
+
|
|
63
|
+
- 免登录:`?tools=web_search_exa,web_fetch_exa,web_search_advanced_exa`
|
|
64
|
+
- 账号:`?tools=...,agent_run`(探测失败自动去掉 `agent_run` 重试)
|
|
65
|
+
|
|
66
|
+
**限流识别**。免登录命中限额时 Exa 返回的是 `_meta.ai.exa/rateLimited` 或一段“free MCP rate limit”文本,而不是 JSON-RPC error。插件会把它翻译成明确错误,提示去配置 Key 或登录,而不是把限流文案当成搜索结果。
|
|
67
|
+
|
|
68
|
+
**Agent 续等**。`exa_research` 最多等 `timeoutSeconds`(默认 120,上限 600),超时返回 `status: "running"` 和 `runId`;再次调用并传入 `runId` 继续等。REST 走 `POST /agent/runs` + 轮询,OAuth 走 MCP 的 `agent_run`。
|
|
69
|
+
|
|
70
|
+
**分类差异**。REST 接受 `research paper` / `linkedin profile` 这类分类名,MCP 高级搜索是严格枚举;插件把前者映射成 `publication` / `people`,映射不了的分类会被丢弃而不是原样发送。
|
|
71
|
+
|
|
72
|
+
**Deep Search 必须带 outputSchema**。Exa 只在请求里有 `outputSchema` 时才做综合——不带的话 `type: deep` 也只返回 `results`,没有 `output`。插件默认补一个 `{ type: "text" }` schema 拿散文答案,调用方传自己的对象 schema 就返回结构化 JSON。
|
|
73
|
+
|
|
74
|
+
**Websets 有自己的挂载点**。OpenAPI 里 `/v0/websets` 的 `servers` 是 `https://api.exa.ai/websets`,真实地址是 `/websets/v0/websets...`;少这个前缀会一直是 404。
|
|
75
|
+
|
|
76
|
+
**功能开通探测**。Batch 未开通返回 403、Websets 未开通返回 401(Pro 计划);两者都是免费 GET,插件在切换/启动时探测,没有开通就不注册对应工具。
|
|
77
|
+
|
|
78
|
+
**抓取格式**。`exa_fetch` 的 `format` 支持 `text` / `highlights` / `summary`,API Key 模式下分别请求 Contents API 的 `text`、`highlights`、`summary` 对象;免登录/账号的 MCP 抓取只返回正文,请求其它格式会得到明确错误。多 URL 用 3 路并发,单个失败不影响其余页面(结果里带 `error`)。
|
|
79
|
+
|
|
80
|
+
**监控更新**。`exa_monitor action=update` 支持改名、暂停/恢复(`status: active | paused`)、改查询和改周期;`manual: true` 或 `period: "none"` 创建不排期的监控,只能手动 `trigger`。
|
|
81
|
+
|
|
82
|
+
**凭据读写**。凭据服务通过 `ctx.inject` 解析,即使它比插件晚激活也不会退回明文;写入前会校验记录键必须是 `<scope>/<id>`,避免出现“能写入、下次启动才炸”的坏记录。刷新令牌被吊销时(`invalid_grant`)会丢弃授权并回退到 API Key 或免登录,而不是让联网一直 401。
|
|
83
|
+
|
|
84
|
+
**请求策略**。所有出站请求都带硬超时(搜索 30s、内容 30s、MCP 工具 60s、Agent 240s),429/5xx/网络失败按指数退避重试 2 次并尊重 `Retry-After`;取消信号会立刻中断等待。
|
|
85
|
+
|
|
86
|
+
**OAuth 令牌失效**。MCP 返回 401 时不会直接把错误抛给用户:先强制刷新一次令牌并用新令牌重放,仍失败才报“需要重新登录”。
|
|
87
|
+
|
|
88
|
+
**取消 Agent**。`exa_research` 的 `action: cancel | stop` 配合 `runId` 可中止运行(`cancel` 立即取消,`stop` 保留已收集结果),仅 API Key 模式可用——OAuth 的令牌只能走 MCP,没有取消入口。
|
|
89
|
+
|
|
90
|
+
**费用估算**。Exa 在 REST 计费响应里返回 `costDollars.total`,插件按进程累计并在 `exa_status` 与设置页显示;Agent 运行只在到达终态时记一次,轮询不会重复计数。注意 OAuth 走 MCP 时搜索响应不带费用字段,所以账号模式的估算只覆盖 Agent 运行;进程重启后从 0 开始。
|
|
91
|
+
|
|
92
|
+
## 安装
|
|
93
|
+
|
|
94
|
+
在 Harness 的插件管理里安装本目录,或从 npm 安装发布后的包(`dsh-exa-search`)。
|
|
95
|
+
以本地目录安装时,profile 里保存的是链接而不是拷贝——移动或删除源目录会让插件失效。
|
|
96
|
+
|
|
97
|
+
安装后 `web` 行被 pin 到 `exa` provider(见 `cordis.patch.yml`)——这是必须的:同时存在两个可用搜索 provider 时,`ctx.web` 会以 `WEB_PROVIDER_AMBIGUOUS` 拒绝选择,而不是猜。
|
|
98
|
+
|
|
99
|
+
## 配置
|
|
100
|
+
|
|
101
|
+
`cordis.patch.yml` 里的行配置:
|
|
102
|
+
|
|
103
|
+
| 键 | 默认 | 说明 |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| `mode` | `keyless` | 首次运行的默认接入方式;设置页会覆盖它。 |
|
|
106
|
+
| `apiKeyEnv` | `EXA_API_KEY` | 没有保存 Key 时读取的环境变量。 |
|
|
107
|
+
| `numResults` | `8` | 默认返回来源数。 |
|
|
108
|
+
| `searchType` | `auto` | 检索强度:`auto` / `lite` / `fast`。 |
|
|
109
|
+
| `maxCharacters` | `12000` | 单页抓取字符上限。 |
|
|
110
|
+
| `locale` | `auto` | 消息语言:`auto` 跟随系统,或强制 `en` / `zh`。 |
|
|
111
|
+
| `mcpTools` | `null` | 覆盖 MCP 的 `tools` 列表(数组或逗号分隔字符串)。 |
|
|
112
|
+
| `toolGroups` | `null` | 分组开关的初始值,例如 `{ "answer": false, "webset": false }`;设置页的改动存进 state 并覆盖它。 |
|
|
113
|
+
|
|
114
|
+
设置页每个分组右侧有一个开关,关掉后该组的 `exa_*` 工具立刻从模型工具表里移除;`web_search` / `web_fetch` 的改道不受影响。设置页顶部还有「测试连接」按钮,会用当前方式跑一次最小搜索并返回耗时与示例结果(消耗一次搜索)。
|
|
115
|
+
|
|
116
|
+
## 数据落在哪
|
|
117
|
+
|
|
118
|
+
- 接入方式、OAuth 客户端注册信息:`<DSH_HOME>/exa-search/state.json`。
|
|
119
|
+
Host 进程不保证有 `DSH_PROFILE_DIR`,所以这份状态按用户存放,而不是按 profile。
|
|
120
|
+
- API Key 与 OAuth 令牌:Harness 凭据服务(`ctx.credentials`),记录键为 `exa-search/api-key`、`exa-search/oauth`;
|
|
121
|
+
只有凭据服务不可用时才退回上面的 `state.json`(文件权限 0600)。
|
|
122
|
+
- 插件不会把任何凭据写进安装目录,也不会随包分发状态文件。
|
|
123
|
+
|
|
124
|
+
> **如果你装过 1.0.0 之前的本地开发版**:旧版把凭据写成 `exa-api-key` / `exa-oauth`,而 Harness 的凭据服务只接受 `<scope>/<id>`,会在下次启动时解析失败并让整个应用起不来。关闭应用后运行下面的修复脚本,或手动改名/删除这两条记录。
|
|
125
|
+
|
|
126
|
+
## 启动崩溃后的修复
|
|
127
|
+
|
|
128
|
+
如果 Harness 报 `credential key "..." must be "<scope>/<id>"` 而无法启动,先完全退出应用,再运行:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
node scripts/repair-credentials.mjs --dry-run # 预演,只打印会改什么
|
|
132
|
+
node scripts/repair-credentials.mjs # 备份并把两个旧键改成 exa-search/*
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
脚本只动这两条记录键,写入前在同目录留一份 `.bak-<时间戳>`;如果新旧键同时存在会拒绝猜测并退出。默认路径是 `<DSH_HOME>/.credentials.yaml`,可用 `DSH_HOME` 或 `DSH_CREDENTIALS` 覆盖,也可以 `npm run repair-credentials`。
|
|
136
|
+
|
|
137
|
+
## 路由
|
|
138
|
+
|
|
139
|
+
设置页通过同源路由读写 Host,避免为纯 JS 插件生成 Remote 命名空间:
|
|
140
|
+
|
|
141
|
+
- `GET /exa-plugin/state` — 当前方式、凭据状态、上游工具表、工具矩阵、各方式计数;
|
|
142
|
+
- `POST /exa-plugin/mode` / `api-key` / `login` / `logout` — 变更操作,拒绝跨源请求;
|
|
143
|
+
- `GET /exa-plugin/oauth/callback` — OAuth 回调。
|
|
144
|
+
|
|
145
|
+
## 没覆盖的部分
|
|
146
|
+
|
|
147
|
+
Team Management、Webhooks/Events、Imports 属于账号与事件投递的底层接口,不注册为模型工具;检索、内容、问答、研究、监控、数据集、批量这条链路已经完整。
|
|
148
|
+
|
|
149
|
+
## 卸载
|
|
150
|
+
|
|
151
|
+
`dsh plugin remove dsh-exa-search`。卸载后 `web` 行恢复由下层 bundle 决定。
|