# @citisen/dsh-github-accelerator

[English](README.md) | 中文

给 [DeepSeek Harness](https://github.com/deepseek-ai)（`dsh`）用的 bundle：当 GitHub 是通过
**hosts 加速器**（SteamCommunity302、Watt Toolkit / Steam++，或任何把域名指到 `127.0.0.1`、
由本机反向代理提供服务的工具）访问时，让 `web_fetch` 工具照常工作。

没有它，抓 GitHub 会直接失败：

```
Error: URL hostname "github.com" resolves to a non-public IP address
```

## 为什么会失败

`dsh` 内置的本地抓取 provider（`http`）会先解析域名，只要结果里有非公网地址就整组拒绝，并把
校验过的地址钉进连接。这对匿名抓取器是正确的默认值，却正好和 hosts 加速器冲突——`hosts` 里
写着 `127.0.0.1 github.com`，域名「必然」解析到非公网地址。

紧接着还有第二道坎。做 TLS 拦截的加速器会用自己的 CA 现场签发证书，这个 CA 装在**操作系统**
的证书库里。浏览器和 `curl` 读得到，Node 读不到；所以就算绕过地址校验，请求仍会死于
`UNABLE_TO_GET_ISSUER_CERT_LOCALLY`。

## 这个插件做了什么

它额外注册一个抓取 provider（id `http-accelerated`），只多做**一个**判断：请求的主机名在不在
白名单里。

- **在白名单里**（默认就是 GitHub）：直连系统解析出来的地址（于是走加速器的本机反代），并把
  操作系统的根证书加入 TLS 信任锚（于是接受加速器的证书）。
- **不在白名单里**：原样交给内置 `http` provider。公网地址校验、重定向策略、字节上限、超时和
  错误码全部保持原样。

所以普通抓取行为不变；不在白名单里的内网地址（比如 `http://127.0.0.1:8080/`）仍然会被内置
provider 拦下。

## 环境要求

- 带 `web` 服务和内置 `http` 抓取 provider 的 DeepSeek Harness（任何自带 profile 都满足）。
  已在 dsh `0.2.1-rc.2`（desktop，Electron 44 / Node 24.21）上验证。
- `dsh` 宿主里的 Node ≥ 24.9 才能读取操作系统证书库（`tls.getCACertificates`）。更老的运行时
  会退回 Node 自带的根证书，此时加速器的 CA 只能靠 `insecureTLS: true` 兜底。

## 安装

Web 界面：**插件 → 安装**，填 `@citisen/dsh-github-accelerator`，然后启用。或者用命令行：

```sh
dsh plugin --profile desktop add @citisen/dsh-github-accelerator
```

直接从 GitHub 装同一个包：

```sh
dsh plugin --profile desktop add github:caparisen/dsh-github-accelerator
```

`dsh plugin` 会在 profile 目录里转发给 pnpm，然后对 `dsh.profile.bundles` 做一次对账；本包声明了
`dsh.bundle`，所以启用时不需要手工改 YAML。

用 CLI 跑浏览器版 profile 时把 `--profile desktop` 换成 `--profile web`。`dsh` 会热加载 profile，
但如果没有生效，就完整退出并重新打开应用。

### 从本地目录安装（开发用）

```sh
cd "$DSH_HOME/profiles/desktop"
pnpm add /abs/path/to/dsh-github-accelerator
```

然后手工把 `"@citisen/dsh-github-accelerator"` 加进该 profile `package.json` 的
`dsh.profile.bundles`。Windows 上跨盘符的链接在 pnpm 里不可靠：它可能被解析成一个不存在的路径，
随后 bundle 对账会**静默跳过**这个包。这种情况请自己修链接（`rmdir
node_modules\@citisen\dsh-github-accelerator`，再用 `mklink /J` 指向真实目录），并手工加
bundle 条目。

## 配置

bundle 自带一份可用的默认值（GitHub 相关域名）。要改就在 profile 的 `cordis.patch.yml` 里覆盖
这一行——profile 层优先级高于 bundle 层：

```yaml
- id: web-fetch-accelerated
  name: '@citisen/dsh-github-accelerator'
  config:
    hosts:
      - github.com
      - '*.github.com'
      - '*.githubusercontent.com'
      - huggingface.co
      - '*.huggingface.co'
```

| 键 | 默认值 | 说明 |
| --- | --- | --- |
| `hosts` | GitHub 全家桶 | 允许解析到非公网地址的主机。`*.example.com` 同时匹配 `example.com` 和所有子域；`*` 匹配一切（不建议） |
| `trustSystemCertificates` | `true` | 给白名单主机的 TLS 握手加上操作系统证书库。本地加速器的 CA 就是靠它被接受 |
| `insecureTLS` | `false` | 对白名单主机跳过证书校验。仅在 CA 装不进证书库时当最后一招 |
| `timeoutMs` | `30000` | 单次抓取的截止时间（含读 body） |
| `maxResponseBytes` | `5000000` | 响应体字节上限；声明超长会报 `WEB_FETCH_TOO_LARGE` |
| `maxBodyChars` | `100000` | 解码后字符上限，超出截断并标记 |
| `maxRedirects` | `5` | 同源重定向跳数上限 |
| `userAgent` | dsh 默认 UA | 请求头 |
| `providerId` / `baseProviderId` | `http-accelerated` / `http` | provider id，除非确定要改 |
| `selfTestUrl` | 空 | 填一个 URL，插件启动时通过 `ctx.web.fetch` 真抓一次并打日志，相当于一行健康检查 |

加速器通常还代理了 `huggingface.co`、`cdn.jsdelivr.net`、`registry-1.docker.io` 等一大批域名
（看 `hosts` 里那些 `#S302` 注释）。只加你真的需要、并且愿意信任其解析结果的域名——放进白名单
就等于关掉该域名的公网地址校验。

## 验证

配上 `selfTestUrl`，启动时看宿主日志：

```
[web-fetch-accelerated] registered provider "http-accelerated" for 5 host pattern(s): github.com, *.github.com, ...
[web-fetch-accelerated] self-test https://github.com/ -> HTTP 200 html 57684 chars in 812 ms
```

之后 `web_fetch` 抓 `https://github.com/` 应返回页面正文，抓 `https://api.github.com/rate_limit`
应返回 JSON。

## 安全说明

- 白名单内的主机不再做「必须是公网地址」的校验。这正是插件的作用，也是它的代价：如果某个白名单
  域名被指到了内网服务，`web_fetch` 会真的去访问。名单越小越好。
- 白名单内的 TLS 信任锚包含操作系统信任的一切，与浏览器一致，但比 Node 自带的根证书集合更宽。
- 白名单之外的请求仍由内置 provider 处理，规则完全不变。
- 只影响模型侧的 `web_fetch`。shell 里的 `git`、`curl`、包管理器走各自的配置，本来就不受这条
  报错影响。

## 排错

| 现象 | 原因与处理 |
| --- | --- |
| 仍报 `resolves to a non-public IP address` | provider 没被选中，或主机不在白名单。看启动日志那一行；确认 `web` 的 `fetchProvider` 是 `http-accelerated`，bundle 已启用 |
| `the TLS certificate for … is not trusted` | 加速器的 CA 不在系统证书库里，或运行时低于 Node 24.9。重装加速器的证书，或设 `insecureTLS: true` |
| `web fetch failed: … ECONNREFUSED … may not be running` | 加速器没在运行，或已不再接管该域名 |
| `WEB_PROVIDER_CONFIGURED_MISSING` | bundle 启用了但包没装上（或文件丢了）。重新安装 |
| `WEB_PROVIDER_UNAVAILABLE` / `not registered` | 内置的 `http` provider 被禁用了。本插件是委托方，不能单独存在 |

## 卸载

在插件页禁用或移除 bundle，或者：

```sh
dsh plugin --profile desktop remove @citisen/dsh-github-accelerator
```

然后重启。`web` 的 `fetchProvider` 会回到 `dsh-base` 给的 `http`，原来的报错也随之回来。

## 实现原理

`cordis.patch.yml` 把 `web` 服务钉到 `http-accelerated` 并插入一行插件。插件的 `apply` 注册一个
`WebFetchProvider`：

1. 解析 URL，用 `hosts` 匹配主机名；
2. 命中时用 `node:https` / `node:http` 直连系统解析结果，跟随同源重定向，并套用与内置 provider
   相同的字节上限、字符集解码和 `WEB_*` 错误码；
3. 未命中时调用注册在 `http` 下的 provider，原样返回它的结果。

provider 是纯 ESM 模块，不 import 任何 `@deepseek-ai/*`，因此可以脱离 `dsh` 单独加载和测试。

## 开发

```sh
npm test              # 本地 HTTP 服务器：重定向、大小上限、字符集、委托、错误分类
npm run test:network  # 额外真抓 https://github.com/ 和 https://api.github.com/
```

`npm test` 只需要 Node ≥ 24；`npm run test:network` 需要机器上已经有能到 GitHub 的路径（比如本机
加速器）。

## License

MIT
