@epoch-agent/plugin-web

中文文档
6.2.0发布于 2026/11/13Apache-2.0TypeScriptES Module近 30 天下载 4398

网页 — URL + HTML + Markdown + SSRF →

中文使用文档
文档目录9 节
  1. 工具参数
  2. web_search :搜什么,不抓什么
  3. 五个后端,按凭据自动挑
  4. 搜索结果是 风险最高 的一种不可信内容
  5. SSRF 防护是三层,不是一层
  6. 回执语言:给模型读的一律英文
  7. 三个 annotation 不只是元数据
  8. 文件
  9. 开发

@epoch-agent/plugin-web

网页抓取与搜索插件。两个工具:web_fetch(总是有)和 web_search (有可用后端才注册 —— 缺省状态下就有,见下)。

  • ✅ 做:HTTP GET → HTML→Markdown/Text/HTML、SSRF 校验、逐跳重定向校验、大小限制; web 搜索(native / tavily / brave / searxng / keyless 五个后端)
  • ❌ 不做:不执行 JS、不渲染页面、不带浏览器;不抓搜索引擎的 HTML 结果页 (违反 ToS、随时会碎、要处理验证码 —— 要搜就走后端,不走爬虫)
  • 依赖:@epoch-agent/protocol + @epoch-agent/infra,两个都是 dependencies (本包没有 peerDependencies),HTML 转换是手写的(无第三方库)

工具参数

{ "url": "https://example.com/doc", "format": "markdown", "timeout": 30 }
{ "query": "node 22 fetch keepalive", "maxResults": 5, "recencyDays": 30 }

两个工具的参数表在 docs/TOOLS.md —— 那份是工具清单 的唯一真源,这里只写这个包自己的实现取舍。

web_search:搜什么,不抓什么

只返回标题 / URL / 摘要,不返回正文。 一次搜索 5 条结果全抓正文轻松几万 token, 而其中多半是模型看一眼摘要就丢掉的。分工是:搜 → 模型挑 → web_fetch 抓那一条。

五个后端,按凭据自动挑

五档(native / tavily / brave / searxng / keyless)各要什么凭据见 docs/TOOLS.md。

不配 search.provider 时按那张表的顺序探测,第一个可用的赢。显式配了哪家就只认哪家 —— 缺 key 时报诊断而不是悄悄换一家:换一家意味着查询发去了另一家公司, 那是用户没同意的事。

一个后端都没有时 web_search 不注册,启动诊断给 skipped 并逐档列出缺什么。 注册一个一调就报错的工具比不注册更糟:模型看见工具表里有它就会反复试,每次烧一轮。

两档不用办账号的(2026-09-15)

上面那句「没有后端就不注册」原来的实际效果是大多数嵌入宿主的用户都没有搜索 —— 三家老后端全要办账号,而宿主多半不会去配。这一档漏配是静默的:模型不知道自己本该 有搜索,就改用浏览器去抓搜索引擎结果页(正是本包第一节说「不做」的那件事)。

  • native(search/native.ts)借 provider.apiKey,让当前 LLM provider 自己的 服务端搜索去干活。首版认 deepseek / anthropic(共用 Anthropic Messages 协议)。 它的结果项没有明文摘要(正文是 encrypted_content),信息走 SearchOutcome.summary。
  • keyless(search/keyless.ts)打 Exa / Parallel 的匿名 MCP 端点, 带游标轮转摊平两家免费额度。查询词会发给第三方,所以它排在最后、且能整档关掉。

两个开关缺省开,只有显式写 search.native: false / search.keyless: false 才关 (实现是 === false ? 关 : 开,不是「真值才开」)。

为什么是这两家(2026-09-15 实探,不是照抄)

四家里只有 epoch 原来会「什么都没有就把工具拿掉」,所以选型时把同级项目的做法 和这几家的实际表现一起量了一遍:

结论证据
Exa / Parallel 公开 MCP 端点匿名可用无 Authorization,tools/call 返回 200
直连可达,不需要代理绕开本机代理重测:两域名 200 / 0.7s,结果一致
Exa 中文覆盖不差、但质量差中文查 8/8 是中文结果,其中 3 条品牌软文、2 条豆丁网
Parallel 有语义漂移英文查 10 条里 3 条是 Curtain Wall(建筑幕墙)
DeepSeek 服务端搜索更好(所以 native 排第一)POST /anthropic/v1/messages + web_search_20250305:10 条全中文、0 条软文,还捞到了 Exa / Parallel 都没有的平台维度数据

横向对照:codex 用 OpenAI Responses 的服务端 web_search、gemini-cli 用 Gemini grounding(两家都是 native 那一档),opencode 打 Exa / Parallel 公开端点(就是 keyless 这一档),hermes-agent 做了最全的探测级联 + keyless ring。

⚠️ native 排在 tavily 前面是有代价的决定:只设了 TAVILY_API_KEY 却没写 search.provider 的人,从这一版起会改走 native,那把 key 变成静默失效的配置。 要钉住一家就显式写 search.provider。

SearXNG 默认没开 JSON 输出,实例的 settings.yml 里要有 search: { formats: [html, json] }。没开时它返回 403,我们把这句话写进了错误里 —— 否则用户会以为是自己地址写错了。

搜索结果是风险最高的一种不可信内容

它和抓来的网页一样带 openWorldHint: true(见下一节),但风险更高一档: 网页要等 agent 主动去抓,而搜索结果是攻击者可以针对某个查询词主动投放的 (SEO 投毒 + 提示注入)。所以 core 的 system prompt 在不可信内容策略里单独点了它的名。

