Bundle
dsh-net-proxy-plugin
Fallback network proxy for DeepSeek Harness: detects system proxies, probes overseas connectivity (Google/GitHub), routes dsh outbound HTTP through a working local proxy when direct access fails.
- Source
- minatoAI
- License
- MIT
- Updated
- Updated 2 days ago
Readme
[English](README.en.md) | **简体中文**
# dsh-net-proxy-plugin
DeepSeek Harness 的**本地网络代理兜底插件**([bundle](https://github.com/deepseek-ai/deepseek-harness/blob/main/docs/user/develop/basic/publish.md)):自动扫描系统里已有的网络代理(环境变量、Windows 系统代理、macOS/Linux 系统代理、常见本地代理端口),探测海外网站(默认 Google 204 与 GitHub)的连通性;当**直连不通**而某个本地代理可用时,把该代理接入正在运行的 dsh —— 进程内所有 fetch 走该代理,并同步设置 `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY` 环境变量,让 agent 启动的 git、pnpm 等子进程同样可用代理。
这是**兜底方案**,不是默认接管:没有可用代理或直连正常时,dsh 的行为与未安装本插件完全一致;卸载时精确恢复环境变量与全局 dispatcher。
## 更新日志
> 此处仅展示最新版本,完整版本历史见 [change-log.md](./change-log.md)。
### 0.1.0(2026-08-31)
- **首发** 即插即用,无需构建安装(仓库已提交构建产物 `lib/`)。
## 功能
- **自动发现候选代理**(优先级去重):`extraProxies` 配置 → 环境变量(`HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY` 及小写)→ 系统代理(Windows WinINet 注册表、macOS `scutil --proxy`、Linux GNOME gsettings)→ 本地监听端口(默认 7890/7897/7891/10809/10808/8888/8080,http 与 socks5 两种都试)。
- **连通性探测**:直连 + 逐候选,对每个目标发一次 GET;返回 HTTP 状态码 < 500 视为可达。
- **自动决策**(`activation: auto` 默认):有代理能全量到达目标且直连存在失败目标 → 激活该代理;`activation: always` 则只要有可用代理就激活。默认每 5 分钟复检一次(`reprobeIntervalMs: 0` 关闭),VPN 断开自动切回直连、重开后再次激活;`/net-proxy refresh` 可立即重检。
- **激活范围**:进程内所有 fetch(undici 全局 dispatcher 换成按主机名路由的 `RoutingDispatcher`,`bypassHosts` 内的主机仍直连;覆盖 LLM、web_search 等)+ 子进程(标准代理环境变量 + `NO_PROXY`)。
- **可选的 `net-proxy` 提供方**:`web_fetch` 内置 `web-fetch-http` 提供方为安全起见自建连接、不走全局 fetch 代理;本插件提供语义一致的兼容提供方,可一键切换(见下)。
- **`/net-proxy` 命令**:Web UI 里查看插件当前状态,`/net-proxy refresh` 立即重检。
## 实测记录
开发期间在真实环境(Windows + VPN 系统代理 `http://127.0.0.1:51266`)验证:插件启动后检测到直连 GitHub 失败、系统代理全量可达 → 自动激活;随后模型首次 `web_fetch` 直连失败、重试经代理返回 `204` —— 正是「超时后重试走本地代理」的路径。dsh 日志输出 `dsh-net-proxy: probe done — direct blocked, working proxies: ..., active: http://127.0.0.1:51266`。
> 单机单次记录,结果受当天网络影响;不同机器/代理环境下以插件自身的探测输出为准。
## 安装
仓库地址:https://github.com/minatoAI/dsh-net-proxy-plugin
插件按 bundle 方式分发,用 `dsh plugin` 安装进 profile(从源码 checkout 运行时用 `pnpm dsh` 代替 `dsh`):
> 从 GitHub 安装。仓库已提交构建产物 `lib/`,无 build 脚本,**无需 allowBuilds 授权**
```sh
dsh plugin --profile web add github:minatoAI/dsh-net-proxy-plugin
```
> 更稳妥:固定到某个 commit,避免后续推送改变实际安装到的代码
```sh
dsh plugin --profile web add github:minatoAI/dsh-net-proxy-plugin#<commit-sha>
```
> 或本地文件夹安装(开发调试用,需先构建)
```sh
pnpm install
pnpm run build
dsh plugin --profile web add ./dsh-net-proxy-plugin
```
安装完成后**重启** dsh(新 bundle 在下次启动时生效):
```sh
dsh --profile web
```
安装时仅从 npm 拉取唯一运行时依赖 `undici`;`@deepseek-ai/cordis`、`@deepseek-ai/schemastery`、`@deepseek-ai/dsh-web` 是 peerDependencies,运行时从 dsh 安装目录解析,不需要额外安装。
## 使用
启动后代理检测自动进行。首次探测需要几秒钟(直连 + 各候选 × 目标,每个最多 `probeTimeoutMs`);此窗口内发起的一次直连请求可能失败,下一次重试就会命中已激活的代理。
- 激活成功:dsh 日志输出 `dsh-net-proxy: activated <url> (system, ...)` 与 `probe done` 摘要。
- Web UI 输入 `/net-proxy` 查看当前状态(激活的代理、来源、激活时间、直连可达性),`/net-proxy refresh` 立即重检。
## 配置
在 profile 的 `cordis.patch.yml` 里覆盖 `net-proxy` 行:
```yaml
- id: net-proxy
config:
targets: [https://www.google.com/generate_204, https://github.com]
probeTimeoutMs: 5000
reprobeIntervalMs: 300000
bypassHosts: [localhost, 127.0.0.1, ::1, api.deepseek.com, "*.deepseek.com"]
extraProxies: [http://127.0.0.1:7890]
probePorts: [7890, 7897, 10809]
activation: auto
```
| 字段 | 默认 | 说明 |
| --- | --- | --- |
| `enabled` | `true` | 总开关 |
| `targets` | `https://www.google.com/generate_204`, `https://github.com` | 连通性探测目标 |
| `probeTimeoutMs` | `5000` | 单次探测超时 |
| `reprobeIntervalMs` | `300000` | 周期复检间隔;`0` 关闭 |
| `bypassHosts` | `localhost`, `127.0.0.1`, `::1`, `api.deepseek.com`, `*.deepseek.com` | 激活后仍直连的主机(精确 / `*.domain` 通配 / `*`) |
| `extraProxies` | `[]` | 手动指定的代理,优先于自动发现 |
| `probePorts` | `7890, 7897, 7891, 10809, 10808, 8888, 8080` | 扫描的本地端口 |
| `activation` | `auto` | `auto`:直连失败才启用;`always`:有可用代理即启用 |
| `fetchTimeoutMs` | `30000` | `net-proxy` web 提供方超时 |
| `maxResponseBytes` | `5000000` | `net-proxy` web 提供方响应上限 |
| `maxRedirects` | `5` | `net-proxy` web 提供方同源重定向上限 |
## 让 web_fetch 工具也走代理
`web_fetch`(dsh 内置 `web-fetch-http` 提供方)为安全起见自建连接、固定公网 IP,不经过全局 fetch 代理。本插件提供语义一致的 `net-proxy` 提供方(`WEB_FETCH_TIMEOUT`/`WEB_ABORTED`/`WEB_REDIRECT_BLOCKED`/`WEB_BLOCKED_URL`/`WEB_FETCH_TOO_LARGE` 等错误码与内置一致;走代理时公网 IP 校验由你选用的代理承载)。在你的 profile 的 `cordis.patch.yml` 里改一行即可启用:
```yaml
- id: web
config:
searchProvider: deepseek-official
fetchProvider: net-proxy
```
不配置时 `web_fetch` 维持原有的直连安全路径,不受本插件影响。
## 网络与代理(中国大陆用户)
插件读取的是**系统里已有的代理**,不内置任何代理服务。VPN/代理开启时自动发现并复用;VPN 重启换了端口也会在下一次复检(默认 5 分钟)或 `/net-proxy refresh` 后自愈。请只使用你信任的本地代理 —— 代理会看到并转发你通过它的全部流量;插件**不会**修改 Windows 系统代理设置,仅影响 dsh 进程及其子进程的网络访问。
## 卸载
```sh
dsh plugin --profile web remove dsh-net-proxy-plugin
```
## 仓库结构
```
dsh-net-proxy-plugin/
├── package.json # manifest: "dsh": { "bundle": {"patch": ...} };构建产物 lib/ 随仓库提交
├── cordis.patch.yml # 组合层:单个行 net-proxy(name: dsh-net-proxy-plugin)
├── src/ # TypeScript 源码(NodeNext ESM,strict)
│ ├── index.ts # 插件主体:配置 Schema、探测调度、激活决策、提供方/命令注册、生命周期
│ ├── candidates.ts # 候选发现(配置→环境变量→系统代理→本地端口);系统代理读取(WinINet/scutil/gsettings)
│ ├── probe.ts # 直连/逐代理连通性探测
│ ├── dispatcher.ts # undici 代理 dispatcher 与按主机名路由的 RoutingDispatcher
│ ├── activate.ts # ProxyActivation:全局 dispatcher 与代理环境变量的激活/停用/切换恢复
│ ├── bypass.ts # 主机匹配(精确 / *.domain 通配)与 NO_PROXY 值生成
│ └── fetch-provider.ts # 可选的 net-proxy WebFetchProvider(同源重定向/大小上限/超时/错误码)
├── lib/ # 构建产物(已提交,GitHub 安装开箱即用)
├── tests/ # vitest:40 个用例(解析/路由/探测/激活/提供方/系统代理集成)
├── README.md # 简体中文说明(本文件)
├── README.en.md # English README
├── change-log.md # 完整版本历史(简体中文)
└── change-log.en.md # 完整版本历史(English)
```
## 开发说明
- 组合层遵循 dsh 约定:单个行 `net-proxy`,`name` 为精确包名 `dsh-net-proxy-plugin`(client-modules 扫描按行名定位根 manifest)。
- 插件 `inject` 为空:探测调度与激活不依赖任何服务,启动即工作;`web`/`commands` 为可选服务,通过 `ctx.inject` 注册提供方与命令 —— 组合里没有对应服务时相关功能自动不注册,不会造成 loader 启动挂起。
- 所有注册都是 effect(`ctx.effect`/`yield` 返回 disposer),生命周期 effect 负责清理复检定时器并 `activation.dispose()`(恢复全局 dispatcher 与代理环境变量)。
- `src/` 相对导入带 `.js` 后缀(tsc NodeNext 输出要求);`tsconfig.json` 开启 `strict`、`noUncheckedIndexedAccess`、`exactOptionalPropertyTypes`。
## 测试
```sh
pnpm install
pnpm test # vitest:40 个用例
pnpm run build # tsc 输出 lib/
dsh plugin check . # 用 dsh 校验 patch 层与插件挂载(从 fork 源码运行时用 pnpm dsh)
```
测试使用本地 mock 代理(HTTP 绝对形式 + CONNECT 隧道)与真实目标服务器路由,覆盖候选解析、主机匹配/NO_PROXY、RoutingDispatcher、探测/决策、激活/停用/切换恢复、WebFetchProvider 契约等 40 个用例。
> 注:`dsh plugin check` 会提示 `unknown inject dependencies: commands` —— 这是检查工具自身已知服务清单未收录 `commands` 的提示(组合中实际存在),不影响安装与运行。
Install
dsh plugin --profile web add github:minatoAI/dsh-net-proxy-plugin
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-net-proxy-plugin from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.