☰
oh-my-opencode-slim 内置增强版 webfetch(smartfetch):面向文档站点的智能抓取工具全解析
2026/9/25 10:45:25 网站建设 项目流程
  • 人工智能
  • AI Agent
  • Agent 编排
  • AI 技能

【免费下载链接】oh-my-opencode-slim

Lean, fine tuned Opencode multi agent suite · Mix any models · Auto delegate tasks

项目地址:https://gitcode.com/gh_mirrors/oh/oh-my-opencode-slim
点击查看免费下载

导读

webfetch是 OpenCode 内置的网络抓取工具,而 oh-my-opencode-slim 在 src/tools/smartfetch/ 目录下实现了一个增强版本,并以同名webfetch注册覆盖默认实现。它面向文档站、静态页与结构化文本做了针对性优化:支持llms.txt探测、Mozilla Readability 正文提取、LRU 缓存与条件重验证、二进制内容落盘,以及可选的廉价次生模型(secondary model)总结。读完本文,你将掌握该工具的全部参数语义、底层执行流程、缓存与重定向策略,以及如何通过webfetch配置项指定专用总结模型。

模块定位与职责划分

根据 src/tools/smartfetch/codemap.md,smartfetch 模块承担两项核心责任:

  • 实现内置webfetch工具:抓取远程文档,执行重定向/origin 策略,在合适时机探测llms.txt,并返回归一化的 text/markdown/html 输出(由 tool.ts 与 network.ts 承担);
  • 围绕抓取步骤做内容整形:HTML 提取、元数据/frontmatter 渲染、标题清理、缓存键、二进制持久化与次生模型回退(由 utils.ts、cache.ts、binary.ts、secondary-model.ts 承担)。

模块内部刻意做了「传输/策略」与「渲染」的拆分:network.ts只负责 URL 归一化、重定向白名单、charset/body 解码、响应头提取与 llms.txt 探测;utils.ts只负责把抓到的内容变成干净的文本/markdown/html,并生成 frontmatter 与面向用户的提示消息。

工具参数详解

createWebfetchTool在 tool.ts 中定义全部入参(Zod schema),下表为完整参数语义:

参数类型默认值说明
urlURL(string)必填要抓取的地址,必须是合法的 HTTP/HTTPS URL
format"text"|"markdown"|"html""markdown"抓取内容的输出格式
timeoutnumber30超时秒数,上限120(见 constants.ts 的MAX_TIMEOUT_SECONDS)
promptstring可选交给次生模型对抓取内容执行的提取任务
extract_mainbooleantrue使用 Mozilla Readability 提取 HTML 正文;关闭时返回整页 body
prefer_llms_txt"auto"|"always"|"never""auto"优先使用/llms.txt或/llms-full.txt;"auto"仅对文档类域名探测
include_metadatabooleantrue在输出前附加 YAML frontmatter(状态码、content type、charset、重定向链、缓存信息等)
save_binarybooleanfalse将二进制负载(图片、PDF、音视频)写入系统临时目录;关闭时只返回元数据

工具的描述字符串(WEBFETCH_DESCRIPTION)同样体现了定位,见 constants.ts:面向静态/文档页的更好提取,支持 llms.txt 探测、内容聚焦的 HTML 提取、元数据、重定向与可选次生模型 prompt。

核心数据流与执行流程

