DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

hpfs0 /

hpfs0/dsh-provider-proxy

Verified

一个让provider会走代理的插件

★ 2 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@6aeab785

dsh-provider-proxy DSH Market

给 DeepSeek Harness(0.2.0-rc.1 及以上)用的插件:在 设置 里为每个 provider 单独配置「这个 provider 要不要走代理」,填写代理类型 + IP/端口。只有被你打开开关的 provider 走代理,其余 provider 和 DSH 的其它流量保持原来的直连路径。

v1.1.0 (Advanced Extensions) 把它推进到「可观测 + 可分流」:后台定时心跳监测与失联预警(链路悄然中断时提前亮红灯)、搜索 / 推理流量分离(两类流量各走各的出口,绑定失效时拒绝而不是悄悄直连)、SOCKS5 over TLS(把到代理服务器的那一跳加密),外加设置页页签化与一轮无障碍封顶。0.8.0 / 0.9.0 / 1.0.0 的配置与备份均无需迁移。

上一版 v1.0.0 (GA) 是生产级治理中枢:运行监控仪表盘(覆盖率、连通成功率、平均时延与累计请求量)、全局紧急旁路(一键全局直连故障排查)、故障容灾自动降级(代理网络中断时自动直连重试,避免问答会话阻断)。

支持官方桌面版(DSH Desktop)与 dsh web。

安装

先确保已构建过产物(lib/index.js、lib/client.js):

npm install
npm run build

桌面版(必须先完整打开一次桌面版以初始化 profile,然后完全退出,否则安装命令会拒绝执行):

dsh plugin --profile desktop add dsh-provider-proxy

Web:

dsh plugin --profile web add dsh-provider-proxy

安装后重启对应应用。设置项写在 $DSH_HOME/profiles/<profile>/cordis.patch.yml。

使用

打开 设置,左侧导航里会出现一项 Provider 代理(英文界面为 Provider Proxy)。 Provider 列表 页签里每个 provider 一张卡片,各自独立配置:

字段 说明
开关 该 provider 是否走代理
端点来源 下拉框:独立自定义配置 / 预设:默认预设 (127.0.0.1:7890) / 预设:<名称> (<地址>)。选中的端点整体提供本卡片的传输字段;选「默认预设」即原来的「使用默认代理」
类型 HTTP / HTTPS / SOCKS5
IP / 主机 只填 IPv4、IPv6 或 ASCII 域名;不要带协议前缀、端口、路径或凭据。IDN 需先转 punycode
端口 1–65535 的整数
用户名 可选(与密码成对填写)
密码 可选(与用户名成对填写)。作为代理认证口令发给代理(HTTP / HTTPS / SOCKS5 均支持)。默认隐藏,可用密码框内右侧的眼睛图标临时查看,切换不会改写或保存密码

同一个代理要给多张卡片用时,只在 代理预设 页签的 默认代理 区块填一次,再在每张卡片勾上 使用默认代理。 勾上之后该卡片的传输字段(类型 / 主机 / 端口 / 用户名 / 密码)整体取自默认区块,卡片不再显示这些输入框, 改为显示它实际会用的地址——所以不会出现「填了一半、另一半不知道从哪来」的模糊状态。目标域名规则与自检目标 始终是卡片自己的。默认区块本身不可用时(未填地址、地址不是合法 IPv4/IPv6/域名、端口非法、凭据只填了一半), 勾了继承的卡片会被拒绝并说明原因(列在「连接诊断」里),卡片上也直接显示这条原因;默认块的「配置完整」 状态与批量按钮用的是同一个判定,所以不会出现「亮着绿灯却一张卡也用不上」。

页签布局(1.1.0)

设置页分成四个页签:Provider 列表(默认)/ 代理预设 / 全局设置 / 备份与恢复。 运行监控仪表盘与底部「连接诊断」留在页签之外——它们是整机状态,不是设置项。

  • 页签是 role="tablist" + role="tab" / role="tabpanel",带 aria-selected 与 aria-controls,非活动面板用 hidden 而不是卸载:草稿、自检结果、筛选条件、折叠块的展开状态 都不会因为切换页签而丢失。
  • 进入「代理预设」「全局设置」页签时会自动展开对应面板,不必再点一次标题。
  • 页签选择记在 localStorage 的查看偏好里,离开再回到本页仍停在上次那一页。

SOCKS5 over TLS / Proxy TLS(1.1.0)

