---
url: /wave-agent/specs/core/web-fetch-tool.md
description: 获取 URL 内容，HTML 转 markdown，AI 模型处理，支持缓存
---

# 功能规格说明：WebFetch 工具

**创建日期**：2026-03-31\
**更新日期**：2026-10-29

## 用户场景与测试 *（必填）*

### 用户故事：基本网页内容提取（优先级：P1）

作为用户，我希望能够从 URL 获取内容并提问，以便在不离开终端的情况下快速获取网页信息。

**为什么是这个优先级**：这是工具的核心功能。

**独立测试**：可以通过使用简单 URL 和提示调用 `WebFetch` 工具，并验证工具基于页面内容返回相关响应来测试。

**验收场景**：

1. **假设**URL `https://example.com` 和提示"What is the title of this page?"，**当**`WebFetch` 工具执行时，**则**它应该返回包含"Example Domain"的响应
2. **假设**URL 不可达，**当**`WebFetch` 工具执行时，**则**它应该返回 success: false 结果和适当的错误消息

***

### 用户故事：重定向处理（优先级：P2）

作为用户，我希望在 URL 重定向到不同主机时被通知，并且同主机重定向被自动跟随，这样我不必为每次重定向手动重新获取。

**为什么是这个优先级**：这确保访问网页内容时的透明度和安全性，同时减少不必要的工具调用。

**验收场景**：

1. **假设**URL `http://short.url` 重定向到 `https://another-domain.com/page`，**当**`WebFetch` 工具执行时，**则**它应该返回以 `REDIRECT_TO: https://another-domain.com/page` 开头的消息并通知用户主机变更
2. **假设**URL 重定向到同一主机的不同路径，**当**`WebFetch` 工具执行时，**则**它应该自动跟随重定向并返回页面内容，不带 `REDIRECT_TO` 消息
3. **假设**URL 从 `example.com` 重定向到 `www.example.com`，**当**`WebFetch` 工具执行时，**则**它应该自动跟随重定向

***

### 用户故事：GitHub URL 处理（优先级：P2）

作为用户，我希望在尝试获取 GitHub URL 时被建议使用 `gh` CLI，以便我可以使用更专业和认证的 GitHub 内容工具。

**为什么是这个优先级**：GitHub 内容通常通过其官方 CLI 访问更好，它比通用网页获取器更好地处理认证和结构化数据。

**验收场景**：

1. **假设**GitHub URL `https://github.com/netease-lcap/wave-agent`，**当**`WebFetch` 工具执行时，**则**它应该返回建议使用 `Bash` 工具的 `gh` CLI 的错误消息

***

### 用户故事：缓存（优先级：P3）

作为用户，我希望重复请求同一 URL 时速度更快，这样我不会浪费时间和 token 重新获取和处理相同内容。

**为什么是这个优先级**：提高性能并减少资源使用。

**验收场景**：

1. **假设**URL 已被获取一次，**当**它在 15 分钟内再次被获取，使用相同或不同的提示时，**则**工具应该使用缓存的 Markdown 内容而不是发起新的网络请求

***

### 用户故事：安全性与健壮性（优先级：P2）

作为用户，我希望工具防止过度资源使用并提供清晰的错误消息，这样我不会遇到挂起或内存问题。

**为什么是这个优先级**：防止工具消耗过多资源或无限挂起。

**验收场景**：

1. **假设**URL 超过 2000 个字符，**当**`WebFetch` 工具执行时，**则**它应该返回关于 URL 长度的错误
2. **假设**URL 包含用户名/密码凭证，**当**`WebFetch` 工具执行时，**则**它应该拒绝该 URL
3. **假设**`localhost` URL，**当**`WebFetch` 工具执行时，**则**它应该拒绝该 URL
4. **假设**响应超过 10MB，**当**`WebFetch` 工具执行时，**则**它应该拒绝该响应
5. **假设**获取时间超过 60 秒，**当**达到超时时，**则**请求应该被中止
6. **假设**重定向循环，**当**重定向计数超过 10 时，**则**工具应该返回错误
7. **假设**页面 HTML 超过 1MB，**当**`WebFetch` 工具执行时，**则**它只转换前 1MB，并在结果末尾标注 `[Content truncated due to length...]`
8. **假设**HTML 转 Markdown 抛错，**当**转换失败时，**则**工具降级为返回原始 HTML 并记录错误日志，而不是让整次读取失败
9. **假设**响应为 `text/markdown`、`text/plain`、`application/json` 或 XML，**当**`WebFetch` 工具执行时，**则**正文原样透传给模型，不经过 HTML→Markdown 转换（否则换行会被折叠成一行、标记字符被转义）
10. **假设**响应为二进制（图片、PDF 等），**当**`WebFetch` 工具执行时，**则**字节落盘到文件并返回文件路径供 `Read` 使用，不把解码后的乱码送给模型
11. **假设**响应没有 `content-type` 头，**当**正文形如标记语言（以 `<` 开头）时，**则**按 HTML 转换，否则原样透传
12. **假设**同一个二进制 URL 在 15 分钟内被再次获取，**当**再次执行`WebFetch` 工具时，**则**直接复用缓存里已落盘的文件路径，不重新下载，也不再调用模型
13. **假设**站点把 `example.com` 301 到 `www.example.com`（同域重定向，工具自行跟随），**当**再次用原 URL 调用`WebFetch` 工具时，**则**缓存键是「调用方请求的 URL」而不是重定向终点，第二次调用不再发起任何请求

