dsh-proxy-whitelist
只让指定的模型渠道走代理,其余全部直连。
DSH 自带的代理支持(@deepseek-ai/dsh-http-proxy)读 HTTPS_PROXY / HTTP_PROXY / ALL_PROXY / NO_PROXY,启动时装一次、全进程生效:除了 NO_PROXY 里的主机,所有请求都走代理。那是一份「绕过名单」,方向恰好相反——它没法表达「默认直连,只有这几个主机走代理」。no_proxy 再怎么写也写不出白名单。
本插件把方向倒过来:保留 DSH 原来的 dispatcher 作为直连路径,只给你列出的主机加一条代理路径,由 dispatcher 在每次请求时按 URL 的 origin 决定走哪条。粒度因此是每个渠道(= 每个 baseURL),也就是 DSH 已经用来命名 provider 路由的单位。
为什么不是「按模型名分流」
模型 id 在请求体里,dispatcher 只看得见 URL。要按模型名分流,必须由拥有请求体的网关来做(例如 new-api 的渠道级 proxy 字段)——本插件不做,也不会假装能做;proxy_status 工具会明确告诉你某个 URL 的实际判定。
安装
dsh plugin add github:lilith1257/dsh-proxy-whitelist
也可以在 DSH 里打开 设置 → 插件 → 添加插件,填 github:lilith1257/dsh-proxy-whitelist。装完重启 DSH。
想改代码就克隆下来装本地路径:
git clone https://github.com/lilith1257/dsh-proxy-whitelist.git
dsh plugin add "$(pwd)/dsh-proxy-whitelist"
设置卡片
装好后在 设置 → 插件 → 代理白名单 有一个卡片(侧边栏「插件」页里也能打开,同一份配置):
1. 勾选渠道。 卡片会去读 设置 → 模型 里已经配好的供应商,列成清单:
已配置的渠道 已选 3 / 3 [拉取渠道]
☑ Command | goat api.commandcode.ai 64 个模型
☑ sta1n cdn.sta1n.cn 106 个模型
☑ d1 d1api.xin 1 个模型
勾上就走代理,取消就走直连。不用手打域名。 新增供应商后点「拉取渠道」刷新。
列表为空时会提示先去 设置 → 模型 配一个供应商。
2. 填代理地址。 你的代理客户端 HTTP 端口,例如 http://127.0.0.1:7890。
3. 保存。 保存后自动重新探测一次,立刻生效(不用重启)。
另外几块
- 运行状态 — 从 Host 读实时数据:分流是否生效、当前代理、生效规则、已分流多少条请求(走代理 / 直连分别计数)。
- 测试一条 URL — 填一个完整 URL,用 dispatcher 的同一个匹配器回答会走代理还是直连、命中哪条规则。
- 高级:手动写规则(默认折叠)— 上面勾选覆盖不到的主机(webhook、镜像、兄弟 API)在这里手写。平时不用展开。
勾选背后做了什么
一个渠道的 baseURL 只是一个主机(cdn.sta1n.cn),但它可能还需要跟同一域名的其它主机通信。所以:
- 勾选 写入精确主机规则
cdn.sta1n.cn。
- 如果已有更宽的规则(比如你手写过
.sta1n.cn),勾选状态按「被这条规则覆盖」来显示,不会重复写。
- 取消勾选 时,如果覆盖它的是宽规则,插件会把它展开成其它渠道各自的精确规则,再删掉你取消的这一个 —— 它下面的兄弟渠道继续走代理,不会被静默断掉。
勾选区下方会列出「你手写的、不对应任何供应商的规则」,取消勾选即删除那条。
配置文件(等价写法)
不想用卡片就改 profile 的 cordis.patch.yml:
- id: proxy-whitelist
name: dsh-proxy-whitelist
config:
enabled: true
proxy: http://127.0.0.1:7890
include:
- .d1api.xin # 该域名及其所有子域
- api.commandcode.ai # 精确匹配这一个主机
- "*.sta1n.cn" # 只匹配子域,不含 apex
- 10.0.0.0/8 # IPv4 网段
exclude:
- internal.example.com # 即使命中 include 也强制直连
规则语法
| 写法 | 含义 |
|---|
api.example.com | 只匹配这个主机 |
.example.com | apex + 任意层级子域 |
*.example.com | 只匹配子域,不含 apex |
10.0.0.0/8、10.1.2.3 | IPv4 网段 / 单个地址 |
api.example.com:8443 | 限定端口 |
匹配不区分大小写,忽略末尾的点;loopback 永远直连(localhost、127.0.0.0/8、::1、0.0.0.0、169.254.0.0/16),因为 DSH 自己就靠这些地址跟 Web UI 和本地 MCP 服务器通话。写错的规则会在加载时按原文报出来,而不是被悄悄忽略。
安全行为
- 默认不代理:
include 为空时一条也不代理,可以先装再配。
- 代理没启动就直连:地址在使用前探测一次,之后每 30 秒复查;代理客户端关掉时整体降级为直连,而不是让每个模型调用都失败。
- 不写
process.env:环境变量对子进程和所有读它的库都生效,写进去就收不回来了。本插件只替换 undici 的全局 dispatcher,卸载时还原。
- SOCKS 会被拒绝并说明原因:undici 只接受
http:// 与 https:// 代理,写 socks5:// 会被告知并保持直连。
模型可用的工具
proxy_status——报告当前代理、规则、是否已安装,以及某个 URL 会走哪条路:
proxy_status({ url: "https://api.commandcode.ai/provider/v1/chat/completions" })
与 mihomo / Clash 的关系
如果你已经在用 Clash Verge / mihomo 这类规则代理,它们本身就能按域名分流,最省事的做法是让 DSH 全量指向它们、由它们决定直连还是转发(见 README 末尾的对照)。本插件的价值在于:不装任何外部代理客户端、规则只属于 DSH 这个进程、随时在设置里改。
已知限制
- 代理地址只能写到端口。
http://127.0.0.1:7890/proxy 这种带路径的写法会被卡片拒收:undici 的 ProxyAgent 只接受 origin,带路径会在加载时抛错——而加载时的异常以前会变成 unhandled rejection,让下一次 DSH 启动直接失败,且坏值已经存进配置、用户没有界面改回来。现在卡片在保存前就拒收,路由层还有一道 try/catch 兜底降级为直连。
- 代理地址可以是远程的,但那意味着把 API Key 交给它。 走代理的每个渠道,请求里都带着该渠道的凭证;
checkProxyUrl 只校验协议与主机名,不限制地址是不是本机。卡片会在「当前代理」一行标出 ⚠ 提示(isLocalProxy),但这只是提示,不是拦截——局域网代理、SSH 隧道端口都是合法用法。不想要这个风险就把地址写回 127.0.0.1。
- 只覆盖把
globalThis.fetch 落到 undici 全局 dispatcher 的调用方(DSH 的 LLM 传输、web 搜索、HTTP MCP 都属于这一类)。worker 线程有自己的一份 globalThis,要单独安装 dispatcher,本插件不涉及。
- 不是透明代理:不会改写 WebSocket 之外的协议,也不处理
CONNECT 以外的隧道语义。
- 与
dsh-commandcode-goat 一类自带 proxy 的插件互不干扰:它们用自己的 dispatcher 发自己的请求,本插件只接管没被它们接管的调用。
开发
cd dsh-proxy-whitelist
npm install # 只有两个直接依赖:undici、@deepseek-ai/schemastery
npm test # 70 个用例:规则、dispatcher、真代理 + 真 fetch 的端到端、桥接、生命周期
node tests/render.mjs # 单独跑卡片的无头渲染(中英两套文案)
测试全部在本机临时端口上跑,不出网:cdn.sta1n.cn 这类域名只作为字符串参与规则匹配,从不真的连接。
卡片是无头渲染测试的:用一个小型 React 替身把每个座位的每个视图都渲染一遍,
再断言中英文案都完整。漏一个翻译键会在测试里显示成 MISSING:xxx,而不是等
用户打开设置页看到一串英文键名。
一个典型场景
三个渠道,三种处境——这正是本插件要解决的形状:
| 渠道 endpoint | 直连 | 走本地代理 |
|---|
api.example-a.com | 失败(超时) | 200 / 0.7s |
cdn.example-b.cn | 401 / 2.3s | 401 / 0.6s |
api.example-c.cn | 401 / 8.0s | 401 / 0.6s |
401 是正常的(这些端点要鉴权),关键是能不能连通和速度。对应配置:
- id: proxy-whitelist
name: dsh-proxy-whitelist
config:
proxy: http://127.0.0.1:7890
include:
- .example-a.com # 直连不通,必须走代理
- .example-b.cn # 能连但要 2s+,走代理快 3 倍
- .example-c.cn # 能连但要 8s,走代理快 10 倍
先别猜。卡片里的「测试一条 URL」用同一个匹配器回答判定,再给一条实测:
api.example-a.com (listed -> proxy): HTTP 200 in 582ms
cdn.example-b.cn (not listed -> direct): HTTP 401 in 7592ms
stats: {"proxied":1,"direct":1}
和 DSH 自带代理的关系(重要)
dsh-http-proxy 如果在启动时就拿到了 HTTPS_PROXY,全进程都已经在走代理了。本插件接管后,不在 include 里的主机回到直连——也就是把「全走」收窄成「只走这几个」。这不是副作用,是它唯一的语义;日志会明确说一遍。如果你要的就是「全走,只有几个例外」,那就继续用 no_proxy,不需要本插件。
对照:现成方案怎么选
| 你的需求 | 最省事的做法 |
|---|
| 几个域名走代理,其余直连 | 本插件(装一个、填 3 行);或 mihomo 写 DOMAIN-SUFFIX 规则 |
| 按模型名 / 渠道精细分流、要统计与重试 | 自建 new-api,渠道 JSON 里填 {"proxy":"socks5://..."} |
| 不想装任何东西、只要全局走代理 | 直接设 HTTPS_PROXY,DSH 自带支持 |
License
MIT © dsh-proxy-whitelist contributors。见 LICENSE。