中文EN

dsh-proxy-switcher

toolsnpmMIT

在 dsh 运行过程中热切换出站代理(直连/系统/自定义),无需重启即可生效

下载量 / Downloads
505

安装

dsh plugin --profile web add dsh-proxy-switcher

沙箱测试

这个插件还没测过。提交测试 →

README

dsh-proxy-switcher

在 dsh 运行过程中 热切换出站代理:直连 / 跟随系统环境变量 / 使用已保存的多个代理

(HTTP · HTTPS · SOCKS5)。切换入口在 dsh 设置 → Proxy,改完立即生效,不需要重启 dsh。


1. 这个插件解决什么问题

dsh 的出站请求(LLM 对话、网页搜索、web_fetch、模型发现、MCP over HTTP)都走宿主进程的

全局 fetch。它背后的代理是 Node 的 NODE_USE_ENV_PROXY + HTTP(S)_PROXY,而 **这个选择在

进程内只会被解析一次**:

  • 启动后修改 process.env.HTTP_PROXY → 无效(实测:指向一个死端口仍然照常走原代理);
  • 启动时若没有代理变量、之后再设置 → 同样无效(实测:直接 DNS 失败)。

所以过去换代理只能重启 dsh。本插件把「用哪个代理」从 启动时决定 变成 请求时决定。

2. 热切换原理

2.1 唯一的切换点:undici 全局 dispatcher

Node 内置 fetch 从 globalThis[Symbol.for('undici.globalDispatcher.1')] 取传输层;npm 版

undici 的 setGlobalDispatcher() 写的正是这个槽位(以及 .2)。因此:

node_modules/undici 的 setGlobalDispatcher(agent)  →  内置 fetch 立即改走 agent

实测(probe/probe-dispatcher-placements.mjs,Electron-as-Node 24.18.1):

放置方式 · 结果

全局槽位 + Agent / Agent{factory} / ProxyAgent · ✅ 全部生效

全局槽位 + ProxyAgent("socks5://…") · ✅ SOCKS5 生效

显式 fetch(url, { dispatcher }) + 8.x dispatcher · ❌ UND_ERR_INVALID_ARG: invalid onRequestStart

undici.fetch(url, { dispatcher }) 同实例 · ✅ 生效(dsh-web-fetch-http 用的就是这种)

原因:内置 fetch 自带一份 undici(24.18.1 内是 7.29.0),而宿主 undici 是 8.11.2。

全局槽位由 Node 做版本适配,显式传入则原样使用,跨代就报错。所以本插件 只通过全局槽位

切换,不包装 fetch。

2.2 复用 dsh 自带的官方 seam

0.2.x 的 dsh 已经自带 @deepseek-ai/dsh-http-proxy(README 明说"SOCKS/PAC/系统代理检测不支持")。

插件优先调用它导出的 installProxyFromEnvironment(env, report):

  • http/https/直连 → 委托给它。这样三件事同步一致:undici 全局 dispatcher、

proxyRouteFor()(dsh-web-fetch-http 靠它决定是否 pin 地址)、子进程继承的代理环境变量。

  • SOCKS5 → 它按设计拒绝 SOCKS,插件改用自己装的全局 dispatcher(8.x 的 ProxyAgent 原生支持

SOCKS5;7.x 则回退到内置的手写 SOCKS5 connector)。

没有 dsh-http-proxy 的老版本 dsh(例如本地 0.1.0-rc.7 检出)会全部走自建路径,插件仍可用。

2.3 已建立的连接

切换时旧的 dispatcher 用 close() 释放(不是 destroy()):已在途的请求在旧传输上跑完

(流式回答不会被打断),新请求用新策略。

2.4 为什么「系统代理」模式需要点一下 "Re-read env & apply"

安装的传输层是策略快照(这也是官方 seam 的设计,保证只有一个真值来源)。所以改了

http_proxy 之后,点设置页的 Re-read env & apply(或 /proxy on)即可,无需重启。

3. 安装

pwsh -File install.ps1                 # 默认 desktop profile
pwsh -File install.ps1 -Profile web

脚本做三件事,全部可逆:备份 profile 的 package.json → 在

<profile>/node_modules/ 建一个指向本目录的 junction → 把包名写入 dependencies 和

dsh.profile.bundles。不跑 pnpm、不联网、不动 lockfile。

安装后需要让 dsh 重新加载 loader,插件才会出现在 设置 → Proxy。在此之前它只是磁盘上的文件,

不影响任何行为。

也可以用 dsh 官方安装器:dsh plugin --profile desktop add link:<本目录>,它会用 pnpm 接管
同样的三个步骤。

4. 卸载与回滚

pwsh -File uninstall.ps1                    # 删 junction + 撤销 package.json 改动
pwsh -File uninstall.ps1 -RestoreBackup     # 另外还原安装前的 package.json

卸载后同样需要 dsh 重载 loader。卸载不影响 dsh 原有功能:插件在 unload 时会把启动时的

dispatcher 和代理环境变量原样放回(有测试覆盖,见验收 8)。

若要彻底忘记保存的代理,删掉 $DSH_HOME/proxy-switcher.json 即可。

升级:直接用新版本覆盖本目录(junction 指向的就是它),然后重载 loader。