tool.ts中的execute是唯一的编排入口。结合 codemap.md 的 Data & Control Flow,完整流程如下:

  1. 归一化与权限:normalizeUrl处理 URL(HTTP 升级、剥离 fragment),buildPermissionPatterns生成权限模式与允许的 origin 集合,随后ctx.ask弹出webfetch权限询问,并同步计算缓存键buildCacheKey;
  2. llms.txt 探测:当prefer_llms_txt为"always",或为"auto"且isDocsLikeUrl判定为文档类域名时,probeLlmsText依次尝试/llms-full.txt与/llms.txt,只跟随允许的重定向,拒绝 HTML 与登录墙响应(network.ts);
  3. 页面抓取回退:当 llms.txt 不可用时,fetchWithUpgradeFallback处理 HTTPS 升级回退、重定向强制、条件请求头(用于重验证)、二进制检测与有界 body 读取(network.ts);
  4. 内容整形:文本/HTML 负载经extractFromHtml、cleanFetchedMarkdown、extractHeadingsFromMarkdown、frontmatter、joinRenderedContent归一化;二进制负载经saveBinary落盘并返回元数据消息(utils.ts、binary.ts);
  5. 次生模型总结:若调用方提供了prompt且配置了次生模型,runSecondaryModelWithFallback会把输入截断到有界大小、为辅助会话禁用工具、在配置的模型链上重试;若该步骤失败,工具回退到基础抓取内容(secondary-model.ts)。

值得注意的实现细节:探测 llms.txt 与抓取页面共用一个AbortController,但探测自身被runWithScopedTimeout限制在 8 秒内(MAX_LLMS_PROBE_TIMEOUT_MS),因此两者在单次调用内可并行受限执行。

llms.txt 探测机制

对于文档站点,webfetch会在回退到页面本身之前,先探测/llms-full.txt再探测/llms.txt。探测行为由prefer_llms_txt控制:

  • "auto"(默认):仅当域名看起来与文档相关时才探测。文档域判定逻辑isDocsLikeUrl(network.ts)依赖 constants.ts 中的两组启发式:后缀白名单(.readthedocs.io、.readthedocs.org、.gitbook.io、.netlify.app、.vercel.app、docs.rs)与前缀白名单(docs.、developer.、dev.、wiki.);
  • "always":无条件探测;若两种 llms.txt 变体都不存在,则返回明确的失败消息(buildLlmsRequiredMessage);
  • "never":完全跳过探测。

探测过程遵循同源重定向策略(仅允许同一 origin),并且会对结果做三重校验(network.ts):解析后的最终路径必须以/llms.txt或/llms-full.txt结尾;响应不得为 HTML(content type 或 body 均检查);不得命中登录墙(<title>log in|sign in|login</title>或 URL 中含 login 关键词)。

缓存侧同样有对应保护:isInvalidLlmsResult(cache.ts)会逐项验证缓存的 llms 结果——路径不对、content type 是 HTML、body 以<!doctype html开头或命中登录关键词时,缓存条目会被驱逐并重新抓取。

重定向策略与 HTTPS 升级

重定向策略在 network.ts 的fetchWithRedirects中实现:

  • 单次请求最多跟随MAX_REDIRECTS = 10次跳转;
  • 每次跳转前调用isPermittedRedirect校验:协议必须一致、端口必须一致、目标不得带用户名/密码,且目标 origin 必须在允许集合内;
  • 跨 origin 重定向被拦截后,返回blockedRedirect结果,并生成buildRedirectResultMessage提示调用方直接抓取新的 URL(utils.ts);
  • http://URL 会先尝试https://(upgradedToHttps标记),在 HTTPS 请求因连接错误、被拦截的重定向或非 2xx 状态失败时,回退到http://原始地址(fetchWithUpgradeFallback)。