协议选 SOCKS5 时,卡片与预设表单会展开 启用 TLS 加密隧道(SOCKS5 over TLS) 开关; 打开后再展开 允许自签名证书(附安全警示框,默认关闭=严格校验)。

  • 开启后端点显示为 socks5s://host:port,诊断与卡片上的地址一眼可见 TLS 是否生效。
  • 粘贴 socks5s:// 或 socks5+tls:// 开头的地址会自动识别为 SOCKS5 + TLS。
  • proxyTls / proxyTlsRejectUnauthorized 参与路由指纹:切换开关会立即重建 dispatcher, 不会继续用旧连接。
  • 默认 严格校验证书(proxyTlsRejectUnauthorized: true);关掉它等于接受任何证书, 只应在自签名的内网代理上临时使用。

后台心跳监测与失联预警(1.1.0)

全局设置 页签里的 启用后台心跳监测 开关(默认 关闭,坚持零额外流量原则):

  • 开启后按 heartbeatInterval(60 / 300 / 900 / 1800 秒)向已启用的路由发送轻量探测, 每个 provider 之间打散抖动,并发受限,不会整点冲峰。
  • 连续失败达到 heartbeatConsecutiveFails(默认 2)次即标记为 失联:仪表盘脉冲点转为红色, 顶部出现失联预警横幅,卡片与紧凑行打上 心跳失联 (ECONNREFUSED) 标签并显示上次成功时间。
  • 未达阈值前会先在卡片上显示黄色 探测异常 N/M,不必等满阈值才发现问题。
  • 代理恢复后的下一次探测即自愈为健康;关闭开关只停止探测,不抹掉已有记录,但记录也不再渲染。

搜索 / 推理流量分离(1.1.0)

web_search 与模型推理可以走不同的出口。全局设置 页签里的 搜索流量分流 提供四个策略 (单选框,默认 继承):

策略 搜索流量走哪
继承 沿用「Web 搜索」卡片(端点 + 目标域名规则);该卡片未启用则直连
独立 固定走绑定的代理预设;不绑定预设则使用「Web 搜索」卡片自己填的端点
直连 绕过本插件管理的所有代理,推理流量不受影响
自定义 完全交给「Web 搜索」卡片管辖,包括它的 only/bypass 规则
  • 宁可拒绝,也不悄悄改走直连:独立 绑定的预设被删除或配置不完整时,每次搜索都会被拒绝 (错误信息里点名是哪个预设),而不是把搜索的真实出口漏到用户特意绕开的网络上。设置页会就地 红色告警,仪表盘也会出现拒绝横幅。
  • 面板下方始终显示 当前解析:这次设置到底会让搜索走卡片、独立端点还是直连,一眼可见。
  • 仪表盘的「累计转发请求量」拆成 推理 N · 搜索 M(直连 K);卡片与「最近调用」行分别标注 [推理] / [搜索],所以「搜索没走代理」与「搜索没被用过」不会混为一谈。

运行监控仪表盘与全局紧急旁路(1.0.0)

页面顶部常驻紧凑的 运行监控仪表盘(Dashboard Grid),实时掌控整机网络与代理健康度:

  • 运行状态指示灯:矢量 SVG 纯圆呼吸灯,实时标识当前网络调度状态(绿色为正常代理接入,黄色为旁路模式)。
  • 代理覆盖率:实时统计 X / Y 活跃(如 3 / 12 活跃),与页面内的勾选/取消勾选操作实时乐观联动,无需切页或重刷。
  • 连通成功率:基于近 24 小时内的真实请求和自检结果计算百分比(如 100% 成功 / 链路状态健康)。
  • 近期平均延迟:基于近期多次探测与自检的加权平均耗时(如 260 ms / 连通响应迅速,未测试时显示 - ms)。
  • 累计转发请求量:展示当前会话经由代理中转的累计请求次数(千分符格式化,如 1,420),数字右侧配有 重置 按钮,一键清零计数(调用 Host /dsh-provider-proxy/reset-hits)。
  • 全局紧急旁路开关 (Bypass All):仪表盘右上角提供全局旁路总控。开启后整机所有 Provider 流量瞬间全量直连上游,卡片头上统一显示警示条;不会改写各个卡片的现有配置,关闭后瞬间恢复原有每张卡片的代理规则,极大简化代理故障时的对比排查。

故障容灾自动降级(1.0.0)

日常高频对话中,本地代理客户端(如 Clash/V2Ray)偶发崩溃、系统休眠唤醒或端口拒绝连接(ECONNREFUSED、ETIMEDOUT、EHOSTUNREACH 等网络级故障)可能直接导致问答流中断。

