← 返回列表
未验证
1. 向 DSH 的 ctx.web 接缝注册一个搜索提供程序和一个抓取提供程序,使标准的 websearch 和…
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/8/20 · 已提供中文文档
DeepSeek Harness 的网页搜索、内容提取、YouTube 转录、PDF 文本和 GitHub 克隆——零依赖,无需 API 密钥即可使用
综合分
27.2
GitHub 分
27.2
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add sehoon123/dsh-web-access该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-web@deepseek-ai/dsh-tools用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-web-access
为 DeepSeek Harness 提供网页搜索、内容提取、YouTube 字幕、PDF 文本和 GitHub 克隆——零 npm 依赖,无需 API 密钥即可进行真正的网页搜索。
License: MIT
Node
Tests
此插件做两件事:
1. 向 DSH 的 ctx.web 接缝注册一个搜索提供程序和一个抓取提供程序,使标准的 web_search 和 web_fetch 在无需更改提示词的情况下大幅提升;以及
2. 注册两个额外的工具——web_read 和 web_research——用于该接缝无法表达的场景,因为 WebFetchRequest 是 { url },不携带提示词、模式、时间戳或查询列表。
灵感来自 Pi agent 的 pi-web-access;基于 DSH 的能力接缝重建,无运行时依赖。
为什么
开箱即用时,DSH 的网页能力很薄弱:
| | 原生 DSH | 使用此插件后 |
|---|---|---|
| web_search | 仅 DeepSeek Search API(需要付费密钥) | 无需密钥即可使用(DuckDuckGo + Exa MCP),外加 7 个可选的带密钥引擎和 SearXNG |
| web_fetch | 默认未挂载;原始 HTML;无 SSRF 防护;拒绝跨源重定向 | Readability 风格的 Markdown,SSRF 防护,安全跟随重定向,无密钥阅读器回退 |
| PDF | 被拒绝(WEB_UNSUPPORTED_CONTENT_TYPE) | 提取文本(字体、CMap、词间距) |
| YouTube | 无用的 HTML | 元数据 + 带时间戳的字幕 |
| GitHub | 页面外壳 | 浅克隆 + 目录树 + README,或原始文件 / issue / PR |
安装
1. Put the package where your profile can resolve it
git clone https://github.com/sehoon123/dsh-web-access.git
mkdir -p ~/.dsh/profiles/node_modules/@deepseek-ai
cp -R dsh-web-access ~/.dsh/profiles/node_modules/@deepseek-ai/dsh-web-access
2. Add to ~/.dsh/profiles//cordis.patch.yml
- insert:
- id: web-access
name: '@deepseek-ai/dsh-web-access'
This plugin supersedes the stock fetch provider (no SSRF protection there).
- id: web-fetch-http
disabled: true
REQUIRED: ctx.web allows exactly one usable provider per capability unless
pinned, and dsh-base already mounts web-search-deepseek.
- id: web
config:
searchProvider: web-access
fetchProvider: web-access
- id: tool-web
config:
fetch: true
searchTimeoutMs: 60000
然后重启 dsh。就这样——无需 API 密钥、无需 Docker、无需配置。
为什么必须固定(pinning)。 ctx.web 在调用时解析提供程序:配置的 id 优先;否则必须恰好注册一个可用的提供程序。
挂载两个提供程序时,若 searchProvider 未设置,则每次搜索都会失败,
并报错 WEB_PROVIDER_AMBIGUOUS。
可选二进制文件
| 二进制文件 | 解锁功能 | 没有它时 |
|---|---|---|
| yt-dlp | YouTube 转录文本 | 仅元数据,并附有清晰说明 |
| git | GitHub 浅克隆 | 改为通过 API 获取 README |
brew install yt-dlp # 或:pipx install yt-dlp
无需密钥即可使用的功能
已针对实时端点验证(参见测试):
| 引擎 | 类型 | 备注 |
|---|---|---|
| DuckDuckGo | 通用网页 | HTML 端点;需要文本模式的 User-Agent(详情) |
| Exa MCP | 通用网页 | Exa 托管的 MCP 端点,匿名;返回干净的页面文本 |
| Wikipedia | 百科 | MediaWiki API + 文章摘要 |
| Stack Exchange | 编程问答 | 匿名配额 |
| Hacker News | 讨论 | Algolia API |
| GitHub | 代码仓库 | 匿名 10 次请求/分钟;使用令牌可提高配额 |
| npm | 软件包 | 注册表搜索 |
| DuckDuckGo Instant Answer | 定义 | 官方 API;仅限实体查询 |
无密钥的页面读取还为受机器人拦截或仅支持 JavaScript 的页面提供了两种回退方案:Jina Reader(r.jina.ai)和 Exa web_fetch——两者均为匿名。
可选的需要密钥的引擎
设置密钥后会自动使用,优先于无密钥的垂直引擎:
TAVILY_API_KEY、BRAVE_API_KEY、SERPER_API_KEY、EXA_API_KEY、
PERPLEXITY_API_KEY、JINA_API_KEY、FIRECRAWL_API_KEY。
密钥首先从 DSH 的凭据接缝解析(因此 ~/.dsh/.credentials.yaml 和
Models 页面均可使用),然后从环境变量解析。它们绝不会写入日志或
搜索输出。
自托管 SearXNG(可选)
公共 SearXNG 实例对程序化使用不友好——对十一个知名实例的扫描返回了
429/403,而做出响应的实例则禁用了 format=json。因此请将其指向你自己的实例:
- id: web-access
name: '@deepseek-ai/dsh-web-access'
config:
searxngUrl: http://localhost:8080
allowPrivateEndpoints: true # 本地主机/局域网实例需要此项
优先尝试 JSON,并自动回退到解析 SearXNG 的 HTML。
搜索的工作原理
一个提供程序 id(web-access)前端连接一个内部引擎链,因为该接缝
每个能力只允许一个提供程序。
query
├─ 并发运行前 N 个可用引擎 (searchConcurrency,默认 3)
├─ 按规范化 URL 合并 + 去重 (去除 utm_、片段、www、末尾 /)
├─ 按排名交错,使任何单一引擎都不会占主导
├─ 保留第一个由提供程序生成的答案作为 content
└─ 如果全部 N 个都未返回任何内容 → 按顺序尝试其余引擎
默认顺序——两个无密钥的通用索引领先,因此裸安装也很有用:
exa-mcp → duckduckgo → tavily → brave → serper → searxng → exa →
perplexity → jina → firecrawl → wikipedia → stackexchange →
hackernews → github → npm → duckduckgo-instant
每个结果的摘要都会标注发现它的引擎,因此多引擎一致性对模型可见。单个引擎失败绝不会导致工具失败;只有所有引擎都失败才会抛出异常。
fetch 的工作原理
url
├─ YouTube? → 元数据 + 转录文本(yt-dlp)
├─ GitHub? → clone + tree + README | 原始文件 | issue/PR | 列表
└─ 直接 HTTP(SSRF 防护)
├─ PDF → 文本提取
├─ HTML → readability 风格的 Markdown
└─ text/JSON/XML → 原样返回
↓ 仅在受阻(401/403/429/5xx)或为空时
无密钥 reader 回退:Jina Reader → Exa web_fetch
所有内容都以 kind: 'text' 返回,dsh-tool-web 会将其原样传递——因此模型看到的就是这个包的提取质量。
回退是刻意保守的
把每个页面都通过第三方洗一遍会更慢、隐私性更差,还会丢弃良好的第一方提取结果。因此回退只在真正的失败信号下触发:
| 情况 | 是否回退? |
|---|---|
| 直接抓取抛出异常 | 是 |
| 401 / 403 / 405 / 406 / 429 / 5xx | 是 |
| HTML,文本很短 且 原始标记大得多(JS 外壳) | 是 |
| HTML,文本很短,原始页面很小(一个真正的小页面) | 否 |
| PDF 提取出的文本为零(扫描件) | 是 |
| PDF 有任何文本 | 否 |
| 404 | 否——那是一个答案 |
安全模型
原版 dsh-web-fetch-http 声明它没有私有网络防护。这很重要,因为 URL 是模型选的:http://169.254.169.254/… 可访问云元数据,而 http://10.0.0.5/admin 可访问你的局域网。
本插件通过三层拒绝这些请求:
1. 语法——仅限 http(s),不允许内嵌凭据,长度受限。
2. 静态主机策略——位于 loopback / RFC1918 / link-local / CGNAT / TEST-NET / multicast / reserved 范围内的字面 IP;localhost、metadata.google.internal 及其同类;.local、.internal、.lan、.home.arpa、.corp。
IPv6 覆盖 ::1、fc00::/7、fe80::/10、2001:db8::/32,并解包 IPv4 映射形式,使 ::ffff:10.0.0.1 无法溜过。
3. DNS 预检——解析主机名,每个应答都必须是公网地址,然后每个重定向跳转都会重新校验(重定向是把内部地址偷运过仅检查第一跳的经典手法)。
还强制实施:字节上限、字符上限、重定向上限、每个请求链一个超时预算、无 cookie、无环境凭据,以及对 yt-dlp 和 git 使用带 argv 数组的 execFile(绝不使用 shell)。
诚实的局限
- DNS 重绑定并未完全封堵。 Node 的 fetch 无法把连接固定到我们校验过的地址,因此恶意的权威服务器可以在预检时返回公网地址,而连接时返回私有地址。封堵它需要一个带有每连接 lookup 钩子的自定义 agent。参考实现也有同样的缺口。
- 策略拒绝绝不回退。 要求 Jina 或 Exa 去抓取一个地址
我们刚刚拒绝了会击败守卫,所以 WEB_BLOCKED_URL 是最终结果。
- allowPrivateEndpoints: true 会为每个请求禁用第 2–3 层。仅在你控制的
自托管 SearXNG/Firecrawl 上使用它。
性能
响应缓存(默认开启)。 多轮分析会反复读取同一份公告
或 PoC。抓取结果会缓存在一个有界的 LRU 中,带有 TTL 和总字节数
上限,因此重复读取实际上是免费的(实测:497 ms → 0.03 ms)。
raw 和 readable 结果从不共享缓存键,非 2xx 结果、错误
以及读取器回退结果被有意地不缓存,这样一次瞬时失败
就不会污染后续读取。传入 noCache: true 可绕过。
可选的搜索结果内容内联(默认关闭)。 400 字符的摘要通常
无法区分可用的 PoC 和 SEO 垃圾。设置 inlineTopResults(1–3),或在
web_research 上按调用传入,以将一段有界的页面摘录附加到顶部结果 —
将两轮工具调用变成一轮。内联会复用缓存和按主机限速,
有时间限制,并且内联抓取失败绝不会导致搜索失败。
配置
全部可选。默认值经过选择,因此空配置也能工作。
引擎选择
| 键 | 默认值 | 含义 |
|---|---|---|
| engines | [](= 默认顺序) | 显式排序的引擎 id |
| disableEngines | [] | 要跳过的 id |
| searchConcurrency | 3 | 并行查询的引擎数 |
| maxResults | 8 | 结果上限 |
| exaMcp / exaMcpUrl | true / 托管 | 无需密钥的 Exa MCP |
| duckduckgo | true | 无需密钥的 DuckDuckGo |
| duckduckgoUserAgent | 文本模式 UA | 不要设置浏览器 UA — 它会被封禁 |
| duckduckgoRegion | – | 例如 us-en、kr-kr |
| wikipediaLanguage | en | 维基百科语言 |
| stackExchangeSite | stackoverflow | Stack Exchange 站点 |
| githubToken | $GITHUB_TOKEN | 提高 GitHub 速率限制 |
限制
| 键 | 默认值 |
|---|---|
| searchTimeoutMs | 45000 |
| fetchTimeoutMs | 45000 |
| readerTimeoutMs | 60000 |
| youtubeTimeoutMs | 120000 |
| githubCloneTimeoutMs | 180000 |
| maxResponseBytes | 8000000 |
| maxBodyChars | 120000 |
| maxRedirects | 5 |
| minArticleChars | 200 |
处理器与提取
| 键 | 默认值 | 含义 |
|---|---|---|
| includeLinks | true | 在 Markdown 中保留超链接 |
| includeImages | false | 保留图片引用(消耗上下文) |
| youtube | true | YouTube 处理器 |
| youtubeTranscripts | true | 通过 yt-dlp 获取字幕 |
| youtubeTimestamps | true | [mm:ss] 前缀 |
| youtubeSubtitleLanguages | en.,en | yt-dlp --sub-langs |
| ytDlpPath | yt-dlp | 二进制文件路径 |
| github | true | GitHub 处理器 |
| githubClone | true | 浅克隆仓库 |
| githubCloneDir | $DSH_HOME/web-access/repos | 克隆缓存 |
| githubTreeDepth | 2 | 树形列表深度 |
| readerFallback | true | 启用无密钥读取器 |
| readerOrder | ['jina','exa'] | 回退顺序 |
安全
| 键 | 默认值 | 含义 |
|---|---|---|
| allowPrivateEndpoints | false | 允许私有/回环目标 |
| trustEnvProxy | false | 在 HTTP(S)_PROXY 后跳过 DNS 预检 |
| userAgent | 产品 UA | 通用抓取 UA |
零依赖
"dependencies": {}。参考实现使用了八个 npm 包
(@mozilla/readability、linkedom、turndown、unpdf、undici 等)。本实现
不使用任何依赖,因为 DSH 插件会在 node_modules 目录树之间被复制(npx
缓存和 $DSH_HOME/profiles),而在这些位置 npm 依赖可能无法解析。
因此改为从零编写:
- HTML 解析器 —— 宽容的分词器 + 树构建器,支持 void/raw-text
元素和隐式闭合规则。值得注意的是,它在查找标签结尾时会
尊重带引号的属性;朴素的 indexOf('>') 会在真实的 MediaWiki 页面上
将属性 JSON 泄漏为可见文本。
- 内容选择 —— readability 风格的评分(文本密度、正文标签
数量、链接密度惩罚),优先选择 //role=main,并带有
回滚保护:若一次剪枝破坏了超过 80% 的文本,则直接拒绝。
- Markdown 渲染器 —— 标题、强调、链接、图片、带语言检测的围栏代码块、
嵌套列表、块引用、GFM 表格。
- PDF 提取器 —— 字体 /Encoding /Differences 字形恢复、连字
规范化、换行去连字符、对象映射、对象流(/ObjStm)、宽容的 inflate
(Z_SYNC_FLUSH,对于使用间接 /Length 的真实文件是必需的)、ToUnicode
CMap(bfchar/bfrange、UTF-16BE + 代理对),以及宽度感知间距,使
输出读作 Recurrent neural networks,而不是 Recurrentneuralnetworks。
- MCP 客户端 —— 调用 Exa 所需的 Streamable HTTP + JSON-RPC 片段。
测试
npm test # 离线 + 在线(需要网络)
npm run test:offline # 仅纯逻辑,无网络
npm run test:pdf # PDF 提取器测试套件
396 个通过,跨五个套件:suite(265,离线 + 在线)、pdf(57)、
security(16)、rank(22)和 cache(36)。离线覆盖 SSRF 范围和 URL
策略、HTML 解析和 Markdown 转换、chrome 剪枝回滚保护、
内容分类、读取器回退启发式、YouTube/GitHub URL
分类、PDF 提取、MCP/SSE 解析、DuckDuckGo 和 SearXNG 结果
解析、去重/合并、全部 16 个带密钥引擎的凭据门控、帧时间戳
解析和采样计划、域名/时效过滤器、答案路由解析,以及
提供方形态。在线覆盖 DNS 预检、六个无密钥引擎、DuckDuckGo、
编排搜索、跨 HTML/JSON/PDF/Wikipedia/GitHub 的抓取路由,以及
端到端 SSRF 强制执行。
PDF 套件新增 46 个断言,包括 240 个变异/截断模糊测试用例
以及恶意输入(引用循环、500 层深的嵌套、一个 80 MB 的 zip 炸弹)。
在真实的 dsh 实例中验证过,而不仅仅是在单元测试中:
- 全部四个工具都出现在目录中:web_fetch, web_read, web_research, web_search
- web_search 无需 API 密钥即可返回带引用的来源
- web_fetch 返回 Example Domain、Dummy PDF file、Attention Is All You Need
(arxiv)、一份 YouTube 转录文本,以及一个 GitHub 克隆路径
- web_read mode:"answer" 仅依据页面内容作答,并报告它所使用的路由,
该路由从调用方 agent 自动解析
- web_read timestamp/frames 将真实视频帧呈现在模型面前——会话日志显示
持久化的图像附件,并且模型描述了实际的屏幕内容
- web_research 合并三个查询组,并应用了域名过滤
与 pi-web-access 的差异
已移植,包括那些需要超出 { url } 的部分
| 能力 | 实现方式 |
|---|---|
| 有依据的答案(mode: "answer") | web_read 仅依据抓取的页面回答提示,当答案不在其中时回复 NOT_IN_SOURCE |
| 视频理解 | web_read 返回转录文本,timestamp/frames 提取真实帧作为图像块,供视觉模型查看 |
| 多查询研究 | web_research 运行 2–4 个查询并合并结果,支持 domainFilter 和 recency |
| 原始模式 | mode: "raw" 返回未经修饰的正文 |
| 批量读取 | url 或 urls[](最多 5 个,并行) |
| 无密钥通用搜索 | DuckDuckGo HTML 端点 + Exa 托管 MCP |
| 16 个带密钥的引擎 | Tavily、Brave、Serper、Exa、Perplexity、Jina、Firecrawl、Kagi、Mojeek、Search1API、TinyFish、Valyu、xAI、Bright Data、SerpBase、Bocha |
答案模式和视频问答运行在harness 自身配置的模型上,通过
ctx.llm——路由从调用方 agent 解析,因此无需管理第二份凭证。参考实现
为同样的功能需要自己的 geminiApiKey。
确实没有的功能
- 针对扫描文档的 PDF OCR。 基于文本的 PDF 可以正常提取;纯图像
的 PDF 需要光栅化二进制文件(poppler/ImageMagick——构建此功能的机器上
均未安装)或文档 AI API。这不是设计限制,只是尚未实现。
- 浏览器 cookie 抓取(Gemini Web、Chrome cookie 提取)以及
curator-page 结果整理流水线(约 4k 行)。
- 本地视频文件可用于提取帧,但没有上传整个视频的路径:
ctx.llm 的请求结构携带文本和图像,而非媒体文件。
有意为之的不同之处
零运行时依赖;默认开启 SSRF 防护;DSH 凭证接缝集成;
一个接缝提供方 id 搭配内部引擎链;kind: 'text' Markdown 使提取质量
由我们掌控;并且额外工具是增量的而非替代性的,因此
web_search/web_fetch 完全照旧工作。
额外工具
web_read
{jsonc
"url": "https://…", // 或 "urls": ["…", "…"] (最多 5 个,并行)
"mode": "readable", // readable | raw | answer
"prompt": "…", // mode 为 "answer" 时必填
"model": "provider/model", // 可选;默认为调用方 agent 的模型
"timestamp": "23:41-25:00", // 视频帧时间或时间范围
"frames": 4 // 帧数(最多 8)
}
bash
从单个页面获取有依据的答案
web_read { url: "https://nodejs.org/api/fs.html", mode: "answer",
prompt: "Is fs.promises.cp recursive by default?" }
查看某一时刻屏幕上的内容
web_read { url: "https://youtube.com/watch?v=…", timestamp: "1:23", frames: 3 }
web_research
jsonc
{
"queries": ["…", "…"], // 2-4 个真正不同的角度
"numResults": 6,
"domainFilter": ["docs.rs", "-pinterest.com"], // "-" 表示排除
"recency": "month" // day | week | month | year
}
用 tools: false / multiQuerySearch: false 禁用其中任意一项。
项目结构
lib/
├── index.js 插件入口:配置、凭据、注册
├── search/
│ ├── index.js 编排器:选择、合并、去重
│ ├── duckduckgo.js 无需密钥的通用网页搜索
│ ├── exa-mcp.js 无需密钥的通用网页搜索(托管 MCP 客户端)
│ ├── keyless.js Wikipedia、DDG-IA、Stack Exchange、HN、GitHub、npm
│ ├── keyed.js 16 个需密钥的引擎(Tavily、Brave、Kagi、xAI……)
│ └── searxng.js 自托管元搜索(JSON + HTML)
├── tools/
│ ├── index.js web_read + web_research 工具定义
│ └── answer.js 通过 ctx.llm 进行有依据的问答
├── fetch/
│ ├── index.js 路由器 + 直接 HTTP 解码
│ ├── youtube.js 元数据 + 字幕
│ ├── frames.js ffmpeg 帧提取 -> 图像块
│ ├── github.js clone / raw / issue / PR
│ ├── pdf.js 零依赖 PDF 文本提取
│ └── reader.js 无需密钥的 Jina / Exa 回退
└── util/
├── ssrf.js URL 策略 + DNS 预检
├── http.js 有界传输、逐跳重新校验
└── html.js 解析器、内容选择、Markdown 渲染器
许可证
MIT扫码进群