另一个值得注意的归一化细节:URL 中的 fragment(#anchor)永远不会到达服务器(RFC 3986 §3.5),因此normalizeUrl会剥离 hash,使同一文档的不同锚点请求共享一次网络请求与一个缓存条目。

内容提取与质量信号

HTML 正文提取

当响应是 HTML 且extract_main=true时,utils.ts 的extractFromHtml会:

  1. 通过 JSDOM 构造 DOM,读取<title>与<link rel="canonical">;
  2. 提取前 12 个h1/h2/h3标题(extractHeadingsFromMarkdown对 markdown 同样截取 12 个);
  3. 用 Mozilla Readability 解析正文(new Readability(document).parse());
  4. 若提取成功,正文 HTML 经 TurndownService 转成 ATX 风格、fenced code block 的 markdown,同时用extractStructuredText生成结构化纯文本;提取失败则回退到整页 body。

噪音抑制

extractFromHtml的 JSDOM 构造被两层包装保护(utils.ts):

  • withCssTreeWarningsSuppressed:jsdom 内部用 css-tree 解析样式表,而 css-tree 会绕过 virtualConsole 直接调用全局console.warn;该包装临时替换console.warn,屏蔽[csstree-match]前缀的警告,其余警告原样放行;
  • withJsdomCssParsingErrorsSuppressed:通过自定义VirtualConsole屏蔽 jsdom 的css-parsing类型错误,避免解析噪音泄漏到宿主进程的 stderr 与 TUI。

质量信号

detectQualitySignals(utils.ts)会为抓取结果打上三类信号,写进 frontmatter 的quality_signals字段:

  • very_short_content:正文少于 60 词;
  • possible_paywall:命中付费墙/登录关键词(如subscribe to continue、members only、premium content等正则模式);
  • high_boilerplate_ratio:未启用正文提取、原始 HTML 字节数与渲染文本字节数之比 ≥ 10 且词数 < 1200,提示页面样板代码占比过高。

缓存设计

缓存实现在 cache.ts,核心参数为 50 MiB 容量上限、15 分钟 TTL 的内存 LRU 缓存。

  • 缓存键:buildCacheKey对「URL +extract_main+prefer_llms_txt+save_binary」做 JSON 序列化。注意渲染格式(text/markdown/html)不在键内——渲染结果从缓存的抓取结果派生,因此切换格式不会触发冗余网络请求,这是 codemap.md 强调的「按抓取形态缓存」设计;
  • 规范 URL 别名:cacheFetchResult会把结果同时写入请求 URL、最终 URL,以及(在协议/主机/端口/路径/查询完全一致时)canonical URL 三个键,让同文档的多个入口共享缓存;
  • 条件重验证:缓存条目携带ETag/Last-Modified时,buildConditionalHeaders生成If-None-Match/If-Modified-Since;上游返回304 Not Modified时,tool.ts用陈旧条目合并新finalUrl与重定向链,并标记cacheRevalidated: true后重新入缓存,TTL 被刷新而无需重新下载;
  • 内存计量:calculateCacheSize对 llms.txt/纯文本页面将四个字段(rawContent/html/markdown/text)指向同一内容的场景按不同字符串去重计费,避免重复占用缓存容量。

二进制内容处理

二进制检测遵循 network.ts 与 constants.ts 定义的流程:

  1. 显式二进制 MIME(image/*、audio/*、video/*、application/pdf、application/zip、application/octet-stream)直接判为二进制,getBinaryKind将其归类为image/audio/video/pdf/binary;
  2. application/octet-stream与已知文本类型会被重查:扫描前 2 KiB 的 null 字节与非打印字符(looksLikeTextBody),区分文本与二进制;
  3. 声明为text/plain但 body 看起来像 HTML 的内容会被升级为text/html,以启用更好的正文提取(looksLikeHtmlText+looksHtmlPayload判定)。

下载限制分两档(constants.ts):

  • 未开启save_binary时:MAX_BINARY_DOWNLOAD_BYTES = 2 MiB;
  • 开启save_binary时:上限提升到MAX_RESPONSE_BYTES = 10 MiB。

超过上限的二进制内容返回metadataOnly: true的元数据消息(含 content type、大小、Content-Disposition或 URL 推断出的文件名、binary kind、下载限制),并取消响应体读取。开启save_binary时,文件写入<tmpdir>/opencode-smartfetch/<filename>(binaryDir可覆盖,默认path.join(os.tmpdir(), 'opencode-smartfetch'),见 tool.ts),文件名经过sanitizeFilename处理(过滤控制字符与<>:"/\|?*,防 Windows 保留名con/prn/aux等)。

readBodyLimited(network.ts)在流式读取过程中按字节数截断,超限即reader.cancel()并返回truncated标记;withTruncationMarker会在输出末尾追加[..content truncated..]提示。

次生模型总结链

触发条件

decideSecondaryModelUse(secondary-model.ts)规定次生模型仅在以下条件全部成立时才被调用:

  • 提供了非空prompt参数;
  • 已配置至少一个次生模型;
  • 抓取内容的 markdown 非空;
  • 抓取内容词数 ≥ 25(少于 25 词直接跳过,reason 为content_too_short)。

模型解析优先级

resolveSecondaryModels(secondary-model.ts)在插件构造时于内存中解析模型链,优先级从高到低:

  1. webfetch.model(专用模型,最高优先级,支持数组形式的多模型回退);
  2. 宿主配置中的small_model(通过RuntimeConfig.smallModel()获取,见 runtime.ts);
  3. exploreragent 的模型(RuntimeConfig.agent('explorer'));
  4. librarianagent 的模型(RuntimeConfig.agent('librarian'))。

解析完全在内存进行,webfetch 热路径不读任何配置文件——所有值在插件构造时从已加载的合并配置捕获一次,这一约束由 config-read-guard.test.ts 等测试保证。src/index.ts中createWebfetchTool的调用点(src/index.ts)正好体现了这一点:webfetchModels由runtime.webfetch?.model归一化而来,smallModelRef是() => runtime.smallModel()的 getter,explorerModel/librarianModel通过pickAgentModelRef提取。

执行方式

runSecondaryModel支持两条通道:

  • v2 通道:若宿主提供experimental_v2.generateText(ctx.generate.text一次性生成通道),则直接调用generateText(buildPrompt(...)),不创建临时会话,并复用与 v1 相同的buildPrompt嵌入、截断提示与 30 秒Promise.race超时语义;
  • v1 通道:创建一个标题为smartfetch-secondary的临时 OpenCode 会话,通过client.tool.ids查询全部工具 ID 并全部禁用(tools: { id: false }),随后调用client.session.prompt提交「系统提示 + 抓取内容 + 用户任务」的组合;响应后带重试地删除临时会话(deleteSessionSafely,最多 3 次重试、间隔 500ms),超时场景则先abortSessionWithTimeout再清理,避免孤儿会话泄漏。

输入内容会被截断到MAX_MODEL_CONTENT_CHARS = 100_000字符,截断时会在 prompt 末尾追加「只提供了长文档的前 N 个字符」的说明。runSecondaryModelWithFallback按模型链逐个尝试,第一个返回可用文本(非空且不是 "No response from secondary model.")的模型胜出;整条链失败时,tool.ts优雅回退到基础抓取内容,并在 frontmatter 中记录secondary_model_skipped_reason: 'secondary_model_failed'与错误信息。

配置方式

webfetch的配置项由 WebfetchConfigSchema 定义,位于插件配置的webfetch键下。

禁用增强版

设置为false时跳过注册增强版,OpenCode 使用其内置webfetch:

{ "webfetch": { "enabled": false } }

schema.ts中enabled默认值为true;loader.ts在多层配置合并时会剥离各层默认的enabled字段,确保「项目层未显式配置 enabled 时不会覆盖用户层的 false」这类语义正确(见 loader.test.ts 的用例)。

专用次生模型

{ "webfetch": { "model": "openai/gpt-4o-mini" } }

多模型回退(按优先级依次尝试):

{ "webfetch": { "model": ["openai/gpt-4o-mini", "anthropic/claude-3-haiku"] } }

带 variant 的对象形式:

{ "webfetch": { "model": [ "openai/gpt-4o-mini", { "id": "anthropic/claude-3-haiku", "variant": "low-latency" } ] } }

model与 agent 模型配置同构(string、string 数组、{id, variant}对象数组均可),优先级高于small_model、agents.explorer.model、agents.librarian.model。

回退链中的其他来源

{ "small_model": "openai/gpt-4o-mini" }

或在插件配置的 agents 段:

{ "agents": { "explorer": { "model": "anthropic/claude-3-haiku" }, "librarian": { "model": "openai/gpt-4o-mini" } } }

配置的合并与深度合并逻辑见 loader.ts,runtime.webfetch的读取见 runtime.ts。

输出格式与元数据

include_metadata=true(默认)时,文本类结果以 YAML frontmatter 前缀输出,字段包括requested_url、final_url、canonical_url、status_code、source_content_type、source_kind、title、headings、used_llms_txt、extracted_main、redirect_chain、upgraded_to_https、cache_hit、cache_revalidated、word_count、quality_signals、truncated,以及二进制场景的filename、binary_kind、download_limit_bytes、saved_path和次生模型场景的secondary_model(格式provider/model)、secondary_model_input_truncated等(frontmatter 渲染逻辑见 utils.ts 与 tool.ts)。

frontmatter生成器对数组字段采用 YAML 列表缩进格式(- "item"),对未定义值直接跳过;joinRenderedContent对 html 格式会把元数据放进<!-- ... -->注释,对已含自身 frontmatter 的源内容则追加Source content:分隔段。

注册与集成点

  • src/index.ts 在runtime.webfetch.enabled !== false时把增强版webfetch注册进工具集,覆盖 OpenCode 内置同名工具(禁用条件还受 schema.ts 的enabled描述约束);
  • src/tools/smartfetch/index.ts 重新导出createWebfetchTool、WEBFETCH_DESCRIPTION与共享类型,其他模块或文档可直接 import 而不触及实现文件;
  • webfetch权限可纳入插件的权限规则(PermissionActionSchema位于 schema.ts),配置详情参考 docs/configuration.md;
  • cache.ts、network.ts、utils.ts被刻意设计为可复用的测试接缝:缓存行为、重定向策略、llms 探测、标题提取、渲染/元数据辅助函数都可脱离完整工具入口单独验证。

源码地图速查

模块职责关键文件
编排入口权限、缓存、llms 偏好、二进制/文本分支、元数据、次生模型集成src/tools/smartfetch/tool.ts
网络层URL 归一化、重定向策略、charset/body 解码、响应头提取、llms 探测、HTTPS 升级回退src/tools/smartfetch/network.ts
渲染层Readability + Turndown 提取、标题清理、质量信号、frontmatter 生成src/tools/smartfetch/utils.ts
缓存层LRU(50 MiB / 15 分钟)、条件重验证、canonical 别名、llms 结果失效src/tools/smartfetch/cache.ts
二进制落盘持久化、MIME 到扩展名映射、安全文件名src/tools/smartfetch/binary.ts
次生模型模型链解析、临时会话、内容截断、回退重试src/tools/smartfetch/secondary-model.ts
常量超时、大小上限、文档域启发式、二进制 MIME 前缀、工具描述src/tools/smartfetch/constants.ts
类型CachedFetch、BinaryFetch、FetchResult、SmartfetchOptions等src/tools/smartfetch/types.ts

模块设计原则可概括为 codemap.md 强调的几点:单一编排入口(createWebfetchTool)、传输策略与渲染彻底拆分、按抓取形态而非渲染格式做缓存键、全程优雅降级(llms.txt 缺失、重定向被拦截、二进制仅元数据、次生模型失败都不会丢弃已抓取内容),以及任何新增的 JSDOM 构造或 css-tree 触发点都必须包进withCssTreeWarningsSuppressed。整套实现既有详实的实操参数,又有可审计的源码级策略,是理解「面向文档抓取」类工具设计的完整样本。

  • 人工智能
  • AI Agent
  • Agent 编排
  • AI 技能

【免费下载链接】oh-my-opencode-slim

Lean, fine tuned Opencode multi agent suite · Mix any models · Auto delegate tasks

项目地址:https://gitcode.com/gh_mirrors/oh/oh-my-opencode-slim
点击查看免费下载

相关推荐

上一篇:PaddleSpeech python_kaldi_features 深度解析:与 Kaldi compute-mfcc-feats 结果一致的 Python 特征提取实现
下一篇:AWTK跨平台GUI开发终极指南:5步掌握SDL2桌面应用构建

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询