在页面 全局设置(高级) 折叠区中提供 代理宕机时自动降级直连 开关(enableFallbackDirect):

  • 默认严格关闭(false):严格保持安全 fail-closed 传统,不擅自向目标服务暴露本机真实 IP。
  • 开启后自愈:当代理网络中断时,插件自动捕获网络层异常并立刻通过本机原生直连重试该请求,保障长任务或无人值守会话不中断,同时在卡片「最近调用」中以醒目标注高亮回显 [降级直连]。
  • 安全告知:界面上附带显式警示框,提示开启后在代理失效时可能会向模型服务商暴露真实出口 IP。

多代理预设(0.9.0)

不同厂商常需要不同出口(OpenAI / Claude 走美区或新加坡、DeepSeek 走直连或香港、企业中转走专线), 所以除默认代理外还可以建命名预设:在 代理预设 页签里「添加预设」,填好端点后, 每张卡片的 端点来源 下拉里就会出现 预设:<名称>。

  • 预设与默认代理是同一类东西:默认预设 (Default) 就是上方的「默认代理」区块,只是显示成一个预设。
  • 改预设 = 改所有绑定它的卡片:这些卡片的「最近调用」记录与自检结果会立即失效,不会拿旧节点的测试结果冒充新节点。
  • 删除仍被使用的预设会先确认,并在提示里列出依赖它的 provider;删除后这些卡片显示「配置被拒」并说明是哪个预设不存在, 不会悄悄退回卡片自己的空字段。
  • 重命名预设会同时改写所有绑定关系的 profileId。
  • profileId 非空时优先于 useDefault;页面选择预设时会自动把 useDefault 置为 false。
  • 端点来源始终是下拉框,每个选项都带上它将使用的地址;选「默认预设」等价于原来的勾选「使用默认代理」, 写盘仍是 useDefault: true,旧配置与旧行为完全一致。

一键导入(0.9.0)

默认代理区块、每个预设行、以及每张卡片都提供一个 常用客户端… 下拉:

选项 填入
Clash / Mihomo HTTP 127.0.0.1:7890
Clash Verge / Meta HTTP 127.0.0.1:7897
V2Ray / Xray HTTP HTTP 127.0.0.1:10809
V2Ray / Xray SOCKS5 SOCKS5 127.0.0.1:10808
从环境变量 Host 进程的 HTTPS_PROXY / HTTP_PROXY / ALL_PROXY

一次选择会一次性写入协议 / 主机 / 端口三项(不是三次写入),仍然走既有的地址与凭据校验, 选完可以直接改。环境变量只取地址:凭据永远不进 /dsh-provider-proxy/providers 的响应, 如果该变量带了用户名密码,页面会提示「请在下方补填」,而不是假装已经导入。 没检测到可用的环境变量时,该项显示「未检测到」并禁用。

官方上游域名模板(0.9.0)

卡片「高级:目标域名规则 → 仅走代理」下方,若 provider id 的首段是已知厂商 (openai / anthropic / google / deepseek),会出现一个 载入官方上游域名 药丸,一次写入规范主机名:

provider 写入 onlyHosts
OpenAI api.openai.com, *.openai.com
Anthropic api.anthropic.com
Google generativelanguage.googleapis.com
DeepSeek api.deepseek.com

它是追加而不是覆盖:你自己加的中转主机不会被丢掉;已经存在的同名规则(含 *. 写法差异)不会重复写入。 模板里的每一项都保证能通过死规则检查;未知 provider 不显示这个按钮,避免猜错主机名把流量限死。

配置备份与恢复(0.9.0 / 1.1.0)

备份与恢复 现在是设置页的一个独立页签:

  • 导出会先问一次「是否包含代理密码?」——默认不含(取消即脱敏)。文件名为 dsh-proxy-backup-<日期>.json,内容包含 providers、proxyProfiles、defaultProxy、 localDirect、testUrl,以及 1.1.0 的三个心跳字段 (heartbeatEnabled / heartbeatInterval / heartbeatConsecutiveFails)与两个分流字段 (searchProxyStrategy / searchProxyProfileId)。
  • 导入先解析并校验类型,不合法会直接报错且不改动现有配置;合法则弹确认框显示将覆盖多少个 provider / 预设,确认后一次写入覆盖全部十段。缺字段按 schema 默认补齐。
  • 向后兼容:0.8.0(没有 proxyProfiles、没有 version)、1.0.0(version: 1,带 proxyTls)、1.1.0(version: 2)三种文件都能直接导入。旧文件缺少的 1.1.0 段按当前默认值 补齐——心跳保持关闭、搜索保持继承;因为恢复是「整体替换」,让未提及的开关沿用还原前的状态会让结果 取决于上一个状态,而不是文件。
  • 来自更新版本的文件会被拒绝(version 大于本版本支持的上限):那种文件的键可能已被改写, 与其半途覆盖,不如给出一条明确的错误。
  • 脱敏导出把每个密码字段写成空字符串(而不是删掉),所以文件里能一眼看出「这里被脱敏了」; 脱敏覆盖每一个端点形态——卡片、命名预设、默认代理,以及 1.1.0 新增的 SOCKS5 over TLS 端点, 而 proxyTls / proxyTlsRejectUnauthorized 属于配置、会被保留。恢复脱敏备份后,代理密码需要重新填写。

