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

LLM 代理分流 fan56/dsh-llm-proxy

DeepSeek Harness
fan5632 天前有提交已提供中文文档扫描:高危

按域名把 LLM 请求分流到指定代理,其余走系统代理

29.6/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add fan56/dsh-llm-proxy
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · model
装得上吗
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 32 天前
档位由哪些信号合成

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

验证判据与等级说明 →
⚠︎ 实装验证未通过(unknown · 2026/10/4) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
🔴 安全扫描发现高危能力自动 · 静态分析源码
v2.7.0
🔴 扫描:高危尚无人工评估

静态扫描发现安装期即执行代码,或命令参数由运行时变量拼接(参数来源无法静态判定,从严处理)。

安装时即执行脚本可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 52 处,展示前 38 条)
  • 读取本地文件
    scripts/link-dsh-closure.mjs:40
    if (!existsSync(join(repoRoot, 'tsconfig.json')) || !existsSync(join(repoRoot, '.git'))) {
  • 读取本地文件
    scripts/link-dsh-closure.mjs:53
    if (existsSync(join(dir, "cordis"))) return dir
  • 执行外部命令固定命令
    scripts/link-dsh-closure.mjs:59
    const bin = execFileSync('which', ['dsh'], { encoding: 'utf8' }).trim()
  • 读取本地文件
    scripts/link-dsh-closure.mjs:63
    if (existsSync(join(closure, 'cordis'))) return closure
  • 执行外部命令固定命令
    scripts/link-dsh-closure.mjs:69
    const root = execFileSync('npm', ['root', '-g'], { encoding: 'utf8' }).trim()
  • 读取本地文件
    scripts/link-dsh-closure.mjs:71
    if (existsSync(join(nested, 'cordis'))) return nested
  • 读取本地文件
    scripts/link-dsh-closure.mjs:76
    if (existsSync(join(flat, 'cordis'))) return flat
  • 写入本地文件
    scripts/link-dsh-closure.mjs:87
    mkdirSync(scopeDir, { recursive: true })
  • 读取本地文件
    scripts/link-dsh-closure.mjs:89
    for (const name of readdirSync(closure)) {
  • 删除本地文件递归
    scripts/link-dsh-closure.mjs:95
    rmSync(target, { recursive: true, force: true })
  • 读取本地文件
    scripts/smoke-boot.mjs:48
    const pkg = JSON.parse(await readFile(path.join(repoRoot, 'package.json'), 'utf8'))
  • 写入本地文件
    scripts/smoke-boot.mjs:54
    mkdirSync(profile, { recursive: true })
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:63
    const pack = spawnSync('npm', ['pack', '--pack-destination', work], { cwd: repoRoot, encoding: 'utf8' })
  • 写入本地文件
    scripts/smoke-boot.mjs:67
    writeFileSync(path.join(profile, 'cordis.yml'), '# dsh profile root — empty; the tree is composed from the bundle patches\n[]\n')
  • 写入本地文件
    scripts/smoke-boot.mjs:68
    writeFileSync(path.join(profile, 'cordis.patch.yml'), '# scratch smoke profile: no extra patch layer\n[]\n')
  • 写入本地文件
    scripts/smoke-boot.mjs:69
    writeFileSync(path.join(profile, 'package.json'), JSON.stringify({
  • 写入本地文件
    scripts/smoke-boot.mjs:89
    writeFileSync(path.join(home, 'settings.yaml'), `\
  • 写入本地文件
    scripts/smoke-boot.mjs:105
    writeFileSync(path.join(profile, '.npmrc'), 'legacy-peer-deps=true\n')
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:107
    const install = spawnSync('npm', ['install', '--no-audit', '--no-fund', '--loglevel=error'], { cwd: profile, encoding: 'utf8' })
  • 读取本地文件
    scripts/smoke-boot.mjs:114
    if (existsSync(path.join(installedOwn, 'node_modules', '@deepseek-ai'))) {
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:121
    const dump = spawnSync('dsh', ['--profile', 'smoke', '--dump-config'], { cwd: profile, encoding: 'utf8', env: dshEnv })
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:129
    const boot = spawnSync('dsh', ['--profile', 'smoke'], {
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:153
    const remove = spawnSync('dsh', ['plugin', '--profile', 'smoke', 'remove', ownName], { cwd: profile, encoding: 'utf8', env: dshEnv })
  • 执行外部命令固定命令
    scripts/smoke-boot.mjs:155
    const dumpAfter = spawnSync('dsh', ['--profile', 'smoke', '--dump-config'], { cwd: profile, encoding: 'utf8', env: dshEnv })
  • 删除本地文件递归
    scripts/smoke-boot.mjs:162
    rmSync(work, { recursive: true, force: true })
  • 发起网络请求
    src/index.ts:209
    originalDispatcher: undici.Dispatcher | undefined
  • 发起网络请求
    src/index.ts:212
    layers: Array<{ router: undici.Dispatcher; fetch?: typeof globalThis.fetch }>
  • 发起网络请求
    src/index.ts:236
    function takeoverGlobals(router: undici.Dispatcher): GlobalLayer {
  • 发起网络请求
    src/index.ts:238
    state.originalDispatcher ??= undici.getGlobalDispatcher()
  • 发起网络请求
    src/index.ts:246
    undici.install?.()
  • 发起网络请求
    src/index.ts:252
    undici.setGlobalDispatcher(router)
  • 读取本地文件
    src/index.ts:332
    content: stripFrontmatter(await readFile(SKILL_BODY_URL, 'utf8')),
  • 发起网络请求
    src/router.ts:33
    export function withUndiciErrorListener<T extends undici.Dispatcher>(dispatcher: T): T {
  • 发起网络请求
    src/router.ts:63
    systemDispatcher?: undici.Dispatcher
  • 发起网络请求
    src/router.ts:65
    proxyFactory?: (proxyUrl: string) => undici.Dispatcher
  • 发起网络请求
    src/router.ts:68
    class ProxyRouterDispatcher extends undici.Dispatcher {
  • 发起网络请求
    src/router.ts:70
    readonly #systemDispatcher: undici.Dispatcher
  • 发起网络请求
    src/router.ts:71
    readonly #proxyFactory: (proxyUrl: string) => undici.Dispatcher
扫描于 2026/10/31(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

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

功能亮点

  • 按域名分流LLM请求
  • 指定代理路由
  • 其余走系统代理

dsh 插件:按主机为 LLM 出站 HTTP 代理做路由(undici ProxyAgent),自带 dsh 技能

README

由 DeepSeek 最新模型翻译生成
dsh-llm-proxy dsh 插件:SYSTEM proxy + LLM 分流代理。用一个进程级 undici 路由 Dispatcher,把 LLM 流量按配置列表分流到指定代理,其余流量走系统代理(启动环境变量),都没配则直连。 优先级语义 LLM 列表命中 > SYSTEM proxy(env 语义)> 直连 - 请求 origin 命中 llmProxy 列表条目 → 走该条目指定的代理(undici ProxyAgent) - 未命中 → 走共享的 undici EnvHttpProxyAgent,它自己处理 HTTPS_PROXY / HTTP_PROXY / ALL_PROXY / NO_PROXY (大小写均兼容)——即标准 env 代理语义 - SYSTEM 也未配置 → 直连 环境变量只读不写:插件不会修改 process.env.HTTP_PROXY——对已启动进程无意义。
展开完整文档(README 剩余 1 节)
dsh-llm-proxy

dsh 插件:SYSTEM proxy + LLM 分流代理。用一个进程级 undici 路由 Dispatcher,把 LLM 流量按配置列表分流到指定代理,其余流量走系统代理(启动环境变量),都没配则直连。

优先级语义

LLM 列表命中 > SYSTEM proxy(env 语义)> 直连

- 请求 origin 命中 llmProxy 列表条目 → 走该条目指定的代理(undici ProxyAgent)
- 未命中 → 走共享的 undici EnvHttpProxyAgent,它自己处理 HTTPS_PROXY / HTTP_PROXY / ALL_PROXY / NO_PROXY (大小写均兼容)——即标准 env 代理语义
- SYSTEM 也未配置 → 直连

环境变量只读不写:插件不会修改 process.env.HTTP_PROXY——对已启动进程无意义。

安装

方式一:作为 npm 插件加入 profile
dsh plugin add @aiwayds/dsh-llm-proxy

方式二:手动 patch 挂载(本包自带 cordis.patch.yml)在 profile 的 bundles 中插入:
- id: dsh-llm-proxy
name: '@aiwayds/dsh-llm-proxy'

要求 dsh >= 0.1.7-rc.1 — 本插件只跟随 dsh RC/stable 线(CI 与发版在运行时解析 latest/next 中更新的 dist-tag)。不再支持 alpha 线。

⚠️ 最低宿主要求:0.2.0 起本插件声明 inject: ['skills'],要求宿主 dsh 提供 skills 服务(@deepseek-ai/dsh-skill 0.1.1-rc 系列+)。无该服务的旧宿主上,本插件(含代理路由功能)不会加载。

发布状态:本包已发布到 npm(0.2.0,自带内置 skill),方式一
dsh plugin add @aiwayds/dsh-llm-proxy 即可直接用。

卸载

dsh plugin --profile <profile> remove @aiwayds/dsh-llm-proxy

宿主自动清理:profile 清单里的 dsh.profile.bundles 条目被摘除,本包的 patch 层随之卸下。

插件自身不在磁盘上留下任何状态——不写任何文件,环境变量只读不写;进程级 undici/fetch 全局接管的拆除是对称且经过测试的(test/dispose.test.mjs 覆盖 dispose 后 globals 还原与 HMR 窗口),dispose 干净。

会留存的只有手动安装的 skill(见上文 Skill 一节),需手工删除:

rm ~/.dsh/skills/dsh-llm-proxy-config # 手动拷贝/软链的副本
rm -r ~/.agents/skills/dsh-llm-proxy-config # npx skills add 装出的条目

Skill(内置使用指南)

本仓库自带一个 dsh skill(skills/dsh-llm-proxy-config/SKILL.md):面向 agent 的配置 + 排障使用指南,frontmatter description 内嵌触发词(dsh 代理、HTTP 代理、LLM 分流、llmProxy、 407、CONNECT 挂起、SOCKS、NODE_USE_ENV_PROXY 等),进入每会话模型目录用于路由。三种获取方式:

1. 随插件自动注册(零操作):通过 npm 包或 bundle(cordis.patch.yml)挂载本插件后, skill 随插件 apply() 自动注册——进入每会话 skill 目录(<available_skills>, frontmatter description 供模型路由),/dsh-llm-proxy 手势可直接调出指南(已实测可用)。
2. GitHub 安装(不装插件,只要 skill):

npx skills add fan56/dsh-llm-proxy # 安装到 ~/.agents/skills/

3. 手动:git clone 本仓库后,把 skills/dsh-llm-proxy-config/ 拷贝或软链到 ~/.dsh/skills/:

git clone https://github.com/fan56/dsh-llm-proxy.git
ln -s "$(pwd)/dsh-llm-proxy/skills/dsh-llm-proxy-config" ~/.dsh/skills/dsh-llm-proxy-config

⚠️ 铁律:本包的 @deepseek-ai/ 只声明在 peerDependencies (devDependencies 保留供本地构建)。请勿把它们挪进 dependencies——那会装出第二份 cordis 闭包,导致双实例崩溃(详见 dsh 生态 link-dsh-closure 机制)。

配置入口(插件 entry config / 设置页)

dsh 0.1.7 起,插件配置 = 插件声明的 static Config schema(声明即注册):本插件为
entry dsh-llm-proxy 声明了 enabled / systemMode / llmProxy 三个字段且全部
.volatile(),宿主据此在设置页生成表单,用户值落在 profile patch 里该 entry 的
config: 段;设置页改动免重启热生效(volatile-only 写入不重挂插件,本插件原地重建 router,值未变的写入自动跳过重建)。

profile 的 cordis.patch.yml(设置页写入也落在这里):
- id: dsh-llm-proxy
config:
enabled: true # 总开关,默认 true;false 时完全不动全局 systemMode: env # 'env'(默认,读环境变量)| 'off'(全部直连,仅 llmProxy 生效)yaml llmProxy: # 匹配列表,按顺序首条命中生效
- match: "api.deepseek.org" # 域名精确匹配(含任意端口) proxy: "http://127.0.0.1:7890"
- match: ".volces.com" # 通配:volces.com 本身 + 任意子域 proxy: "http://127.0.0.1:7891"
- match: "https://api.openai.com" # 完整 origin 精确匹配(大小写不敏感) proxy: "http://127.0.0.1:7892"

要点:

- 旧 settings.yaml 的 dsh-llm-proxy: 段自动迁移:0.1.7 宿主启动时把旧 settings.yaml 各段按「段名 = entry id」一次性导入 profile patch。本插件旧段名与 entry id 同为 dsh-llm-proxy、三个键都在 volatile Config 里,存量用户值零手工迁移(导入后旧文件被宿主改名为 settings.yaml.imported)。
- entry id 必须是 dsh-llm-proxy(bundle patch insert 的 id)。旧 settings.yaml 段名写成其他键(如 llm-proxy:)不会被导入,静默失效。
- 编辑 热生效:volatile-only 改动不重挂插件,插件拆掉旧 router、按新配置原地重建,无需重启。
- bundle entry config(cordis.patch.yml insert 行的 config:)作为 base 层仍然有效; profile patch/设置页值覆盖其上的同名键。
- 未挂载 settings 服务的宿主上,事件订阅自动休眠,entry config 照常生效。

match 规则

| 写法 | 含义 |
| api.example.com | host 精确匹配,任意端口都算命中 |
| api.example.com:8443 | host + 显式端口必须相等(https 默认 443 / http 默认 80 会补全比较) |
| .example.com | example.com 本身及任意层级子域 |
| https://api.example.com | 完整 origin 精确匹配(scheme 也要一致) |

匹配全部大小写不敏感、纯词法比较(无 DNS/IO)。IDN 域名在匹配前统一归一化为 punycode (中文.com 与 xn--fiq228c.com 两种写法等价,pattern 和 origin 两侧对称处理)。
实现为纯函数(src/match.ts),有完整单测。

proxy 协议限制

undici ProxyAgent 只支持 http:// 与 https:// 上游代理。
socks5:// 等 schema 在启动校验时直接报错(fail-fast),README 明说:SOCKS 不支持。

实现要点

- service start 时创建自定义 undici 路由 Dispatcher 并 setGlobalDispatcher(router); router 的 dispatch() 按 origin 分流:命中 → 该条的 ProxyAgent(按 URL 缓存复用);未命中 → 共享 EnvHttpProxyAgent(或 systemMode: off 时直连 Agent)。
- 照抄 pi-src 生产模板的防御细节:
- undici.install?.() 防 Node 内建 fetch 与 npm undici 版本偏斜 bug (Node 26 bundled fetch 经 npm undici dispatcher 不解压导致 response.json() 失败);用 pristine/owned 双守卫只在未被替换时 install。
- undici Client 中途断流会发裸 error 事件导致 unhandled crash——对本插件创建的每个 dispatcher 挂 EventEmitter 级 error 兜底 listener(与 pi-src http-dispatcher.ts 同款机制,覆盖进程内所有由本插件创建的 dispatcher)。
- HMR 重载窗口闭环:接管状态(原始 dispatcher/fetch + 活跃层列表)存放在 Symbol.for 共享槽位,跨模块副本可见。cordis Group.update 先 create 后 remove、 vendor HMR 先 re-import 后 dispose——新版 apply 可能发生在旧版 dispose 之前; dispose 只摘除自己那一层,把 globals 还给最近的存活层,绝不打掉别人的路由器。
- dispose(cordis effect 语义)时非阻塞拆除:先把 globals 还回去(新请求立即不再进入旧 router),再后台 fire-and-forget 地 close() 排空在途请求——长 LLM 流式响应不会拖住热重载。
- proxy URL 中可能出现的 userinfo 凭证(user:pass@host)在日志/error message 里一律脱敏为 **@host。
- 与 NODE_USE_ENV_PROXY 官方姿势的关系:本插件是等价替代——无需该变量、无需重启,对新请求即时生效,且额外提供 LLM 分流能力。

手动 e2e 验证(CI 无网,不做真实请求测试) bash

1. 配一个本地回显代理(如 clash/mihomo 的 mixed-port 7890)

export HTTPS_PROXY=http://127.0.0.1:7890

2. 给 dsh-llm-proxy entry 配 llmProxy(设置页/profile patch),把某个 API 域名指到另一端口

3. 启动 dsh 后发起对话,观察代理访问日志:

- llmProxy 命中域名的请求出现在该条代理日志里

- 其他外网请求出现在 7890 日志里

4. unset HTTPS_PROXY 重启 → 只有 llmProxy 命中的流量走代理,其余直连

开发 bash
npm run build # tsc -> lib/
npm run check # tsc --noEmit
npm test # node --test(match / router 探针分流 / config 边界 / skill 注册 /

dispose 对称性 / e2e 全链路:apply → 全局 fetch 分流 → dispose 恢复 + HMR 窗口回归)
预览分享卡片长什么样 ↓fan56/dsh-llm-proxy
分享 · DeepSeek Harness Hub
fan56/dsh-llm-proxy

按域名把 LLM 请求分流到指定代理,其余走系统代理

回到顶部