qp-exa-dynamic
基于 Exa 的网页搜索提供方:默认开启 Exa Dynamic Highlights(并带上该设置必需的 Exa-Beta 请求头),开放全部 8 种 Exa 检索类型,提供 /exa 命令在运行时改高亮、检索类型与返回条数,并注册一个可自行指定条数的 exa_search 工具。
geighlord007
@geighlord007
⬇ 2
★ 0
main
安装
dsh plugin --profile web add github:geighlord007/qp-exa-dynamic
需要可复现安装时,可在仓库后追加 #commit 固定提交。
基于 Exa 的网页搜索提供方:默认开启 Exa Dynamic Highlights(并带上该设置必需的 Exa-Beta 请求头),开放全部 8 种 Exa 检索类型,提供 /exa 命令在运行时改高亮、检索类型与返回条数,并注册一个可自行指定条数的 exa_search 工具。
该插件未提供要点说明,请参考仓库 README。
deepseek-harnessdshdsh-pluginexaweb-searchdynamic-highlights
- 安装并启动 DeepSeek Harness:
npx @deepseek-ai/dsh web - 在终端执行上面的安装命令(CLI 会解析插件并核验来源)
- 用 dsh plugins list 确认已安装,必要时重启 Harness 生效
插件以当前 dsh 进程的权限运行,安装时可能执行代码。请先通读仓库源码与许可证,确认无破坏性命令与越权访问;本站只做索引,不对第三方插件安全性作担保。
| 代码仓库 | github.com/geighlord007/qp-exa-dynamic |
| 许可证 | MIT |
| 主要语言 | main |
| 下载量 | 2 |
| GitHub 星标 | 0 |
| 最近推送 | 2026-09-17 |
| 收录日期 | 2026-09-19 |
| 分类 | 工具与能力 |
事实信息来自公开插件目录快照(2026-10-01),介绍文案由本站再加工。
以下为插件仓库 README 全文(原始内容,由公开目录抓取整理)。
# qp-exa-dynamic
English | [简体中文](README.zh.md)
[](https://www.npmjs.com/package/qp-exa-dynamic)
An [Exa](https://exa.ai)-backed `WebSearchProvider` for the
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) `ctx.web` seam, with Exa
**Dynamic Highlights** on by default and an `/exa` command that changes highlights, search type and
result count at runtime.
```sh
dsh plugin --profile web add qp-exa-dynamic
```
## Why this exists
The first-party `@deepseek-ai/dsh-web-search-exa` cannot reach Exa Dynamic Highlights, for two
independent reasons:
1. **Its request body is hardcoded.** It sends `contents.highlights.highlightsPerUrl`, and no config
field it accepts reaches a `dynamic` field.
2. **Dynamic Highlights is a beta API and needs a header.** Every request that sets `dynamic: true`
must also send `Exa-Beta: dynamic-highlights-2026-08-28`. That provider's headers are
`authorization`, `content-type`, `accept` and `user-agent` — no `Exa-Beta`. Without it Exa answers
HTTP 400:
```json
{"error":"'highlights.dynamic' is in beta. Send the 'Exa-Beta: dynamic-highlights-2026-08-28' request header to use it.","tag":"INVALID_REQUEST"}
```
This provider sends both, and drops `highlightsPerUrl` entirely — measured against the live API, Exa
ignores that parameter, returning byte-identical payloads for values 1 and 5. It uses
`maxCharacters` instead, which is the knob that actually works when Dynamic Highlights are off.
## Measured
Real `/search` calls, one query, 8 results:
| Configuration | Highlight characters |
| --- | --- |
| First-party provider's default (no working knob) | 51,152 |
| This provider, `dynamicHighlights: false` + `highlightsMaxCharacters: 1500` | 10,973 |
| This provider, `dynamicHighlights: true` (default) | 12,716 |
Dynamic Highlights is not a uniform truncation: it concatenates the retrieved documents into one
input, runs a single forward pass, and allocates a shared budget across the result set — so useful
pages keep more context and redundant ones get less.
## Install
```sh
dsh plugin --profile web add qp-exa-dynamic
```
The package declares a `dsh.bundle` manifest, so its bundle patch inserts the provider row for you —
no hand-written patch entry is needed.
To select it, override the `web` row in `$DSH_HOME/profiles/web/cordis.patch.yml`:
```yaml
- id: web
name: '@deepseek-ai/dsh-web'
config:
searchProvider: exa
fetchProvider: http
```
> A patch **replaces** the targeted row's whole `config` rather than merging into it, so
> `fetchProvider: http` must be restated or the fetch provider is dropped.
Then give it a key, either as plugin config:
```yaml
- id: qp-exa-dynamic
name: qp-exa-dynamic
config:
apiKey: 'your-exa-api-key'
```
or through the environment. `apiKey` is declared `role('secret')`, so it never appears in a
`describe()` response — but a plain-text config file is still a plain-text config file; prefer the
environment when you can.
> **On `$DSH_HOME/.env`.** The plugin reads `apiKeyEnv` (default `EXA_API_KEY`) through the harness's
> launch-environment snapshot, which is documented to consult the inherited environment, the invoking
> directory's `.env` and the Harness home's `.env`. That worked in some deployments and not in
> others — on one Windows install the snapshot came back without the variable even though the file
> was correct, and the config `apiKey` above was the fix. If your provider reports
> `registered but unavailable`, the key is not reaching it; set `apiKey` directly.
Restart `dsh web` after changing the environment. `cordis.patch.yml` itself is hot-reloaded, so
config edits apply without a restart.
## Configuration
Every field has a safe default; you normally only supply a key.
| Field | Default | Meaning |
| --- | --- | --- |
| `providerId` | `exa` | Registry id. Change it only to coexist with another Exa provider. |
| `apiKey` | unset | Literal key; falls back to `apiKeyEnv`. |
| `apiKeyEnv` | `EXA_API_KEY` | Environment variable consulted when `apiKey` is unset. |
| `baseURL` | `https://api.exa.ai` | Exa endpoint; `/search` is appended. |
| `searchType` | `auto` | Retrieval type — see below. Runtime-settable with `/exa type`. |
| `numResults` | `8` | Source cap. Runtime-settable with `/exa results`. |
| `dynamicHighlights` | `true` | On by default; adds the required `Exa-Beta` header. Runtime-settable with `/exa`. |
| `highlightsMaxCharacters` | unset | Per-page highlight cap, used **only** when `dynamicHighlights` is false. |
`dynamicHighlights` is never combined with `highlightsMaxCharacters`: Exa sizes and distributes the
shared budget itself when dynamic is on, and its docs warn against combining the two.
## The `/exa` command
Typed in the composer. It runs directly against the interface and creates no model message.
| Command | Effect |
| --- | --- |
| `/exa` | Toggle Dynamic Highlights |
| `/exa on` / `/exa off` | Set them explicitly |
| `/exa type` | List the retrieval types |
| `/exa type deep` | Set the retrieval type |
| `/exa results` | Report the source cap |
| `/exa results 3` | Set the source cap |
| `/exa status` | Report every knob, the available types, and the real ceiling |
Plain words, no punctuation: the command declares **no argument hint**, so the composer inserts no
template to edit around. `/exa status` lists the retrieval types too, so "which types were there
again" never costs a second command, and a mistyped argument replies with copy-pasteable examples.
Writes land in the `qp-exa-dynamic` settings namespace's user layer, so they survive a
restart. Clearing that section returns the plugin to its configured defaults.
Measured on one provider instance, one query: switching Dynamic Highlights off took the same search
from 12,716 to 57,958 highlight characters — a 4.6x difference, applied on the next search.
## The `exa_search` tool
The plugin also registers a second, model-facing tool beside `web_search`:
```
exa_search(query: string, maxResults?: integer) // maxResults 1-50
```
It exists because of a hard structural fact: `ctx.web.search()` caps its result at the **caller's**
`request.maxResults`, and `dsh-tool-web` sends its own `searchMaxResults` on every call — so no
provider can ever exceed that cap, and raising it means forking an agent preset. This tool owns its
own `request.maxResults`, so the result count becomes a **per-call model argument** instead of a
deployment-level ceiling.
That makes the preset fork optional. Two ways past the default 8 sources:
| Want | Use | Needs the preset fork? |
| --- | --- | --- |
| 20 sources for one question | `exa_search(query, maxResults: 20)` — just ask in words | no |
| `/exa results 20` to work | the `/exa` command | yes |
`/exa results` is a fine setting to keep even without the fork, because it is what this tool falls
back to: with `/exa results 20` set, `exa_search(query)` with no `maxResults` returns 20. It is
`web_search` alone that stays clamped to the deployment's ceiling.
The tool goes through `ctx.web` like `web_search` does, so it uses the same selected provider, the
same search type and the same Dynamic Highlights setting; only the count differs. Its ceiling of 50
is its own — dynamic highlights measured ~1.6k characters per source, so 50 is already ~20k tokens
of context.
A prompt section next to `web_search`'s tells the model when to reach for it; ordinary lookups stay
on `web_search`. If the composition has no `tools` registry the provider still mounts and only the
tool is absent.
## Retrieval types
Exa's `type` is the latency/quality dial. All eight were verified against the live API; measured
latency for one query, 8 results:
| Type | Measured | Use |
| --- | --- | --- |
| `keyword` | 464 ms | Keyword only, fastest |
| `neural` | 737 ms | Semantic retrieval |
| `fast` | 798 ms | Speed with minimal quality loss |
| `instant` | 856 ms | Real-time (chat, voice) |
| `auto` | 1,914 ms | **Default** |
| `deep-lite` | 3,116 ms | Lightweight synthesized output |
| `deep` | 5,282 ms | Multi-step reasoning |
| `deep-reasoning` | 18,278 ms | Hardest research tasks |
The first-party provider's schema lists only `auto`, `keyword` and `neural` — that set is stale.
This provider exposes all eight.
### The `deep*` types are discounted by this seam
Measured through this provider's own class, same query, dynamic highlights on:
| Type | Time | Sources returned |
| --- | --- | --- |
| `fast` | 718 ms | 8 |
| `auto` | 215 ms | 8 |
| `deep` | 6,891 ms | 3 |
| `deep-reasoning` | 14,776 ms | 4 |
The raw API returns 8 results for `deep`; the rest carry no non-blank highlight and are dropped,
because the seam has no other field to derive a snippet from and inventing one would make the seam
lie. The `deep*` family's real product is the synthesized `output`, which `WebSearchSource` has no
field for. In practice the useful range here is `keyword`, `neural`, `fast`, `instant` and `auto`.
## Result count belongs to `dsh-tool-web`
Worth stating plainly, because it is easy to misread:
- The model-facing `web_search` tool takes only `queries` — the model cannot ask for a count.
- The ceiling belongs to `dsh-tool-web`: `searchMaxResults`, default 8. Its own comment:
*"The consumer owns the returned-context limit; providers and models do not."*
- The tool sends `maxResults` on **every** call, and the seam truncates the returned sources to it —
so no provider can exceed that ceiling.
That makes this plugin's `numResults` one-directional: it can pull the count down, never up.
| Configured | Tool ceiling | Sent to Exa |
| --- | --- | --- |
| 3 | 8 | 3 |
| 12 | 8 | 8 (clamped) |
| 20 | 8 | 8 (clamped) |
`/exa status` and `/exa results` report the ceiling the provider actually observed, so a clamped
value is explained rather than silently applied.
**Raising the ceiling is not a profile-patch edit in the Web app.** `dsh-web-app` disables the host
`tool-web` row — `- id: tool-web` / `disabled: true` — because only the `web` service and its search
provider are host-side; the model-facing tool is per session, and the one that actually runs comes
from the **agent preset**. The shipped `standard` preset's row carries `fetch` and `searchTimeoutMs`
and no `searchMaxResults`, so it takes the schema default of 8. A profile patch targeting `tool-web`
lands on the disabled host row and does nothing at all.
Changing it therefore means forking the preset: copy `standard` into `$DSH_HOME/.agent-presets/`, add
`searchMaxResults` to its `tool-web` row, and select it as the default. That is a real trade — a copy
does not track upstream updates to the shipped preset, and the default is read when a session is
created, so running sessions keep the preset they assembled with. For most uses leaving it at 8 is
the better deal: `/exa results` still pulls the count *down*, which is the direction that saves
tokens.
## Known limitations
- **Exa's beta surface can move.** `dynamic-highlights-2026-08-28` is a research preview; a change on
Exa's side means updating `DYNAMIC_BETA_VALUE`.
- **Results without highlights are dropped**, matching the seam's rule. With dynamic highlights on,
8 of 8 results carried a highlight in testing, so it rarely fires.
- **No `category`, domain or date filters, and no full text.** Those are Exa features this provider
does not expose yet.
- **One of this and the first-party Exa provider per profile.** Both register the provider id `exa`
by default; running both needs a distinct `providerId` on one of them.
- **Tested against dsh `0.1.5-rc.1` only**, which is what the peer ranges pin.
- **Without a settings service the `/exa` writes are in-memory only.** The provider itself works
either way; without a command registry there is simply no `/exa`.
## Development
```sh
node test/index.test.js # 40 unit tests, no API key needed
EXA_API_KEY=... node test/live.mjs # hits the real API, spends credit
```
Run the test file directly rather than through `node --test`: the test runner spawns a child process
per file with piped stdio, which fails with `spawn EPERM` in a restricted sandbox.
### Installing from a checkout
`dsh plugin --profile web add ` records a `link:` dependency, and the profile links the package
by name. Node resolves a linked module's own imports from its **real** path, not from the link inside
the profile — so a checkout that lives outside `$DSH_HOME/profiles/` has no `node_modules` ancestor
able to reach the harness's packages, and every `@deepseek-ai/dsh-*` import fails with
`ERR_MODULE_NOT_FOUND`. The plugin mounts, then throws on its first import.
Give the checkout a `node_modules` that points at the **same** instances the runtime uses:
```powershell
$plugin = ""
$hoist = "$env:USERPROFILE\.dsh\profiles\node_modules\@deepseek-ai"
New-Item -ItemType Directory -Force "$plugin\node_modules\@deepseek-ai" | Out-Null
foreach ($pkg in @("dsh-web", "dsh-tools", "dsh-launch-environment", "schemastery")) {
cmd /c mklink /J "$plugin\node_modules\@deepseek-ai\$pkg" "$hoist\$pkg"
}
```
Junctions rather than a second `pnpm install`, deliberately: `@deepseek-ai/dsh-tools` is a runtime
singleton, and a nested copy of it breaks the agent loop before the provider is ever called. Pointing
every link at the hoist directory guarantees one physical instance for each package.
## Uninstall
```sh
dsh plugin --profile web remove qp-exa-dynamic
```
Remove the `web` override from `cordis.patch.yml` to return to the built-in DeepSeek search. That
edit is hot-reloaded, so it takes effect immediately.
## License
MIT
数据来源:公开的 DeepSeek Harness 插件目录与各插件 GitHub 仓库。本站为独立第三方目录,与 DeepSeek、幻方(High-Flyer)及插件作者均无隶属或背书关系。