端点输入不随开关变化:关闭的卡片照样可以填写、也可以测试。这样就能先验证再启用,避开了 「必须先打开代理才能测,而配置是坏的」这个窗口。

卡片头上的状态只描述路由:

状态 含义
配置被拒 已启用,但端点 / 凭据不完整或 Host 无法建立路由。该 provider 的请求不会经过本插件代理;原因直接显示在卡片内,也列在「连接诊断」里
走代理 已启用且配置可建立路由。单次请求仍可能因目标域名规则直连;它不代表代理可达,连通性看「测试」与「最近调用」
直连 未启用,流量走宿主原有路径

Host catalog 是否列出某个 provider(live)不影响已保存路由的卡片状态;它只决定未配置的 provider 是否出现在列表里。

页面上的几处便利:

  • 状态筛选标签:卡片列表顶部提供「全部 / 走代理 / 直连 / 配置被拒」快捷筛选胶囊标签,实时统计各状态下的 provider 数量,方便运维巡检与故障排查。
  • 紧凑与卡片双视图:支持卡片视图(便于逐项配置与编辑)与紧凑视图(高密度列表呈现代理类型、端点、最近调用与自检状态),紧凑行支持点击就地展开/折叠对应卡片。 紧凑行自 0.9.0 起分三行:第一行是名称 / id / 实际生效的端点 / 最近调用,第二行是开关、端点来源下拉与测试、编辑按钮, 第三行只在自检有结果时出现,展示延迟、HTTP 状态、出口 IP(可一键复制)或失败原因。结果单独成行, 所以测速进行中或报错时不会挤动上方的下拉与按钮。 视图模式与状态筛选属于查看偏好,记在浏览器本地(localStorage),离开再回到本页仍保持上次选择; 它们不会被写进 cordis.patch.yml——显示方式不是配置,也不该因为一次设置写入失败而丢失。
  • 响应式双列表单:端点配置(传输协议、主机、端口、用户名、密码)在卡片内采用响应式双列栅格排版,大幅缩减页面纵向高度与滚动频次。
  • 一键批量自检:搜索过滤栏右侧集成「全部测试」按钮,内置并发数为 3 的受控并发队列,批量验证所有已配置且已启用的端点,并实时显示已完成百分比进度。
  • 结果一键复制:自检回报的出口 IP、探测目标以及连接失败的错误信息旁均配有快捷复制按钮,点击即复制至剪贴板并提供微交互反馈。
  • 提示文字视觉高亮:输入框下方的辅助操作提示(如 IP 粘贴格式、认证口令说明等)采用醒目的提示色高亮显示,与常规字段标签清晰区分。
  • 过滤框:provider 多时按 id 或名称筛掉不必看的卡片。它纯粹是视图,不写任何设置。
  • 粘贴完整代理 URL:把 socks5://user:pass@host:1080 粘进任一「IP / 主机」输入框,会自动拆成 协议 / 主机 / 端口 / 用户名 / 密码,并且是一次写入(不是五个字段各写一次)。显式写出的 :80 / :443 会保留;完全没写端口时保持「未配置」,不会自动补默认端口。
  • 设为默认代理(卡片上,仅在该卡片有自己的端点时出现):把它当前的端点一次性提升为 代理预设 页签里的默认端点。 如果默认端点已配置且有卡片正在继承,会先弹确认框;取消后不写入、不改草稿。
  • 所有 provider 使用默认代理(N)(默认区块里):N 是全部 provider 的数量,不受过滤框影响。 一条批量写把每张卡片都指向默认端点;默认端点不可用时按钮禁用。
  • 最近一次调用:卡片头上会显示「最近成功 3 分钟前」,失败时显示「最近失败 ECONNREFUSED」。 命中计数只说明这条路由被用过,这一行说明它现在是否还工作。已启用但还没代理过请求的路由显示 「尚无代理记录」;测试按钮不计入——它证明的是「代理这一跳通」,不是「这条路由承载过真实流量」,两者混在一起会让这一行的含义不可信。 「最近成功」的口径是拿到了 <500 的响应,而不是「整个流跑完」——流式中途被切断仍会记为成功, 因为成功判据在响应头到达时就已成立(流式转发不能靠消费 body 来判断)。 「最近调用」的时间取请求发起时刻;「最近成功 / 失败」的结果与时间来自最近一次结束的请求—— 两者分开记录,并发调用下不会把一次请求的发起时间配到另一次的结果上。 这条记录只属于当前路由配置:改代理端点、凭据、useDefault 的默认端点、目标域名规则或 localDirect 后,该 provider 的「最近调用」会变成「尚无代理记录」;testUrl / testTarget 只影响测试,不会清掉它。仅关闭再打开代理开关也保留记录。修改配置前已经启动的请求不会把旧结果 写回新配置;删除后重新添加同名 provider 同样视为一条新路由。hits 命中计数是历史累计, 上述配置变化都不会清空它。

