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

dsh-web-search-free

v1.4.0

Published

Free web search plugin for dsh supporting firecrawl and tavily

Readme

dsh-web-search-free

中文 | English

面向 DeepSeek Harness (dsh) 的免费 Web Search / 网页抓取插件。

它把 dsh 默认的 deepseek-official 搜索/抓取通道,替换成一个多引擎 + 自动 fallback 的通道:你填入哪些引擎的 API Key,它就按你排定的顺序依次尝试,前一个失败(或额度耗尽)会自动落到下一个;同一引擎也可以填多个 Key(每行一个),引擎内 Key 同样按顺序轮换。所有检索请求都由 dsh 的**宿主进程(Node)**直接发往各引擎,不经过官方搜索后端、也不经过任何 LLM;浏览器侧只有那张设置卡片,不发任何网络请求。

  • 注册为 dsh 的 web 能力通道(同时提供 searchProviderfetchProvider,id 均为 web-search-free)。
  • 自带一个 Web 设置卡片(设置 → 插件 → 免费 Web 搜索,英文界面下为 Web Search Free),可拖动排序、逐个填 Key;卡片文案跟随 dsh 的语言设置在中英之间切换。
  • 作为 dsh bundle 层安装:装上即接管 web 搜索/抓取,卸载(并重启 dsh)后回落到默认通道,无需手改 profile。
  • 可在卡片里开关 web_fetch(URL 抓取)——关闭后模型每次调用都会收到明确的"后端已停用"错误,而不是继续抓取。

为什么是 free 的:与官方通道的计费差异

dsh 默认的官方通道 deepseek-official(由 @deepseek-ai/dsh-web-search-deepseek 提供)不是一个专用搜索端点:每次搜索都会发起一次完整的 Messages 模型调用POST …/anthropic/v1/messages,带原生 web_search 服务端工具),由 DeepSeek 在服务端执行搜索、返回结构化结果块。因此每次搜索都要烧两份 token:

  1. 辅助搜索请求本身——一个独立模型收到 Perform a web search for the query: <query> + 工具定义,产生 input + output token(maxTokens 默认 4096、maxUses 默认 5);这是一次完整的模型轮次,延迟与生成都按模型调用计。
  2. 结果回灌对话——解析出的 sources(URL/标题/片段)注入回对话模型上下文,作为对话 token 一直重发直到压缩。

两份都从 DEEPSEEK_API_KEY 余额扣除。

本插件走各引擎的专用检索端点(如 Tavily /search、Exa /search、Jina s.jina.ai),是纯检索,不经任何 LLM:

| | 官方 deepseek-official | 本插件 web-search-free | | ---------- | ------------------------------------------------------------------------ | ------------------------------------------ | | 检索方式 | 一次完整 LLM 模型调用 + 服务端搜索工具 | 直接调各引擎专用检索端点 | | 模型 token | 每次搜索都烧(input + output) | 0(纯检索,不碰任何 LLM) | | 计费来源 | DeepSeek API 余额 | 各搜索 API 自身额度(多数有免费层) | | 凭据 | 必须 DEEPSEEK_API_KEY | 各引擎各自的 API Key | | 结果内容 | 只有 sources;snippet 取自模型引用的片段,未被引用的结果没有 snippet | sources + snippet;Tavily 另给一段直接回答 |

这正是插件名为 "free" 的核心理由——官方每次搜索烧一整轮模型 token,这里只走检索、不烧 token。

还有一个容易踩的坑:官方通道强制依赖 DEEPSEEK_API_KEY。如果你的对话模型走的是第三方渠道(自建 provider、中转站等),你很可能根本没配这个 key——而官方 provider 的 available() 只检查"有没有 key 解析器"(永远有),所以 dsh 会照常选中它,直到模型真的调用 web_search 才抛 WEB_PROVIDER_CREDENTIAL_MISSING,UI 上看不出问题。本插件不依赖任何 LLM 凭据。

支持的引擎

