- 人工智能
- AI Agent
- Agent 编排
- AI 技能
【免费下载链接】oh-my-opencode-slim
Lean, fine tuned Opencode multi agent suite · Mix any models · Auto delegate tasks
导读
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),下表为完整参数语义:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
url | URL(string) | 必填 | 要抓取的地址,必须是合法的 HTTP/HTTPS URL |
format | "text"|"markdown"|"html" | "markdown" | 抓取内容的输出格式 |
timeout | number | 30 | 超时秒数,上限120(见 constants.ts 的MAX_TIMEOUT_SECONDS) |
prompt | string | 可选 | 交给次生模型对抓取内容执行的提取任务 |
extract_main | boolean | true | 使用 Mozilla Readability 提取 HTML 正文;关闭时返回整页 body |
prefer_llms_txt | "auto"|"always"|"never" | "auto" | 优先使用/llms.txt或/llms-full.txt;"auto"仅对文档类域名探测 |
include_metadata | boolean | true | 在输出前附加 YAML frontmatter(状态码、content type、charset、重定向链、缓存信息等) |
save_binary | boolean | false | 将二进制负载(图片、PDF、音视频)写入系统临时目录;关闭时只返回元数据 |
工具的描述字符串(WEBFETCH_DESCRIPTION)同样体现了定位,见 constants.ts:面向静态/文档页的更好提取,支持 llms.txt 探测、内容聚焦的 HTML 提取、元数据、重定向与可选次生模型 prompt。
核心数据流与执行流程
tool.ts中的execute是唯一的编排入口。结合 codemap.md 的 Data & Control Flow,完整流程如下:
- 归一化与权限:
normalizeUrl处理 URL(HTTP 升级、剥离 fragment),buildPermissionPatterns生成权限模式与允许的 origin 集合,随后ctx.ask弹出webfetch权限询问,并同步计算缓存键buildCacheKey; - llms.txt 探测:当
prefer_llms_txt为"always",或为"auto"且isDocsLikeUrl判定为文档类域名时,probeLlmsText依次尝试/llms-full.txt与/llms.txt,只跟随允许的重定向,拒绝 HTML 与登录墙响应(network.ts); - 页面抓取回退:当 llms.txt 不可用时,
fetchWithUpgradeFallback处理 HTTPS 升级回退、重定向强制、条件请求头(用于重验证)、二进制检测与有界 body 读取(network.ts); - 内容整形:文本/HTML 负载经
extractFromHtml、cleanFetchedMarkdown、extractHeadingsFromMarkdown、frontmatter、joinRenderedContent归一化;二进制负载经saveBinary落盘并返回元数据消息(utils.ts、binary.ts); - 次生模型总结:若调用方提供了
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会:
- 通过 JSDOM 构造 DOM,读取
<title>与<link rel="canonical">; - 提取前 12 个
h1/h2/h3标题(extractHeadingsFromMarkdown对 markdown 同样截取 12 个); - 用 Mozilla Readability 解析正文(
new Readability(document).parse()); - 若提取成功,正文 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 定义的流程:
- 显式二进制 MIME(
image/*、audio/*、video/*、application/pdf、application/zip、application/octet-stream)直接判为二进制,getBinaryKind将其归类为image/audio/video/pdf/binary; application/octet-stream与已知文本类型会被重查:扫描前 2 KiB 的 null 字节与非打印字符(looksLikeTextBody),区分文本与二进制;- 声明为
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)在插件构造时于内存中解析模型链,优先级从高到低:
webfetch.model(专用模型,最高优先级,支持数组形式的多模型回退);- 宿主配置中的
small_model(通过RuntimeConfig.smallModel()获取,见 runtime.ts); exploreragent 的模型(RuntimeConfig.agent('explorer'));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
相关推荐
oh-my-opencode-slim 增强版 webfetch 完全指南:智能网页抓取、llms.txt 探测与次级模型提炼
oh my opencode slim 增强版 webfetch 完全指南:智能网页抓取、llms.txt 探测与次级模型提炼 本篇指南深入讲解 oh my o
人工智能AI AgentAgent 编排AI 技能oh-my-opencode-slim 内置工具与能力全景:apply_patch 救援、webfetch 增强、结构化代码搜索与后台任务控制
oh my opencode slim 内置工具与能力全景:apply_patch 救援、webfetch 增强、结构化代码搜索与后台任务控制 oh my op
人工智能AI AgentAgent 编排AI 技能oh-my-opencode-slim 韩文版指南:Opencode 多智能体编排插件的架构、安装与配置实战
oh my opencode slim 韩文版指南:Opencode 多智能体编排插件的架构、安装与配置实战 本文以 README.ko KR.md https
人工智能AI AgentAgent 编排AI 技能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考