整页设置写在 cordis.patch.yml,也可以在 全局设置 页签里修改(同样可以手写):

顶层设置 默认 说明
defaultProxy 未配置 供勾选「使用默认代理」的卡片复用的端点
proxyProfiles {} 命名预设字典(名称 → 端点,与 defaultProxy 同结构);由卡片上的 profileId 引用
enableFallbackDirect false 故障容灾自动降级:代理宕机或网络中断时自动回退本机直连重试(防会话中断,带安全提示)
bypassAll false 全局紧急旁路模式:开启时所有流量绕过代理直连上游,不破坏卡片个体配置
localDirect true 本机 / 内网目标是否直连(见「目标域名规则」一节)
testUrl https://api.ipify.org?format=json 自检的最后兜底目标;可写多个(逗号或空格分隔,上限 5 个),按序回退
heartbeatEnabled false 后台心跳监测总开关。默认关闭:不做任何未经许可的后台外呼
heartbeatInterval 300 心跳探测周期(秒),页面提供 60 / 300 / 900 / 1800
heartbeatConsecutiveFails 2 连续失败多少次判定为「失联」并亮红预警
searchProxyStrategy inherit 搜索流量分流:inherit / dedicated / direct / custom(见上节)
searchProxyProfileId '' dedicated 绑定的预设名;留空表示用「Web 搜索」卡片自带的端点

每个 providers.<id> 除原有的 enabled / useDefault 等字段外,0.9.0 起还接受 profileId(默认 '')。 profileId 非空时该路由的传输字段整体取自 proxyProfiles[profileId],并优先于 useDefault; 目标域名规则与自检目标始终是卡片自己的。

localDirect / testUrl / enableFallbackDirect / bypassAll / 三项心跳设置 / 两项搜索分流设置都在 全局设置 页签里:testUrl 留空时 Host 使用内置默认值,非法候选或超过 5 项只会提示、不会阻止保存; localDirect 默认勾选。改动会清空页面上所有自检结果,并且点「测试」前会把待写设置先落盘—— 唯一例外是心跳与搜索分流这五个字段:它们只改变「探测」与「搜索」的路由,卡片自检的答案仍然有效, 所以切换它们不会白白丢掉刚刚测出来的结果。

列表只包含已激活的 provider(llm.listProviders() 能列出的那些)。仅在配置里声明、 但没有实际注册的可配置 provider 不显示——它的请求根本不会被发出去。 已经在 providers 里配过代理的条目即使暂时不激活也仍然保留,避免已保存的设置悄悄消失; catalog 的 live 只影响未配置 provider 是否出现在列表里,不参与已保存卡片的状态判定。

如果 Host 目录读取成功,但某个已保存的 provider 完全没有出现在目录里,页面不会隐藏它, 而是在「连接诊断」里列出这些 provider,并说明代理配置仍会保留。目录读取中或读取失败时不会显示 这条提示,避免把所有已保存 provider 误报为缺失。live: false 只表示目录中存在但当前未激活, 不算缺失。

保存立即对新请求生效,不需要重启;正在进行中的流会继续用它启动时的那条连接。

设置写入失败时,字段会回滚,并在页面顶部显示「保存失败:<字段> — <原因>」和关闭按钮;下一次成功写入或点击关闭后消失。多次失败只保留最新一条。写入成功与失败的诊断都不会包含密码值,密码字段固定显示「值已隐藏」。