| 引擎 | 搜索 | 抓取 | 结果日期 | 免费额度 | 获取 API Key | | -------------- | :--: | :--: | :------: | -------------------------------------------------- | ------------------------------------------------- | | TinyFish | ✓ | ✓ | 部分 | 搜索/抓取免费(仅按速率限) | https://www.tinyfish.ai/pricing | | AnySearch | ✓ | ✓ | ✗ | 1,000 次/天(每天重置) | https://anysearch.com/pricing | | Exa (Metaphor) | ✓ | ✓ | 部分 | 注册送 $20 + 每月补 $10 credit(累积,不按月清零) | https://dashboard.exa.ai/ | | Tavily | ✓ | ✓ | ✗ | 1,000 credits/月(每月重置) | https://app.tavily.com/ | | Firecrawl | ✓ | ✓ | ✗ | 1,000 credits/月(搜索 2 credits/10 结果) | https://www.firecrawl.dev/ | | Brave Search | ✓ | ✗ | 多数 | $5 额度/月(需绑卡,不扣费) | https://api-dashboard.search.brave.com/register | | SerpApi | ✓ | ✗ | 弱* | 250 次/月(每月重置) | https://serpapi.com/users/sign_up | | Jina AI | ✓ | ✓ | 部分 | 新 key 送 10M tokens(一次性,用完即止) | https://jina.ai/api-key |

表格顺序即默认调用顺序(按可持续免费量从大到小排)。Jina 虽是一次性额度,但仍会进入抓取链——只要填了 Key,supportsFetch 为真的引擎都会被 getActiveProviders('fetch') 选中,与它在搜索链里的位置无关。

关于额度的几种机制:Jina 是一次性 token(新 key 送 10M,搜索 s.jina.ai 每次固定扣 1 万 token,约够 1,000 次搜索,用完只能充值或换 key,不重置);Exa 是可累积 credit(注册送 $20 + 每月补 $10,余额不清零、不重置,约能跑 1,400 次基础搜索);AnySearch 是每日重置(1,000 次/天,约 3 万次/月);Tavily / Firecrawl / SerpApi 是每月重置;Brave 也是每月 $5 credit 重置;TinyFish 的搜索/抓取则完全免费,只卡速率(免费层 Search 30 req/min、Fetch 150 url/min)。

关于抓取:Brave Search 和 SerpApi 都是纯 SERP,没有 URL 抓取端点,所以它们不会进入抓取链(只在搜索链里)。如果只配了这两家的 Key,抓取链为空、会以 No web fetch providers configured. 报错——请再给一个支持抓取的引擎配上 Key。

关于结果日期publishedAt 决定模型能否判断一条结果的时效性,各引擎差别很大(下面是单次查询的实测覆盖率,仅供参考):Brave page_age 18/20、Exa publishedDate 4/10、Jina publishedTime 1~3/10、TinyFish date(新闻结果较全、网页结果部分)、SerpApi date(弱,显示串);Tavily 的 published_date 仅在 topic: 'news' 下返回,本插件走通用网页搜索,因此实际为空;Firecrawl 和 AnySearch 的搜索结果没有日期字段。

如果时效性判断对你重要,可以把 Brave 往调用顺序前面挪——代价是丢掉 Tavily 的直接回答段和较长的摘录。

前置条件

  • dsh ≥ 0.1.2-rc.1。从这个版本起,宿主组合(dsh-base 的 tool-web 行与各 agent preset)统一挂载 web_search/web_fetch 并经 @deepseek-ai/dsh-web seam 取数,本插件只向 seam 注册 provider。更早的版本(≤ 0.1.2-alpha.5)里组合不挂载 web 工具,装本插件后搜索可用,但 web_fetch 不会出现(那类组合请用 1.3.0,它自行挂载 tool-web)。
  • 已安装 dsh,且 dsh 命令可用(在 dsh 源码检出里开发时用 pnpm dsh ... 代替)。
  • pnpmPATH 上(dsh plugin 通过 pnpm 在 profile 目录里管理依赖)。
  • 目标 profile 一般是 web(本插件的客户端半边声明 platform: web,设置卡片只在 Web 界面出现)。web profile 首次使用时会从模板自动初始化。

安装

dsh plugin --profile <name> <pnpm args> 会把 pnpm 参数转发到 profile 目录里执行,执行成功后自动对账 dsh.profile.bundles:凡解析到声明了 dsh.bundle 的依赖,都会被自动追加进 bundle 层栈——本插件正好声明了 dsh.bundle.patch,所以装上即接管 web 搜索/抓取,无需手改 profile。

从 npm 安装(推荐)

dsh plugin --profile web add dsh-web-search-free

从本地源码安装(开发 / 二次开发用)

