From 5278ce1f3a8cdec5f5e253816f463d0fa18c55ed Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Fri, 3 Apr 2026 10:56:52 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9E=E4=B8=A4=E4=BB=BD?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/features/web-search-tool.md | 186 +++++++++++++++++++++++++++++++ docs/internals/sentry-setup.mdx | 106 ++++++++++++++++++ mint.json | 2 + 3 files changed, 294 insertions(+) create mode 100644 docs/features/web-search-tool.md create mode 100644 docs/internals/sentry-setup.mdx diff --git a/docs/features/web-search-tool.md b/docs/features/web-search-tool.md new file mode 100644 index 000000000..84802cc2b --- /dev/null +++ b/docs/features/web-search-tool.md @@ -0,0 +1,186 @@ +# WEB_SEARCH_TOOL — 网页搜索工具 + +> 实现状态:适配器架构完成,Bing 适配器为当前默认后端 +> 引用数:核心工具,无 feature flag 门控(始终启用) + +## 一、功能概述 + +WebSearchTool 让模型可以搜索互联网获取最新信息。原始实现仅支持 Anthropic API 服务端搜索(`web_search_20250305` server tool),在第三方代理端点下不可用。现已重构为适配器架构,新增 Bing 搜索页面解析作为 fallback,确保任何 API 端点都能使用搜索功能。 + +## 二、实现架构 + +### 2.1 适配器模式 + +``` +WebSearchTool.call() + │ + ▼ + createAdapter() ← 适配器工厂 + │ + ├── ApiSearchAdapter — Anthropic 官方 API 服务端搜索 + │ └── 使用 web_search_20250305 server tool + │ 通过 queryModelWithStreaming 二次调用 API + │ + └── BingSearchAdapter — Bing HTML 抓取 + 正则提取(当前默认) + └── 直接抓取 Bing 搜索页 HTML + 正则提取 b_algo 块中的标题/URL/摘要 +``` + +### 2.2 模块结构 + +| 模块 | 文件 | 说明 | +|------|------|------| +| 工具入口 | `src/tools/WebSearchTool/WebSearchTool.ts` | `buildTool()` 定义:schema、权限、执行、输出格式化 | +| 工具 prompt | `src/tools/WebSearchTool/prompt.ts` | 搜索工具的系统提示词 | +| UI 渲染 | `src/tools/WebSearchTool/UI.tsx` | 搜索结果的终端渲染组件 | +| 适配器接口 | `src/tools/WebSearchTool/adapters/types.ts` | `WebSearchAdapter` 接口、`SearchResult`/`SearchOptions`/`SearchProgress` 类型 | +| 适配器工厂 | `src/tools/WebSearchTool/adapters/index.ts` | `createAdapter()` 工厂函数,选择后端 | +| API 适配器 | `src/tools/WebSearchTool/adapters/apiAdapter.ts` | 封装原有 `queryModelWithStreaming` 逻辑,使用 server tool | +| Bing 适配器 | `src/tools/WebSearchTool/adapters/bingAdapter.ts` | Bing HTML 抓取 + 正则解析 | +| 单元测试 | `src/tools/WebSearchTool/__tests__/bingAdapter.test.ts` | 32 个测试用例 | +| 集成测试 | `src/tools/WebSearchTool/__tests__/bingAdapter.integration.ts` | 真实网络请求验证 | + +### 2.3 数据流 + +``` +模型调用 WebSearchTool(query, allowed_domains, blocked_domains) + │ + ▼ + validateInput() — 校验 query 非空、allowed/block 不共存 + │ + ▼ + createAdapter() → BingSearchAdapter(当前硬编码) + │ + ▼ + adapter.search(query, { allowedDomains, blockedDomains, signal, onProgress }) + │ + ├── onProgress({ type: 'query_update', query }) + │ + ├── axios.get(bing.com/search?q=...&setmkt=en-US) + │ └── 13 个 Edge 浏览器请求头 + │ + ├── extractBingResults(html) — 正则提取
  • 块 + │ ├── resolveBingUrl() — 解码 base64 重定向 URL + │ ├── extractSnippet() — 三级降级摘要提取 + │ └── decodeHtmlEntities() — he.decode + │ + ├── 客户端域名过滤 (allowedDomains / blockedDomains) + │ + ├── onProgress({ type: 'search_results_received', resultCount }) + │ + ▼ + 格式化为 markdown 链接列表返回给模型 +``` + +## 三、Bing 适配器技术细节 + +### 3.1 反爬绕过 + +使用 13 个 Edge 浏览器请求头(含 `Sec-Ch-Ua`、`Sec-Fetch-*` 等),避免 Bing 返回 JS 渲染的空页面: + +```typescript +const BROWSER_HEADERS = { + 'User-Agent': '...Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0', + 'Sec-Ch-Ua': '"Microsoft Edge";v="131", "Chromium";v="131", ...', + 'Sec-Fetch-Dest': 'document', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'none', + 'Sec-Fetch-User': '?1', + // ... 共 13 个标头 +} +``` + +`setmkt=en-US` 参数强制美式英语市场,避免 IP 地理定位导致区域化结果。 + +### 3.2 URL 解码(`resolveBingUrl()`) + +Bing 返回的重定向 URL 格式:`bing.com/ck/a?...&u=a1aHR0cHM6Ly9...` + +- `u` 参数前 2 字符为协议前缀:`a1` = https,`a0` = http +- 剩余部分为 base64url 编码的真实 URL +- Bing 内部链接和相对路径被过滤返回 `undefined` + +### 3.3 摘要提取(`extractSnippet()`) + +三级降级策略: + +1. `

    ` — Bing 的搜索摘要段落 +2. `

    ` 内的 `

    ` — 备选摘要位置 +3. `

    ` 直接文本 — 最终 fallback + +### 3.4 域名过滤 + +客户端侧实现,支持子域名匹配: +- `allowedDomains`:白名单,结果域名必须匹配列表中的某项(含子域名) +- `blockedDomains`:黑名单,匹配的结果被过滤 +- 两者不可同时使用(`validateInput` 校验) + +## 四、适配器选择逻辑 + +当前 `createAdapter()` 硬编码返回 `BingSearchAdapter`,原逻辑已注释保留: + +```typescript +export function createAdapter(): WebSearchAdapter { + return new BingSearchAdapter() + // 注释保留的选择逻辑: + // 1. WEB_SEARCH_ADAPTER 环境变量强制指定 api|bing + // 2. isFirstPartyAnthropicBaseUrl() → API 适配器 + // 3. 第三方端点 → Bing 适配器 +} +``` + +恢复自动选择:取消 `index.ts` 中的注释即可。 + +## 五、接口定义 + +### WebSearchAdapter + +```typescript +interface WebSearchAdapter { + search(query: string, options: SearchOptions): Promise +} + +interface SearchResult { + title: string + url: string + snippet?: string +} + +interface SearchOptions { + allowedDomains?: string[] + blockedDomains?: string[] + signal?: AbortSignal + onProgress?: (progress: SearchProgress) => void +} + +interface SearchProgress { + type: 'query_update' | 'search_results_received' + query?: string + resultCount?: number +} +``` + +### 工具 Input Schema + +```typescript +{ + query: string // 搜索关键词,最少 2 字符 + allowed_domains?: string[] // 域名白名单 + blocked_domains?: string[] // 域名黑名单 +} +``` + +## 六、文件索引 + +| 文件 | 职责 | +|------|------| +| `src/tools/WebSearchTool/WebSearchTool.ts` | 工具定义入口 | +| `src/tools/WebSearchTool/prompt.ts` | 搜索工具 prompt | +| `src/tools/WebSearchTool/UI.tsx` | 终端 UI 渲染 | +| `src/tools/WebSearchTool/adapters/types.ts` | 适配器接口 | +| `src/tools/WebSearchTool/adapters/index.ts` | 适配器工厂 | +| `src/tools/WebSearchTool/adapters/apiAdapter.ts` | API 服务端搜索适配器 | +| `src/tools/WebSearchTool/adapters/bingAdapter.ts` | Bing HTML 解析适配器 | +| `src/tools/WebSearchTool/__tests__/bingAdapter.test.ts` | 单元测试 (32 cases) | +| `src/tools/WebSearchTool/__tests__/bingAdapter.integration.ts` | 集成测试 | +| `src/tools.ts` | 工具注册 | diff --git a/docs/internals/sentry-setup.mdx b/docs/internals/sentry-setup.mdx new file mode 100644 index 000000000..a58a59d41 --- /dev/null +++ b/docs/internals/sentry-setup.mdx @@ -0,0 +1,106 @@ +--- +title: "自定义 Sentry 错误上报配置" +description: "通过环境变量连接自托管或 Cloud Sentry,实现 CLI 运行时的错误捕获与上报。不配置则完全静默。" +keywords: ["sentry", "错误上报", "监控", "DSN", "自托管"] +--- + +## 概述 + +Claude Code 支持通过 Sentry 捕获运行时异常并上报到你自己指定的 Sentry 实例。 + +- **配置了 `SENTRY_DSN`**:自动初始化 Sentry SDK,捕获未处理异常和关键错误 +- **未配置**:所有 Sentry 调用均为 no-op,零开销 + +## 环境变量 + +| 变量 | 必填 | 说明 | +|---|---|---| +| `SENTRY_DSN` | 是 | Sentry 项目 DSN,如 `https://xxx@o123456.ingest.sentry.io/789` | + +只需要这一个变量,设置后即启用。 + +## 使用方式 + +### 自托管 Sentry + +```bash +SENTRY_DSN=https://public_key@your-sentry.example.com/123 \ +bun run dev +``` + +### Sentry Cloud (SaaS) + +```bash +SENTRY_DSN=https://public_key@o123456.ingest.sentry.io/789 \ +bun run dev +``` + +### 不使用 Sentry(默认行为) + +```bash +bun run dev +# SENTRY_DSN 未设置,所有 sentry 函数为 no-op +``` + +## Sentry 服务端配置 + +### 步骤 + +1. **部署 Sentry 实例**(Docker 自托管 或 使用 [sentry.io](https://sentry.io) Cloud) +2. **创建 Project**,选择 **Node.js** 平台 +3. 获取项目的 **DSN**(Settings → Projects → Client Keys → DSN) +4. 将 DSN 设置为 `SENTRY_DSN` 环境变量 + +## 功能详情 + +### 错误捕获 + +- **自动捕获**:`SentryErrorBoundary` 包裹关键 React 组件,捕获渲染错误 +- **手动上报**:`errorLogSink` 在写入错误日志时同步上报到 Sentry +- **优雅关闭**:进程退出时 `closeSentry()` 确保事件发送完毕(2s 超时) + +### 安全过滤 + +`beforeSend` 钩子会自动剥离以下敏感 header: + +- `authorization` +- `x-api-key` +- `cookie` +- `set-cookie` + +### 忽略的错误类型 + +以下错误模式会被忽略,不会上报: + +| 错误 | 原因 | +|---|---| +| `ECONNREFUSED` / `ECONNRESET` / `ENOTFOUND` / `ETIMEDOUT` | 网络不可达,不可操作 | +| `AbortError` / `The user aborted a request` | 用户主动取消 | +| `CancelError` | 交互式取消信号 | + +### 其他配置 + +- **采样率**:`sampleRate: 1.0`(捕获全部错误事件) +- **面包屑上限**:`maxBreadcrumbs: 20`(控制 payload 体积) +- **性能事务**:已关闭(`beforeSendTransaction` 返回 `null`),仅上报错误 + +## API + +| 函数 | 说明 | +|---|---| +| `initSentry()` | 初始化 SDK,在 `src/entrypoints/init.ts` 中自动调用 | +| `captureException(error, context?)` | 手动上报异常,可附加额外上下文 | +| `setTag(key, value)` | 设置标签,用于 Sentry 面板分组过滤 | +| `setUser({ id, email, username })` | 设置用户上下文,用于错误归因 | +| `closeSentry(timeoutMs?)` | 刷出队列并关闭客户端,进程退出时调用 | +| `isSentryInitialized()` | 检查是否已初始化 | + +## 实现文件 + +| 文件 | 说明 | +|---|---| +| `src/utils/sentry.ts` | 核心 SDK 初始化与封装 | +| `src/components/SentryErrorBoundary.ts` | React Error Boundary 组件 | +| `src/utils/errorLogSink.ts` | 错误日志 sink,集成 `captureException` | +| `src/utils/gracefulShutdown.ts` | 优雅退出,调用 `closeSentry()` | +| `src/entrypoints/init.ts` | 启动时调用 `initSentry()` | diff --git a/mint.json b/mint.json index 3f2ed8b64..27e2f39cd 100644 --- a/mint.json +++ b/mint.json @@ -106,6 +106,8 @@ "docs/internals/three-tier-gating", "docs/internals/feature-flags", "docs/internals/growthbook-ab-testing", + "docs/internals/growthbook-adapter", + "docs/internals/sentry-setup", "docs/internals/hidden-features", "docs/internals/ant-only-world" ]