每张卡片还有一个 测试 按钮:点它会让 Host 通过该 provider 自己的代理发一次请求,回报 HTTP 状态、延迟、脱敏后的探测目标与(探测目标返回时)出口 IP;冷却命中时标注「缓存」。只要该卡片有完整端点它就会出现——不必先启用;未启用的检查会标注为「未启用,仅测试」。代理不可达时它会明确失败,而不是假通过。结果与当前配置绑定:改端点 / 凭据 / 自检目标 / 域名规则 / 全局设置后旧结果会立即消失;点「测试」前会先把防抖中的设置写入,Host 测的是刚填的值。

探测目标在 Host 侧脱敏后才进入响应:合法 URL 只保留 origin + pathname,userinfo、query 与 fragment 都不会出现在结果、诊断或错误文本里;非法 URL 也会去掉 userinfo / query / fragment 并截断长度。网络错误同样不会回显原始目标。

目标与判定口径:目标按三级取用——卡片的 自检目标 → 该 provider 的真实上游(见下一节)→ 顶层 testUrl(可写多个候选,按序回退,命中任一即停;407 会提前终止,因为那是代理的问题)。结果按状态码分类:

结果行会标出这次用的是哪一级:(本卡片自检目标) / (该 provider 的真实上游) / (全局自检地址)。 所以「改了 testUrl 但某张卡片没变」是可以解释的——该 provider 自己发布了真实上游,本就不读 testUrl; 要覆盖它,请在那张卡片的「高级 → 自检目标」里显式填写。

状态 含义
407 代理要求认证
2xx / 4xx(非 407) 已达上游,链路通。真实上游对一次裸 GET 正常就是回 401/404,那正是「通了」的证据
5xx 疑似代理或上游故障。HTTP 代理够不到上游时会自己回 502/503/504
抛出(ECONNREFUSED、超时等) 按失败原因分类,不会假通过

延迟数值按快慢着色——≤500 ms 绿、≤1500 ms 黄、更慢红——但只有延迟数字带这个颜色, 状态码、目标与其它文字用普通标签色。一次超过 1500 ms 的成功检查仍然是成功,不会整行变红。

自检遵循与真实流量相同的代理 / 直连判定。 目标命中本机 / 内网地址、bypassHosts,或落在 onlyHosts 之外时,自检与路由一样直连——不把一个到不了该目标的请求塞给代理,也就不会把一条健康路由报成「代理坏了」。直连成功时卡片会标注「· 直连(未经过代理)」,直连失败则报「目标不可达」而非指向从未使用的代理。

卡片的「高级」里还有一个 自检目标(testTarget):留空时先用该 provider 适配器发布的上游地址(Host 经 ctx.settings 按该 provider 的 settings 路径读 baseURL,取不到再退回顶层 testUrl);填写后该卡片一律请求这个地址。它只被自检读取,路由本身不受影响;填错了只会让一次检查失败,不会拒绝该路由。

卡片里还有一个折叠的 高级:目标域名规则,按目标主机决定是否走代理:

字段 说明
仅走代理(onlyHosts) 逗号或空格分隔。非空时,只有匹配这些主机的请求走代理,其余直连。
直连(bypassHosts) 逗号或空格分隔。匹配这些主机的请求直连,优先于 onlyHosts。

两者都按后缀匹配(example.com 覆盖 api.example.com),* 表示全部,大小写不敏感。默认是 fail-closed:启用了代理的路由默认走代理,只有三种显式例外才直连——「目标命中本机 / 内网地址」「bypassHosts 命中」「onlyHosts 非空且未命中」;解析不出目标主机时仍走代理,避免畸形 URL 把带凭据的请求悄悄直连出去。

指向本机或内网的请求默认直连。 目标主机命中 localhost(含子域)、127.0.0.0/8、::1、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、169.254.0.0/16 时不经代理。这类目标通常是跑在同一台机器上的中转(LiteLLM、Ollama、本地 mock),正向代理根本到不了它,硬走代理只会失败,而且失败看起来像「代理坏了」。顶层 localDirect: false 可关掉这条(例如代理本身就是目标)。