回滚:保留旧版本目录,把 junction 重新指过去,或用 -RestoreBackup 还原 profile。

5. 设置界面

Settings → Proxy 一个页面,包含:

  • Currently in force:当前生效模式/代理、判定原因、传输策略、no_proxy、切换次数与耗时、

配置文件路径、上次错误(含时间)。

  • Mode:Use a proxy 总开关 + 三选一(Direct / System / Saved proxy)。
  • Saved proxies:列表 + 单选切换 + 每项 Test / Edit;Add proxy 新增。
  • 编辑器:协议(HTTP/HTTPS/SOCKS5)、主机、端口、用户名、密码(掩码输入)、

密码来源环境变量、该代理独立的 no_proxy。可单独 Test this proxy。

  • Advanced:全局 no_proxy、探测 URL、超时、代理缺失时的行为(拒绝请求 / 直连)、

连续失败自动回退。

  • Recent activity:脱敏的操作与错误历史。
  • Save and apply / Discard changes / Test current / Re-read env & apply / Refresh。

一键切换(开关、模式单选、代理单选)会立即生效并保存;编辑器里的改动需要 Save and apply。

页面上的 temporary 语义由 API 的 persist:false 提供(当前进程内生效,不写入文件)。

6. 配置

配置文件:$DSH_HOME/proxy-switcher.json(可用 DSH_PROXY_SWITCHER_CONFIG 覆盖)。

示例见 [config.example.json](config.example.json)。

字段:enabled · mode(direct|system|custom) · activeProfile · profiles[]

(id,name,protocol,host,port,username,password,passwordEnv,noProxy) ·

noProxy · bypassLoopback · probeUrl · timeoutMs · onMissingProfile(block|direct) ·

fallback{mode,failureThreshold} · historyLimit。

优先级

运行时设置(设置页 / /proxy / API)  >  环境变量  >  配置文件  >  默认值

环境变量:

变量 · 作用

DSH_PROXY_SWITCHER_ENABLED · 1/0 总开关

DSH_PROXY_SWITCHER_MODE · direct / system / custom / off(=关闭代理)

DSH_PROXY_SWITCHER_PROFILE · 选择已保存代理的 id

DSH_PROXY_SWITCHER_URL · 直接用一条代理 URL(最高优先,隐式 custom)

DSH_PROXY_SWITCHER_NO_PROXY · 覆盖 no_proxy

DSH_PROXY_SWITCHER_CONFIG · 配置文件路径

其它管理方式

  • /proxy 命令:status · on · off · direct · system · use <id> · temp <id> · test [id]
  • 插件 API(其它 Cordis 插件可用):ctx.proxySwitcher.status()/config()/configure(patch)/applyRuntime(patch)/reset()/test(candidate)
  • HTTP(设置页用的就是它,同源校验):GET state|status,POST apply|reset|reapply|test

于 /dsh-proxy-switcher/*

7. 安全

  • 密码永不回传浏览器:接口返回 password: "" + hasPassword + 掩码预览

(socks5://user:***@host:port),未重填时回传哨兵值表示"保持不变"。

  • 日志/状态/历史全部脱敏:redactProxyUrl() 统一处理,含错误的 cause 链。

(这一条是实测发现并修掉的真实缺陷:状态曾把实时代理环境变量原样带出。)

  • 推荐用 passwordEnv:密码留在环境变量里,配置文件不落盘,且每次请求重新读取,轮换无需重启。

写文件时用 0600 权限(Windows 上依赖用户目录 ACL)。

  • 请求接口做同源 + loopback 校验,拒绝跨站调用。

8. 兼容性

项 · 支持范围

dsh · 0.2.0-rc.1+(自动复用 dsh-http-proxy);更早版本走自建路径

运行时 · Node 22.19+ / 24+(实测 Electron 44 ↔ Node 24.18.1,bundled Node 24.21.0)

操作系统 · Windows 实测;实现只用 node:net / node:tls / undici,无平台专属代码

代理协议 · http · https · socks5 / socks5h(域名远端解析)

覆盖的流量 · 所有走全局 fetch 的路径:LLM(deepseek / pi-ai)、web search、web_fetch、MCP HTTP

9. 已知限制

1. OTLP 遥测不走代理。它用 node:http 自建 agent(dsh 官方也这么记录)。需要时用

DSH_TELEMETRY_MODE=DISABLED 关掉。

2. SOCKS5 下 web_fetch 会直连。dsh-http-proxy 按设计拒绝 SOCKS,于是

proxyRouteFor() 报"未代理",web_fetch 就用它自己 pin 地址的 agent 直连。其他链路

(LLM、搜索)在 SOCKS5 下正常。要覆盖它必须改 dsh 自身。

3. system 模式不是每请求重读环境变量(见 2.4)。

4. 切换瞬间有一个极短窗口:先还原再安装,窗口内新请求使用启动时的策略;在途请求不受影响。

5. onMissingProfile 默认 block(宁可明确报错也不静默直连,避免误以为走了代理)。

6.

…(truncated)

以上内容来自 npm 上的 README,我们只做格式化,不改内容。

版本

npm 上共发布:1,最后更新 2026-10-02

链接

GitHub · Homepage · npm

同类插件