本仓库 dist/ 被 gitignore,安装前需要先构建出产物。

# 1) 在插件仓库目录里构建
cd /path/to/dsh-web-search-free
pnpm install
pnpm build            # 生成 dist/index.js 与 dist/client.js

# 2) 装进 web profile(在插件目录里执行,"." 会被锚定到当前目录)
dsh plugin --profile web add .

也可以用绝对路径,从任意目录执行: dsh plugin --profile web add /absolute/path/to/dsh-web-search-free

pnpm 对本地目录默认以链接方式安装,所以之后在源码里重新 pnpm build,profile 会即时拿到新产物,适合二次开发。改完客户端半边后刷新浏览器即可(客户端插件的热更新需要 pnpm run dev:web 这类重建监听在跑)。

配置

安装后启动 dsh Web 界面:

dsh web          # 等价于 dsh --profile web

打开 设置 → 插件 → 免费网页搜索(英文界面下为 Web Search Free)卡片:

  1. 点开卡片,引擎分成两组:**「调用顺序」里是已存过 Key、真正参与调用的引擎(带 #1#2 序号);「其他可用引擎 (n)」**里是还没填 Key 的,默认折叠,点标题展开。每一行显示引擎名、能力徽章(搜索 · 抓取仅搜索)、免费额度,以及已配置的 Key 数量。
  2. 点击某一行展开它,会出现输入框;在输入框粘贴 API Key,点行内「获取 API Key ↗」可直达各引擎的申请页。每个引擎支持填多个 Key:每行一个,引擎内会按行顺序轮换。再点一次行可收起。保存后该行会自动移进「调用顺序」组。
  3. 拖动行左侧的 ⋮⋮ 手柄调整调用顺序:排在前面的引擎优先调用,失败则按顺序 fallback;同一引擎的多个 Key 也会逐个尝试,任一 (引擎, Key) 成功即返回,全部失败才报错。未填 Key 的引擎不进入调用链,也没有排序的必要,所以只有「调用顺序」组里的行可拖。注意点行体会触发展开/收起,要拖动请抓 ⋮⋮
  4. 引擎列表上方有 「启用 web_fetch(URL 抓取)」 开关。开启时模型可对指定 URL 调用 web_fetch 取全文;关闭后每次调用会收到明确的错误提示(web_fetch 工具由 dsh 统一挂载,开关控制的是本插件的抓取后端,不再从工具表移除工具)。切换即时生效,无需重启。
  5. 点「保存」。配置通过 dsh 的设置命名空间(web-search-free)持久化,下一次搜索即时生效,无需重启。卡片头部会显示已配置的引擎数量徽章,不用展开就能看出插件是否就绪。

至少配置一个引擎的 Key,否则搜索/抓取会以 No web search providers configured. 报错。

关于 web_fetch:dsh 0.1.5 起官方组合默认启用它(TUI/headless 由 dsh-base 的 tool-web 行挂载,Web 界面由各 agent preset 的行挂载),工具始终在模型工具表里。本插件把"抓取走哪个后端"接了过来,并提供一个开关:关闭后调用会收到明确的错误提示。若你在意出网面,关掉即可——代价是模型无法读取你贴给它的 URL,也无法精读长文档,只能靠搜索摘要。

验证

  1. dsh web 启动 Web 界面。
  2. 在对话里让模型用 Web 搜索/抓取(例如「搜一下今天的新闻」或「抓取 https://example.com 的内容」)。抓取需要卡片里的 「启用 web_fetch」 处于开启状态,否则模型调用 web_fetch 会收到"后端已停用"的错误提示。
  3. 请求会经 web-search-free 通道按你排定的引擎顺序执行;某个引擎失败时会在日志里看到 Provider <name> ... failed. Trying next provider ...,随后自动尝试下一个。

更新

# 本地源码:重新构建即可(链接安装,无需重装)
cd /path/to/dsh-web-search-free && pnpm build

# npm:升级到新版本
dsh plugin --profile web update dsh-web-search-free

update 同样会触发对账:新版本若新增/移除了 dsh.bundle 声明,bundle 层栈会自动跟进。

卸载

dsh plugin --profile web remove dsh-web-search-free

对账会把本插件从 dsh.profile.bundles 移除,web 搜索/抓取回落到 dsh-base 的 deepseek-official 默认通道——无需手改 profile 或 patch。

两点需要注意:

  • 卸载前先点卡片底部的「清空全部配置」。dsh 的卸载流程不会删除设置命名空间里存的东西,你的 API Key 会留在 $DSH_HOME/settings.yaml 里。这个按钮会把本插件写入的所有值清掉(需点两次确认)。清空后文件里会剩一个空的 web-search-free: 键——不含任何值,客户端没有 API 能删掉键本身。
  • 卸载后需要重启 dsh 才能真正回落到官方通道。web 行的 provider 选择是启动时组合出来的,卸载只会让本插件的条目在当前进程里失效;重启前搜索会报 WEB_PROVIDER_CONFIGURED_MISSING

工作原理

本插件是一个 dsh bundle 层package.json 里声明 dsh.bundle.patch: ./cordis.patch.yml)。cordis.patch.yml 做两件事:

  • insert 一行 web-search-free,把本插件的宿主半边纳入组合;
  • 用一条同 id 的 web 覆盖层,把 searchProviderfetchProvider 都重指到 web-search-free,从而盖过 dsh-base 里钉死的 deepseek-official

宿主半边(src/index.tsinject: ['web'])向 ctx.web 注册搜索与抓取 provider,内部按 providerOrder 顺序遍历「已配 Key」的引擎做 fallback;每个引擎的 Key 字段可填多个(每行一个),引擎内也会按行顺序逐个轮换。客户端半边(src/client.tsx)在「插件」设置页注册一张 React 卡片,读写同一命名空间 web-search-free 的设置。两层靠这个命名空间字符串对齐。

web_fetch 工具本身的挂载归组合层所有(dsh ≥ 0.1.5):TUI/headless 下由 dsh-basetool-web 行挂载(fetch: true),Web 界面上由每条 agent preset 各自挂载同名的 tool-web 行。preset 文件由 dsh-agent-presets 从自己的根目录加载、不属于 profile patch 栈,bundle patch 够不到——也不需要:所有这些行都通过同一条能力通道(seam)取数,上面那条 web 覆盖层已把通道指向本插件。因此本插件自行挂载 dsh-tool-web(0.1.2 时代的做法):per-agent 作用域的注册会遮蔽全局注册,自挂载只会和 preset 行重复注册 web_fetch,卸载自己的 fiber 也动不到 preset 那份。

enableFetch 开关据此实现为抓取 provider 的可用性:seam 在每次执行时读取 available(),关闭后 web_fetch 仍在工具表里,但每次调用都返回结构化的 WEB_PROVIDER_CONFIGURED_UNAVAILABLE 错误(dsh 官方语义)。切换即时生效,无需 watch/重挂载接线。两个 provider 的注册接在 ctx.effect 上:dsh-web 的 register* 返回注销函数,插件被禁用或热重载时会把自己的 provider 从 seam 摘除,避免下次 apply 撞上 WEB_DUPLICATE_PROVIDER

构建分两步(包声明 "type": "module"):tsconfig.jsonmodule: NodeNext)把宿主半边编成 ESM 产物(dist/index.js 等,与 dsh 运行时同为 ESM,避免 CJS require() 一个 ESM 依赖时触发的加载竞态);tsconfig.client.jsonmodule: CommonJS)单独编出 dist/client.js,再由 wrap-client.cjs 包成 window.__ModuleLoader__.load(...),使其能被 dsh 的浏览器侧模块加载器加载。宿主半边从 @deepseek-ai/schemasterySchema(而非旧版 cordis),Context 仅作类型从 @deepseek-ai/cordis 引入。

插件是装在 profile 旁边的,所有宿主服务必须解析到运行中 dsh 的那一份实例。任何 @deepseek-ai/* 一旦进了 dependencies(或进了非 optional 的 peerDependencies——pnpm 会自动装它),就会在用户的 profile 里多出一份私有副本;hoisted 布局下它平铺到 profile 根目录,遮蔽宿主自己那份,Cordis Service 身份不再相等。这类问题只在别人机器上出现,本地 pnpm build 永远看不见,所以 prepublishOnly 会跑 scripts/check-package.cjs 把它挡在发布之前:禁止宿主包进 dependencies、要求宿主 peer 标 optional、核对 dist 的实际 import 与声明的依赖双向一致、校验 exports 条件顺序与客户端 bundle 是否已包装。改动 package.json 后请跑 pnpm run check

许可证

MIT,见 LICENSE