带协议前缀(https://…)、路径(example.com/v1)、端口(example.com:443)或杂散通配符(*foo、foo*)的条目永远匹配不上——规则只认主机名,通配符只能是单独的 * 或以 *. 开头;单独的 *. 也视为死条目。方括号 IPv6 字面量(如 [::1])是合法的。这类条目会在输入框下以错误色标出,因为后果是静默的:onlyHosts 里的死条目会让 fail-closed 把流量直连出去,而用户以为已经走代理。Host 与设置页使用同一份共享解析 / 判定逻辑。

当组成里存在 web 服务时,列表还会多出一张 Web 搜索 卡片(保留 id web-search,字段与其它卡片完全相同,也支持自检、计数与域名规则)。它承载 web_search 工具的流量;不启用它,搜索保持直连。

为什么是独立设置页,而不是内嵌在 Models 的 provider 卡片里? 本插件最早按卡片内嵌(settings.models.provider-card)实现,但那个槽位是 keyed 的、 按 provider 命名空间派发,第三方注册在 0.2.0-rc.1 上始终匹配不上——不报错、不发请求、 界面上什么都不出现。独立分区(settings.section)是同一 profile 下 dsh-llm-retry-settings 已被验证可用的机制。

它是怎么工作的

0.2.0 没有「给单个 provider 注入 transport」的官方口子,这是核对过源码的结论:

  • llm-deepseek 直接调用全局 fetch,请求参数里没有 dispatcher;
  • llm-pi-ai 走 pi-ai SDK 自己的 HTTP,DSH 无法配置;
  • LlmCallControls 只有 reasoningEffort / temperature / maxTokens / stop;
  • llm/stream 拦截的是已解码的 StreamChunk,发生在 HTTP 之后;
  • ctx.web 只服务 web_search / web_fetch,与 provider 流量无关;
  • 官方的 @deepseek-ai/dsh-http-proxy 是库不是插件:installGlobalProxy 是模块私有函数,没有 service、没有 Config、只有从环境变量解析出的一份进程级策略。

所以请求在唯一还能拿到线索的地方被路由:llm/stream waterfall 的 options.provider。用一个 AsyncLocalStorage 把选中的 dispatcher 带到流上,再由一层很薄的 globalThis.fetch 包装把它交给 fetch。

两个关键点:

  1. 每个 provider 各自持有一个 dispatcher(而不是共用一个),所以即使两个 provider 指向同一个上游主机,也不可能互相串用对方的代理。
  2. 流的惰性消费必须被处理:adapter 的 HTTP 调用通常发生在后续的 next() 里,那时 als.run() 早已返回。所以每次迭代器的 next/return/throw 都会被重新包进作用域里 —— 这正是旧版实现在这里踩过的坑,仓库里有对应的回归测试。

代价是这一个全局 fetch 包装:作用域之外的行为与原生完全一致,作用域之内若调用方已经自带 dispatcher 也不会被覆盖。

已知限制

  • SOCKS5 由 undici 内置的 Socks5ProxyAgent 实现(RFC 1928 / RFC 1929),API 稳定性标记为 Experimental;undici 若在未来移除,需要同步调整。
  • 代理地址只接受 IPv4、IPv6 或 ASCII DNS 域名。 IDN 需先转 punycode;下划线、纯数字单标签(如 127),以及把端口写进主机字段(127.0.0.1:7890)都会被拒绝。粘贴完整 URL 时显式 :80 / :443 会保留,未写端口仍保持「未配置」。旧配置里不合规的主机名升级后会显示「配置被拒」,需要改成合法地址。
  • 代理认证字段成对填写:用户名与密码要么都填,要么都不填。 只填任一项都会被拒绝并给出提示;用户名在配对前会 trim(),纯空白的用户名视为未填。SOCKS5 侧的依据是 Socks5ProxyAgent 仅在两者齐备时才发起认证——只填用户名会静默广告无认证,只填密码则密码根本不会被发送(对开放代理能用,对要求认证的代理必然失败,且失败原因看起来像密码错误);HTTP/HTTPS 侧基本认证本可只发用户名(alice:,空密码),这里仍按成对拒绝:半填的凭据几乎总是漏填,三种传输共用一条规则比「哪种传输能承载哪种组合」更好记。代价如实写明——凭据真的使用空密码的代理无法在这里配置。不合规则的配置会被拒绝,原因列在设置页的「连接诊断」面板里(该路由的「测试」也会返回同一条),而不是被静默忽略——否则在强制认证的代理端,报错会指向代理而真正原因是设置缺项。
  • SOCKS5 不支持 socks:// 别名;SOCKS5 over TLS 自 1.1.0 起支持(proxyTls + proxyTlsRejectUnauthorized),依据是 undici 的 Socks5ProxyAgent 原生 options.proxyTls,TLS 仍是 Experimental API。
  • 搜索分流的 dedicated 绑定一旦失效就拒绝搜索(预设被删、端点不完整)。这是刻意的 fail-closed:悄悄改走直连会把搜索的真实出口暴露在用户特意绕开的网络上。要恢复搜索,改绑定或把策略切回「继承」。
  • bypassHosts 命中时由本机解析并连接目标(DNS 不经过代理)。SOCKS5 下这一点后果最明显:命中的域名不再由代理端解析,本机 DNS 会暴露该次查询。
  • web_search 可以走代理,web_fetch 不行。 搜索 provider(dsh-web-search-deepseek)用全局 fetch,插件通过包裹 ctx.web.search 把它送进代理作用域;这条成立的前提是:工具每次调用都重新取 ctx.web.search,且 provider 的 search() 是 await-to-completion(不是惰性消费的流)。web_fetch 则用 undici 自带的 dispatcher(来自官方的进程级 env 策略),没有 per-provider 注入点,本插件覆盖不到——需要覆盖它请改用 HTTP_PROXY 之类的环境变量。
  • 保留 id web-search。 它的路由存在 providers 字典里;真实 provider 若恰好撞名,会与该路由共用同一份配置,无害。
  • 上游若绕过 globalThis.fetch 自行建连,则不受影响。 llm-deepseek 确认是调用时解析全局 fetch;llm-pi-ai 走外部 SDK,抓取 fetch 的时机未能在源码层面确证。
  • 不支持 PAC,也不读取系统代理设置。
  • 不改写 baseURL —— 反向代理/中转请直接在 provider 上配置。
  • 代理不可达时请求照常失败,DSH 既有的重试与错误码语义不变。
  • 连接自检的目标可能不可达。 默认先尝试该 provider 适配器发布的上游地址,取不到才退回顶层 testUrl(默认是回显服务,部分网络下不可达,请换自建服务)。自检只接受 POST,同一 provider 的在飞请求会合并;配置未变时,完成后的 3 秒内重复点击直接返回上次结果(标记 cached),一旦改动配置(端口 / 地址 / 凭据 / 自检目标 / 域名规则 / localDirect / testUrl)即重新外呼。未启用的检查用完即释放临时 dispatcher——不会被当成外呼放大器。
  • 代理密码以明文随设置写入 cordis.patch.yml。 HTTP/HTTPS 的密码会拼进交给 undici 的代理 URL(ProxyAgent 由此生成 Proxy-Authorization),SOCKS5 的密码只作为选项传给 Socks5ProxyAgent;两者都不会出现在日志或诊断里的代理 URL——那里只显示不含凭据的 origin。
  • 设置页的两个端点没有自己的鉴权。 /dsh-provider-proxy/providers(provider 列表、计数、被拒原因)与 /dsh-provider-proxy/test 的访问边界完全由 DSH 的 webServer 决定。webServer 只绑定本机回环时没有顾虑;若它绑定到非 loopback 地址,同网络的其它主机可以读到这些信息并触发自检外呼(自检有在飞合并、8 KiB 请求体上限与 3 秒冷却,但仍是可触发的外呼)。

如何验证代理确实生效

插件本身不显示出口 IP。可靠的判断方式是在代理端看日志,或临时把端口改成 一个没人监听的端口——如果请求失败并报 ECONNREFUSED 127.0.0.1:<该端口>, 就说明请求确实是被代理承载的(直连时报错会指向目标站点)。

仓库里的 tests/proxy-routing-proof.test.ts 就是用这个思路做的:起一个会记录 连接的假代理,断言「启用的路由收到 1 次连接、未配置的 provider 收到 0 次」。

开发

npm run build      # 构建 lib/index.js 与 lib/client.js
npm test           # 410 个用例,跑的是构建产物
npm run typecheck  # 类型检查(仅覆盖 src/)
npm run verify     # 上面三件事,按顺序

lib/ 是构建产物,不入库;包上的 prepare 脚本会在 npm publish 以及从 git 地址安装时自动重建。直接 dsh plugin add <克隆目录> 前请先执行 npm install && npm run build。

浏览器半必须输出 client module system 的 lazy-CJS factory 格式(脚本本身只注册工厂,所有副作用都在工厂闭包里执行),scripts/build.mjs 负责这件事,格式与 DSH 自带插件一致。

发布

npm run verify              # 先确认全绿
npm pack --dry-run          # 核对包内容(9 个文件)
npm version 1.1.0           # 1.0.0 -> 1.1.0
npm publish
git push --follow-tags

发布说明放在 release_notes/,文件名形如 RELEASE_NOTES_110.md。

—/ 5

No ratings yet

Verified DSH bundle

Commit 6aeab7858e93

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout