npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

qp-exa-dynamic

v0.1.0

Published

Exa web search provider for the DeepSeek Harness ctx.web seam, with Dynamic Highlights on by default, an /exa command to change highlights, search type and result count at runtime, and an exa_search tool that owns its own result cap.

Downloads

174

Readme

qp-exa-dynamic

English | 简体中文

Exa 支撑的 WebSearchProvider,接入 DeepSeek Harness 的 ctx.web seam。 默认开启 Exa Dynamic Highlights,并提供 /exa 命令,在运行时改高亮、检索类型和返回条数。

dsh plugin --profile web add qp-exa-dynamic

为什么需要它

官方的 @deepseek-ai/dsh-web-search-exa 够不到 Dynamic Highlights,是两处互相独立的硬伤:

  1. 请求体写死。 它发的是 contents.highlights.highlightsPerUrl,配置里没有任何字段能影响到 dynamic。

  2. Dynamic Highlights 是 beta 能力,需要请求头。 每个设了 dynamic: true 的请求都必须同时带上 Exa-Beta: dynamic-highlights-2026-08-28。那个提供方只发 authorization、content-type、 accept、user-agent——没有 Exa-Beta。缺了它 Exa 直接回 HTTP 400(实测):

    {"error":"'highlights.dynamic' is in beta. Send the 'Exa-Beta: dynamic-highlights-2026-08-28' request header to use it.","tag":"INVALID_REQUEST"}

本插件两样都发,并且彻底去掉了 highlightsPerUrl——对着真实 API 实测,Exa 已经忽略这个参数, 传 1 和传 5 返回逐字节相同的结果。改用 maxCharacters,那才是关掉动态高亮时真正生效的旋钮。

实测数据

真实 /search 调用,同一个查询,8 条结果:

| 配置 | 高亮字符数 | | --- | --- | | 官方提供方的默认值(无可用旋钮) | 51,152 | | 本插件 dynamicHighlights: false + highlightsMaxCharacters: 1500 | 10,973 | | 本插件 dynamicHighlights: true(默认) | 12,716 |

Dynamic Highlights 不是一刀切截断:它把所有召回文档拼成一条输入、只做一次前向,在全局范围分配 共享预算——好内容多给,冗余的不给。

安装

dsh plugin --profile web add qp-exa-dynamic

包自带 dsh.bundle 清单,安装后 bundle patch 会自动插入 provider 行,不需要手写。

然后在 $DSH_HOME/profiles/web/cordis.patch.yml 里覆盖 web 那一行来选中它:

- id: web
  name: '@deepseek-ai/dsh-web'
  config:
    searchProvider: exa
    fetchProvider: http

patch 是整块替换目标行的 config、不做合并,所以 fetchProvider: http 必须一起重写, 否则抓取提供方会丢。

然后给密钥,两种方式。写进插件配置:

- id: qp-exa-dynamic
  name: qp-exa-dynamic
  config:
    apiKey: '你的 Exa 密钥'

或者走环境变量。apiKey 标了 role('secret'),不会出现在任何 describe() 响应里——但明文配置文件 终究是明文配置文件,能用环境变量就尽量用。

关于 $DSH_HOME/.env。 插件通过 harness 的启动环境快照读取 apiKeyEnv(默认 EXA_API_KEY), 该快照按文档会查阅继承环境、调用目录的 .env 与 Harness 主目录的 .env。这在有的部署里有效、 有的无效——在一台 Windows 机器上,文件内容正确但快照里就是没有这个变量,最后靠上面的 apiKey 配置解决。如果你的 provider 报 registered but unavailable,就是密钥没送到,直接写 apiKey。

改环境变量后要重启 dsh web。cordis.patch.yml 本身是热加载的,所以配置改动不用重启。

配置字段

全部有安全默认值,通常只需要提供密钥。

| 字段 | 默认值 | 含义 | | --- | --- | --- | | providerId | exa | 注册 id。只在需要与另一个 Exa 提供方共存时改。 | | apiKey | 未设置 | 字面密钥;不设则回退到 apiKeyEnv。 | | apiKeyEnv | EXA_API_KEY | apiKey 未设时读取的环境变量名。 | | baseURL | https://api.exa.ai | Exa 端点;会追加 /search。 | | searchType | auto | 检索类型,见下。可用 /exa type 运行时改。 | | numResults | 8 | 来源上限。可用 /exa results 运行时改。 | | dynamicHighlights | true | 默认开;开启时自动带上必需的 Exa-Beta 头。可用 /exa 运行时改。 | | highlightsMaxCharacters | 未设置 | 每页高亮上限,仅在 dynamicHighlights 为 false 时生效。 |

dynamicHighlights 不会和 highlightsMaxCharacters 同时发——动态开启时共享预算由 Exa 自行分配, 官方文档也明确警告两者不要并用。

/exa 命令

敲在输入框里。它直接对界面执行,不产生模型消息。

| 命令 | 作用 | | --- | --- | | /exa | 切换 Dynamic Highlights | | /exa on / /exa off | 明确设置 | | /exa type | 列出可用的检索类型 | | /exa type deep | 设置检索类型 | | /exa results | 报告来源上限 | | /exa results 3 | 设置来源上限 | | /exa status | 一次报全:当前状态、可用的 type 列表、真实天花板 |

全是裸单词,不需要任何符号:命令没有声明参数提示,所以输入框不会塞进一个待编辑的模板。 /exa status 顺带把检索类型列出来,想不起来有哪些 type 时不用再敲第二条;参数打错时会回一行可以 直接照抄的示例。

写入落在 qp-exa-dynamic settings 命名空间的用户层,跨重启保留。清掉那一节即回到插件 配置的默认值。

实测同一个 provider 实例、同一个查询:关掉动态高亮让同一次搜索从 12,716 字符变成 57,958 字符—— 差 4.6 倍,下一次搜索即刻生效。

exa_search 工具

插件还在 web_search 旁边注册了第二个面向模型的工具:

exa_search(query: string, maxResults?: integer)   // maxResults 1-50

它存在的原因是一条硬结构事实:ctx.web.search() 按调用方的 request.maxResults 截断结果, 而 dsh-tool-web 每次调用都传自己的 searchMaxResults——所以任何提供方都不可能超过那个上限, 要抬它就得 fork agent preset。这个工具拥有自己的 request.maxResults,于是条数从部署级天花板 变成了每次调用的模型参数。

这让 preset fork 变成可选的。 两条拿到超过默认 8 条的路径:

| 想要 | 怎么做 | 需要 fork preset 吗 | | --- | --- | --- | | 某次要 20 条 | exa_search(query, maxResults: 20)——直接用话问就行 | 不需要 | | 让 /exa results 20 生效 | /exa 命令 | 需要 |

即使不 fork,/exa results 也值得设——因为这个工具不传 maxResults 时就回退到它: 设了 /exa results 20 之后,exa_search(query) 会返回 20 条。被夹在部署上限里的只有 web_search。

工具和 web_search 一样走 ctx.web,所以用的是同一个被选中的提供方、同一个检索类型、同一个 Dynamic Highlights 设置;只有条数不同。它 50 的上限是自己的——动态高亮实测每条约 1.6k 字符, 50 条已经是约 2 万 token 的上下文。

web_search 旁边会多一段提示告诉模型什么时候该用它(日常检索仍走 web_search)。如果组合里没有 tools 注册表,provider 照常挂载,只是没有这个工具。

检索类型

Exa 的 type 就是延迟/质量的旋钮。8 种全部对着真实 API 验证过;同一查询、8 条结果的实测耗时:

| 类型 | 实测 | 用途 | | --- | --- | --- | | keyword | 464 ms | 纯关键词,最快 | | neural | 737 ms | 语义检索 | | fast | 798 ms | 快,质量损失极小 | | instant | 856 ms | 实时场景(对话、语音) | | auto | 1,914 ms | 默认 | | deep-lite | 3,116 ms | 轻量综合输出 | | deep | 5,282 ms | 多步推理 | | deep-reasoning | 18,278 ms | 最难的研究任务 |

官方提供方的 schema 只列了 auto、keyword、neural 三种——那套已经过时。本插件全部开放。

deep* 在这个 seam 下有折扣

用本插件自己的类实测,同一查询,动态高亮开启:

| 类型 | 耗时 | 返回来源数 | | --- | --- | --- | | fast | 718 ms | 8 | | auto | 215 ms | 8 | | deep | 6,891 ms | 3 | | deep-reasoning | 14,776 ms | 4 |

原始 API 对 deep 是返回 8 条的;其余几条没有非空白高亮,被整个丢弃了——seam 没有别的字段能当 snippet,编造一个会让 seam 说谎。而 deep* 真正值钱的是跨来源综合出的 output,WebSearchSource 里没有字段承载它。所以在这个 seam 下,实用区间是 keyword、neural、fast、instant、auto。

返回条数归 dsh-tool-web 管

这一条容易误解,说清楚:

  • 模型侧 web_search 工具的参数只有 queries——模型无法要求条数。
  • 天花板归 dsh-tool-web:searchMaxResults,默认 8。它自己的注释: "The consumer owns the returned-context limit; providers and models do not."
  • 工具每次调用都会传 maxResults,seam 再按它截断结果——所以任何提供方都不可能超过天花板。

于是本插件的 numResults 是单向的:能拉低,拉不高。

| 你设的 | 工具天花板 | 实际发给 Exa | | --- | --- | --- | | 3 | 8 | 3 | | 12 | 8 | 8(夹取) | | 20 | 8 | 8(夹取) |

/exa status 和 /exa results 会报出 provider 观测到的真实天花板,所以被夹取时是明说而不是 静默生效。

在 Web 端,抬天花板不是改 profile patch 能解决的。 dsh-web-app 把宿主那行 tool-web 设为 disabled: true——因为宿主侧只有 web 服务和它的搜索提供方,面向模型的工具是每会话一份的, 真正生效的那份来自 agent preset。随附的 standard preset 里那行只有 fetch 和 searchTimeoutMs,没有 searchMaxResults,于是取 schema 默认值 8。往 profile patch 里写 tool-web 只会落在被禁用的宿主行上,什么都不做。

所以要改就得 fork preset:把 standard 复制到 $DSH_HOME/.agent-presets/,在它的 tool-web 行加 searchMaxResults,再选为默认。这是笔真实的账——副本不会跟随随附 preset 的上游更新,而默认值是在 创建会话时读取的,运行中的会话仍停在它们当初组装的 preset 上。多数情况下留在 8 更划算:/exa results 仍然能把条数往下调,而那个方向才是省 token 的。

已知限制

  • Exa 的 beta 接口可能变。 dynamic-highlights-2026-08-28 是 research preview,Exa 改版后需要 更新 DYNAMIC_BETA_VALUE。
  • 没有高亮的结果会被整条丢弃,这是 seam 的规则。动态高亮下实测 8/8 条都带高亮,所以很少触发。
  • 没有 category、域名/日期过滤,也没有全文。 这些 Exa 能力本插件尚未暴露。
  • 本插件与官方 Exa 提供方每个 profile 只能选一个。 两者默认都注册 provider id exa;要共存需给 其中一个设不同的 providerId。
  • 仅测过 dsh 0.1.5-rc.1,peer 范围也锁在这条线上。
  • 没有 settings 服务时 /exa 只改内存。 provider 本身照常工作;没有命令注册表时就没有 /exa。

开发

node test/index.test.js    # 40 个单元测试,不需要密钥
EXA_API_KEY=... node test/live.mjs   # 打真 API,会消耗额度

请直接运行测试文件,不要用 node --test:后者的 runner 会为每个文件 spawn 子进程,在受限沙箱下 会以 spawn EPERM 失败。

从本地检出安装

dsh plugin --profile web add <路径> 记下的是一个 link: 依赖,profile 按包名链接它。而 Node 解析 被链接模块自身的 import 时,用的是它的真实路径、不是 profile 里那个链接——所以放在 $DSH_HOME/profiles/ 之外的检出没有能到达 harness 各包的 node_modules 祖先目录,每一个 @deepseek-ai/dsh-* 导入都会以 ERR_MODULE_NOT_FOUND 失败:插件挂载上了,然后在第一个 import 上炸。

给它配一份指向运行时同一批实例的 node_modules:

$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"
}

用 junction 而不是再跑一次 pnpm install,是刻意的:@deepseek-ai/dsh-tools 是运行时单例, 嵌套一份副本会让 agent loop 在 provider 被调用之前就崩。全部指向 hoist 目录,才能保证每个包只有 一个物理实例。

卸载

dsh plugin --profile web remove qp-exa-dynamic

把 cordis.patch.yml 里的 web 覆盖删掉即回到内置的 DeepSeek 搜索。那个改动是热加载的,立刻生效。

许可

MIT