🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

Provider 代理设置 hpfs0/dsh-provider-proxy

DeepSeek 客户端v0.2.0
hpfs04 天前有提交已提供中文文档扫描:未发现敏感能力兼容 / 相关生态

一个让provider会走代理的插件

31.5/100
综合分
2
GitHub 星标
414
npm 周下载
—/5
用户评分

数据截至 2026/10/28 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/hpfs0/dsh-provider-proxy.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
生态插件
可安装,未声明 dsh 能力
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功(非静态推断);本次验证针对插件 v0.7.0,当前版本为 v0.2.0
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 4 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-provider-proxy @ 0.2.0
✓Node 引擎要求 ^22.19.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/12 22:43:35

安全扫描自动 · 静态分析源码
v2.7.0
🟢 扫描:未发现敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。

写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 7 处,展示前 7 条)
  • 发起网络请求
    lib/index.js:47
    const inFlight = undiciFetch(input, {
  • 发起网络请求
    lib/index.js:1368
    return new Socks5ProxyAgent(url, auth);
  • 发起网络请求
    lib/index.js:1370
    return new ProxyAgent({ uri: url });
  • 发起网络请求
    lib/index.js:1982
    const response = await undiciFetch2(requestTarget, {
  • 发起网络请求
    lib/client.js:574
    const response = await globalThis.fetch(CATALOG_PATH, { headers: { accept: "application/json" } });
  • 写入剪贴板
    lib/client.js:1396
    void globalThis.navigator.clipboard.writeText(text).then(() => {
  • 发起网络请求
    lib/client.js:1462
    const response = await globalThis.fetch(TEST_PATH, {
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 单独配置代理开关
  • 自定义代理地址端口

README

由 DeepSeek 最新模型翻译生成
dsh-provider-proxy DSH Market 给 DeepSeek Harness(0.2.0-rc.1 及以上)用的插件:在 设置 里为每个 provider 单独配置「这个 provider 要不要走代理」,填写代理类型 + IP/端口。只有被你打开开关的 provider 走代理,其余 provider 和 DSH 的其它流量保持原来的直连路径。 支持官方桌面版(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)。
展开完整文档(README 剩余 1 节)
dsh-provider-proxy DSH Market

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

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

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

多代理预设(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)

页面顶部的 备份配置 / 恢复配置:

- 导出会先问一次「是否包含代理密码?」——默认不含(取消即脱敏)。文件名为 dsh-proxy-backup-<日期>.json,内容包含 providers、proxyProfiles、defaultProxy、 localDirect、testUrl。
- 导入先解析并校验类型,不合法会直接报错且不改动现有配置;合法则弹确认框显示将覆盖多少个 provider / 预设,确认后一次写入覆盖全部五段。缺字段按 schema 默认补齐, 0.8.0 的旧备份(没有 proxyProfiles)也能导入。
- 脱敏导出把每个密码字段写成空字符串(而不是删掉),所以文件里能一眼看出「这里被脱敏了」;恢复脱敏备份后,代理密码需要重新填写。

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

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

| 状态 | 含义 |
| 配置被拒 | 已启用,但端点 / 凭据不完整或 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 引用 |
| localDirect | true | 本机 / 内网目标是否直连(见「目标域名规则」一节) |
| testUrl | https://api.ipify.org?format=json | 自检的最后兜底目标;可写多个(逗号或空格分隔,上限 5 个),按序回退 |

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

其中的 localDirect 与 testUrl 也可以在默认代理区块下方的 全局设置(高级) 折叠块里修改: 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(proxyTls)。
- bypassHosts 命中时由本机解析并连接目标(DNS 不经过代理)。SOCKS5 下这一点后果最明显:命中的域名不再由代理端解析,本机 DNS 会暴露该次查询。
- web_search 可以走代理,web_fetch 不行。 搜索服务商(dsh-web-search-deepseek)用全局 fetch,插件通过包裹 ctx.web.search 把它送进代理作用域;这条成立的前提是:工具每次调用都重新取 ctx.web.search,且服务商的 search() 是 await-to-completion(不是惰性消费的流)。web_fetch 则用 undici 自带的 dispatcher(来自官方的进程级 env 策略),没有 per-provider 注入点,本插件覆盖不到——需要覆盖它请改用 HTTP_PROXY 之类的环境变量。
- 保留 id web-search。 它的路由存在 providers 字典里;真实服务商若恰好撞名,会与该路由共用同一份配置,无害。
- 上游若绕过 globalThis.fetch 自行建连,则不受影响。 llm-deepseek 确认是调用时解析全局 fetch;llm-pi-ai 走外部 SDK,抓取 fetch 的时机未能在源码层面确证。
- 不支持 PAC,也不读取系统代理设置。
- 不改写 baseURL —— 反向代理/中转请直接在服务商上配置。
- 代理不可达时请求照常失败,DSH 既有的重试与错误码语义不变。
- 连接自检的目标可能不可达。 默认先尝试该服务商适配器发布的上游地址,取不到才退回顶层 testUrl(默认是回显服务,部分网络下不可达,请换自建服务)。自检只接受 POST,同一服务商的在飞请求会合并;配置未变时,完成后的 3 秒内重复点击直接返回上次结果(标记 cached),一旦改动配置(端口 / 地址 / 凭据 / 自检目标 / 域名规则 / localDirect / testUrl)即重新外呼。未启用的检查用完即释放临时 dispatcher——不会被当成外呼放大器。
- 代理密码以明文随设置写入 cordis.patch.yml。 HTTP/HTTPS 的密码会拼进交给 undici 的代理 URL(ProxyAgent 由此生成 Proxy-Authorization),SOCKS5 的密码只作为选项传给 Socks5ProxyAgent;两者都不会出现在日志或诊断里的代理 URL——那里只显示不含凭据的 origin。
- 设置页的两个端点没有自己的鉴权。 /dsh-provider-proxy/providers(服务商列表、计数、被拒原因)与 /dsh-provider-proxy/test 的访问边界完全由 DSH 的 webServer 决定。webServer 只绑定本机回环时没有顾虑;若它绑定到非 loopback 地址,同网络的其它主机可以读到这些信息并触发自检外呼(自检有在飞合并、8 KiB 请求体上限与 3 秒冷却,但仍是可触发的外呼)。

如何验证代理确实生效

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

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

开发

npm run build # 构建 lib/index.js 与 lib/client.js
npm test # 392 个用例,跑的是构建产物
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 patch # 0.9.0 -> 0.9.1
npm publish
git push --follow-tags

发布说明放在 release_notes/,文件名形如 RELEASE_NOTES_090.md。
预览分享卡片长什么样 ↓hpfs0/dsh-provider-proxy
分享 · DeepSeek Harness Hub
hpfs0/dsh-provider-proxy

一个让provider会走代理的插件

回到顶部