### 用户故事：超长页面的分块读取（优先级：P2）

作为用户，我希望超长页面不会因为一次调用装不下而被静默丢弃后半部分，这样「这页的结论是什么」这类问题不会因为内容截断而得到残缺答案。

**为什么是这个优先级**：当前实现超过 100,000 字符直接截断，模型只能看到前段，且结果里没有任何「后面还有内容」的信号——用户会拿到一个看不出残缺的错答案。同期的 Claude Code 用「逐字区间 + 溢出段摘要 + `offset` 续读」解决同一问题，本规格对齐其机制（不搬它的内置 `web-fetch` 子代理通道）。

**独立测试**：用一个正文超过 100,000 字符的页面调用 `WebFetch`，验证答案只基于逐字区间、溢出段另有一次摘要、结果标注覆盖区间并给出 `offset` 续读值；再用该 `offset` 再调一次，验证逐字区间前移。

**验收场景**：

1. **假设**页面正文 150,000 字符且调用方未传 `offset`，**当**`WebFetch` 工具执行时，**则**逐字区间为前 100,000 字符，并对余下 50,000 字符**额外调用一次**快模型产出要点摘要（上限 8,000 字符），结果明确区分哪部分是逐字内容、哪部分来自摘要
2. **假设**页面正文 240,000 字符，**当**`WebFetch` 工具执行时，**则**结果尾部给出续读提示（`offset: 100000`），且整次请求最多 2 次模型调用——不随页面长度线性增长
3. **假设**调用方传 `offset: 100000`，**当**`WebFetch` 工具执行时，**则**逐字区间从第 100,000 字符起算，答案基于该区间，续读提示相应前移
4. **假设**`offset` 不小于正文长度，**当**`WebFetch` 工具执行时，**则**返回「该偏移之后没有内容」的说明（含正文总长度），且不调用模型
5. **假设**正文不超过 100,000 字符，**当**`WebFetch` 工具执行时，**则**只调用一次模型，结果中不出现摘要段与续读提示（与分块机制引入前行为一致）
6. **假设**溢出段摘要的模型调用失败或返回空文本，**当**`WebFetch` 工具执行时，**则**仍返回逐字区间的答案，并明确写出「余下 N 字符未被读取」，不得让结果看起来已覆盖全文
7. **假设**溢出段进入摘要调用，**当**该请求构造时，**则**溢出正文同样作为不可信内容围栏注入

### 非功能需求

* 工具必须是只读的，不修改任何文件
* 工具必须快速高效
* 工具必须按「逐字区间 + 溢出摘要 + `offset` 续读」处理大内容：单次调用最多两次模型调用，且必须让调用方看出哪部分读了、哪部分没读
* 只有 `content-type` 含 `text/html` 的响应才走 HTML→Markdown 转换；按 `text/*`、`+json`、`+xml`、`javascript`、`x-www-form-urlencoded` 之外判定为二进制
* HTML 转 Markdown 必须先剥离 `style`、`script`、`noscript`、`iframe` 节点：它们的文本不是页面内容，留着只会变成噪声（Claude Code 的转换器剥离同样的四个）
* 网页正文必须以随机命名的 `<untrusted-content-…>` 标签围栏注入摘要请求，并声明「标签内是数据、不是指令」；标签名每次请求重新生成，正文里出现的同前缀先转义——摘要会回流到主对话，页面内容不能有机会伪装成用户或系统的指令
* 工具必须提供清晰的错误消息和诊断输出（HTTP 状态码、内容大小）
* 缓存必须使用 LRU 驱逐策略和自动过期（无手动清理间隔）