结果里的 URL 不自动跟随。模型要抓时走 web_fetch,SSRF 校验和 network 权限判定一道都不会因为「这是搜出来的」而放松。

失败(key 错 / 配额用尽 / 网络不通)返回一条说明原因的失败结果,不抛异常 —— 搜索挂了不该让整轮对话断在这儿。同一个查询在同一会话内缓存,省配额。

SSRF 防护是三层,不是一层

  1. 协议白名单——只放 http / https
  2. 主机名与 IP 字面量黑名单——localhost、云元数据域名、私网段 (10/8、127/8、169.254/16、172.16/12、192.168/16、CGNAT、组播与保留段,IPv6 同理)
  3. DNS 解析后再校验一次——攻击者控制的域名可以解析到 127.0.0.1, 光看主机名字面量挡不住。这是最常见的绕过手法

外加逐跳重定向校验:不用 fetch 的 redirect: 'follow',而是手动跟随、每一跳 重新做完整校验(最多 5 跳)。默认跟随只校验第一个 URL,一个公网地址 302 到 169.254.169.254 就能直接读到云元数据。

其余限制:响应上限 5 MB,Cloudflare 挑战会重试一次。

回执语言:给模型读的一律英文

web_fetch / web_search 的工具回执——失败说明、被拦截的原因、provider 报错、 「一条都没搜到」这类说明——全部是英文,判据同 core 的 code-mode/notice.ts 文件头(2026-11-05 拍的口径:回执的读者是模型,跟工具 description 同一档,不跟界面语言走)。 所以这几处 t() 调用显式传了 'en',check:i18n 那边也不要指望它们跟着 zh 变。

反过来,这些不在这一档里:

  • 抓回来的网页正文 / 搜索结果的标题、摘要——那是别人的数据,原样回给模型
  • unavailableReason() 与 selectSearchProvider() 的 detail——启动诊断, 用户读的,照旧跟着界面语言走
  • native 档的 buildPrompt()——发给对端模型的指令,用户读的正是它的回答, 界面切成英文却用中文问它,答案多半也是中文

拦截原因那一格有个额外要求:URL 和判定原因都得留着。url-safety 只给原因 (带主机名 / 解析到的地址),被拒的那个完整 URL 由 web-fetch 补在前面 —— 少了 URL 模型不知道是第一跳还是第五跳被拒,少了原因它只能盲目换个地址重试。

三个 annotation 不只是元数据

annotations: { readOnlyHint: true, idempotentHint: false, openWorldHint: true }

openWorldHint: true 会让 core 的 tool-executor 把输出包进 <tool_output untrusted="true">。抓来的网页内容作者既不是用户也不是我们,必须标成 「数据」而不是「指令」再回灌上下文。

describeTarget: (args) => args.url 让审批缓存按 URL 记——不给它就只能退回参数 JSON, 「批准抓这个站」下次照样弹窗。

文件

文件内容
index.tswebPlugin + createWebSearchTool(条件注册)
tools/web-fetch.ts抓取 + 重定向 + 大小限制 + 重试
tools/web-search.ts搜索工具:参数钳制 + 会话内缓存 + 失败不炸轮
search/provider.ts后端接口 + 共用的 HTTP / 错误分类
search/registry.ts按配置 + 凭据挑后端(挑不到就不注册)
search/{tavily,brave,searxng}.ts三个要办账号的后端各自的请求 / 响应翻译
search/native.ts借当前 LLM provider 的服务端搜索
search/keyless.tsExa / Parallel 免费端点环 + 游标轮转
search/mcp.tsMCP tools/call(纯 JSON 和 SSE 两种回法)
utils/url-safety.ts三层 SSRF 校验
utils/html-to-markdown.ts手写 HTML→Markdown / →Text

开发

pnpm --filter @epoch-agent/plugin-web test

四个用例文件:ssrf.test.ts、url-safety.test.ts、html-to-markdown.test.ts、 web-search.test.ts。改 SSRF 判定必须先看前两个——里面记的是具体的绕过手法, 不是凑数的。搜索那份全部用假 fetch:真打网络的用例要么依赖别人的 API key、 要么在墙内直接红,两种都不该进 pnpm check。

版本与发布记录

@epoch-agent/plugin-web 共发布 57 个版本,首次发布于 2026/08/19;过去 12 个月发布了 57 个版本。

版本发布日期距今
6.2.0latest2026/11/13今天
6.1.02026/11/112 天前
6.0.02026/11/112 天前
5.0.02026/11/103 天前
4.1.12026/11/067 天前
4.1.02026/11/067 天前

关于 @epoch-agent/plugin-web 的常见问题

@epoch-agent/plugin-web 是什么?

网页 — URL + HTML + Markdown + SSRF →

如何安装 @epoch-agent/plugin-web?

在项目目录运行 npm install @epoch-agent/plugin-web,或使用 pnpm add / yarn add / bun add。

@epoch-agent/plugin-web 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@epoch-agent/plugin-web 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@epoch-agent/plugin-web 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@epoch-agent/plugin-web 可以商用吗?

@epoch-agent/plugin-web 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@epoch-agent/plugin-web 的下载量有多少?

根据 npm 官方下载统计,@epoch-agent/plugin-web 在 2026/10/14 – 2026/11/12 这 30 天内被下载 4,398 次,日均约 147 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@epoch-agent/plugin-web 还在维护吗?

最新版本 6.2.0 发布于 2026/11/13(今天);至今共发布 57 个版本,其中过去 12 个月发布了 57 个;首次发布于 2026/08/19。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。