按 Provider 分配代理与客户端指纹,绕开中转站拦截
30.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/20 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/dyh886/dsh-llm-net-router.git状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
生态应用
桌面端 / Web 外壳,不以 dsh plugin add 安装
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 11 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🟢 扫描:未发现敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。
发起外部网络请求
查看扫描证据(共 5 处,展示前 5 条)
- 发起网络请求client.js:94
const response = await fetch(`${apiBase()}${API_PATH}`, { - 发起网络请求lib/router.js:170
const wrapped = async function fetch(input, init) { - 发起网络请求lib/httpclient.js:451
const request = http.request({ - 发起网络请求lib/httpclient.js:469
const request = http.request({ method, path, headers, host: target.hostname, port: targetPort, ...(signal ? { signal } : {}) }); - 发起网络请求lib/httpclient.js:477
const request = https.request({ method, path, headers, host: target.hostname, port: targetPort, servername: target.hostname, ...(signal ? { signal } :
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
按服务商分流出口流量并控制客户端指纹,DeepSeek Harness 支持按主机走代理或直连,并在请求边界重写客户端请求头。
README
由 DeepSeek 最新模型翻译生成dsh-llm-net-router
给 DeepSeek Harness 补一层它自己表达不了的出网策略:按 Provider 选代理,按 Provider 选「我是谁」。
设置页位置:设置 → 网络路由(settings.section,order 72)。
零运行时依赖 · 只包装 globalThis.fetch · 卸载即恢复 · 规则文件原子落盘
目录
- 它解决的两件事
- 界面
- 安装
- 配置
- 指纹
- 1M 上下文(context1m)
- 命令行
- 探测结果的读法
- 配置体检与匹配漂移
- 实现要点
- 已知边界
- 开发与测试
- 项目结构
- 贡献
- 许可证
它解决的两件事
1. 代理策略是进程级的,但中转站不是一个一个来的
Harness 官方的 @deepseek-ai/dsh-http-proxy 从启动环境与 $DSH_HOME/.env 解析一份策略,装成 undici 的全局 dispatcher(它自己的文档写明「不读取操作系统代理设置」,也说明策略只有一份)。
于是「中转站 A 必须走代理、中转站 B 必须直连、本地网关 C 必须别碰代理」这件事,只能靠 NO_PROXY 打补丁——补丁是全局的、按地址的,不是按 Provider 的,而且改一次要重启。
展开完整文档(README 剩余 1 节)
dsh-llm-net-router
给 DeepSeek Harness 补一层它自己表达不了的出网策略:按 Provider 选代理,按 Provider 选「我是谁」。
设置页位置:设置 → 网络路由(settings.section,order 72)。
零运行时依赖 · 只包装 globalThis.fetch · 卸载即恢复 · 规则文件原子落盘
目录
- 它解决的两件事
- 界面
- 安装
- 配置
- 指纹
- 1M 上下文(context1m)
- 命令行
- 探测结果的读法
- 配置体检与匹配漂移
- 实现要点
- 已知边界
- 开发与测试
- 项目结构
- 贡献
- 许可证
它解决的两件事
1. 代理策略是进程级的,但中转站不是一个一个来的
Harness 官方的 @deepseek-ai/dsh-http-proxy 从启动环境与 $DSH_HOME/.env 解析一份策略,装成 undici 的全局 dispatcher(它自己的文档写明「不读取操作系统代理设置」,也说明策略只有一份)。
于是「中转站 A 必须走代理、中转站 B 必须直连、本地网关 C 必须别碰代理」这件事,只能靠 NO_PROXY 打补丁——补丁是全局的、按地址的,不是按 Provider 的,而且改一次要重启。
本插件按 request 的目标 host 决策,运行中随时改:
| 模式 | 含义 |
| 跟随全局 | 交回 Harness 原生策略(保持既有行为,最保守的默认值) |
| 强制直连 | 这条 host 不走任何代理 |
| 走代理 | 这条 host 走指定的 地址:端口(HTTP / HTTPS / SOCKS5 都支持) |
2. user-agent 在配置层改不掉,而中转站看的正是它
很多中转站(New API / one-api 系)在网关层挑客户端,请求头里既没有 originator: codex_cli_rs 也没有官方 CLI 的 UA 时直接回:
{"error":{"message":"unauthorized client detected, ..."},"type":"unauthorized_client_error"}
Harness 侧有两处天然做不到:
1. user-agent 由 dsh-llm 的 attributionHeaders() 写死(deepseek-harness/<版本>), provider profile 里的同名字段会在 requestHeaders() 合并时被当作冲突过滤掉——配置里写 headers: { user-agent: ... } 是不生效的。
2. originator 之类的头虽然能通过 profile headers 加上,但要改 YAML、要重启,而且没法按站点分别试。
本插件在传输边界解决:包装 globalThis.fetch(pi-ai 提供方栈与模型发现都在调用点解析它),在「最终请求头已经拼完」之后改写它。不碰 provider 的 baseURL,不起本地中继端口,卸载即恢复。
界面
设置 → 网络路由
- 默认策略:没被任何 Provider 规则命中的地址怎么走。
- 代理池:本地代理地址 + 端口,可逐个「探测」(TCP 通不通 / 隧道能不能真的把请求送出去)。
- Provider 规则:每个已配置的 provider 一行——通路(跟随全局 / 强制直连 / 走代理)、代理地址、客户端指纹(不伪装 / Codex CLI / Claude Code / 自定义)、「矩阵探测」按钮。
指纹选 Claude Code 时旁边会多出一个 1M 勾选项(只有 Claude Code 有,见下文「1M 上下文」)。
- 用真实推理端点校验(复选框):默认只探 GET /models;勾上后改探真实的 POST /chat/completions(max_tokens: 1)。
很多中转站的客户端校验只挂在这里,只探目录接口会得到「这个站不需要伪装」的假结论。
- 最近路由决策:只记录真正被本插件影响过的请求,含命中规则、通路、指纹、1M 标记、结果与耗时。
矩阵探测一次回答两个问题:这条地址在「直连 / 每个代理」×「不伪装 / Codex / Claude Code(/ Claude Code + 1M)」里哪一种能过,以及过不去到底是被客户端校验拦下、被 1M 闸门拦下还是链路/密钥问题。结论可以一键「采纳并写入规则」(1M 勾选项会一起落盘)。探完会对选中的姿势复测一次,复测不过的推荐会被降级。
安装
要求:DeepSeek Harness 0.2.0-rc.2(dshTarget)、Node.js >= 18。插件零运行时依赖,不需要 npm install 装依赖。
方式 A:从 GitHub clone(推荐,便于更新)
$dshHome = $env:DSH_HOME; if (-not $dshHome) { $dshHome = "$env:USERPROFILE\.dsh" }
1) clone 到插件目录powershell
git clone https://github.com/dyh886/dsh-llm-net-router.git "$dshHome\plugins\dsh-llm-net-router"
2) 挂到 profile 的 node_modules(junction,不需要管理员权限)
New-Item -ItemType Junction
-Path "$dshHome\profiles\desktop\node_modules\dsh-llm-net-router"
-Target "$dshHome\plugins\dsh-llm-net-router"
3) 在 profiles\desktop\package.json 里:
- dependencies 加 "dsh-llm-net-router": "file:../../plugins/dsh-llm-net-router"
- dsh.profile.bundles 加 "dsh-llm-net-router"
4) 重启 Harness(桌面端:托盘退出 → 重新启动)
方式 B:交给官方插件入口 powershell
dsh plugin --profile desktop add file:$dshHome/plugins/dsh-llm-net-router
或直接从 GitHub 安装
dsh plugin --profile desktop add github:dyh/dsh-llm-net-router
更新 powershell
git -C "$dshHome\plugins\dsh-llm-net-router" pull --ff-only
重启 Harness
卸载
从 dsh.profile.bundles 与 dependencies 里删掉那两行、删掉 junction、重启。规则文件($DSH_HOME/net-router/rules.json)可以留着,也可以直接删。
配置
profile 的 cordis.patch.yml 里,与其它插件行同级: yaml
- id: llm-net-router
config:
enabled: true
defaultMode: inherit # inherit | direct | proxy
defaultProxy: '' # defaultMode=proxy 时用 proxyPool: # 界面下拉与"探测全部"的候选
- http://127.0.0.1:7890
- socks5://127.0.0.1:1080
probeTimeoutMs: 8000 # 目录接口探测的单行超时 inferenceTimeoutMs: 30000 # 推理端点探测的单行超时(真实推理要排队,8 秒必然误判) probePath: '' # 覆盖探测路径 decisionLogSize: 60 rulesFile: 'net-router/rules.json'
providers: {} # 可选的声明式规则(见下)
界面里保存的规则写在 $DSH_HOME/net-router/rules.json (先写临时文件再 rename 的原子替换;容忍 BOM,记事本手改过也还能读): json { "version": 1,
"defaults": { "mode": "inherit", "proxy": "" },
"proxies": ["http://127.0.0.1:7890"],
"providers": {
"agentrouter": {
"enabled": true,
"match": ["agentrouter.org"],
"mode": "proxy",
"proxy": "http://127.0.0.1:7890",
"fingerprint": "codex",
"codexVersion": "",
"claudeVersion": "",
"headers": { "x-app": "cli" },
"note": ""
}
}
}
match 支持 example.com、.example.com、.example.com、host:port、;命中优先级是"匹配串更长者优先",同长度按 provider id 字典序(结果稳定可复现)。
patch 里的 providers: 与文件里的规则会合并,文件优先——所以既能在 YAML 里做声明式种子,也能在界面上随手改而不动 YAML。
指纹
| id | 发出去的头 |
| none | 不改写(Harness 原生 attribution UA) |
| codex | originator: codex_cli_rs、user-agent: codex_cli_rs/<版本> (Windows 11; x86_64) |
| claude-code | user-agent: claude-cli/<版本> (external, cli)、x-app: cli、anthropic-dangerous-direct-browser-access: true;勾了 1M 再追加 anthropic-beta: context-1m-2025-08-07 |
| custom | 界面里手写的任意头(JSON) |
codexVersion / claudeVersion 可以单独覆盖版本号;headers 里的键会覆盖内置值——比如只想要 originator、不想换 UA,就把指纹设为 codex 再用 headers 把 user-agent 改回 Harness 自己的值。
1M 上下文(context1m)
症状:请求 1M 上下文的模型时上游回 json {"error":"1m 上下文已经全量可用,请启用 1m 上下文后重试","type":"error"}
原因:这是中转站(anyrouter 系)在网关层做的闸门,判据是 anthropic-beta 里有没有 context-1m-2025-08-07。它和「客户端校验」(401 unauthorized client)是两回事:
| 现象 | 判据 | 修法 |
| 401 unauthorized client detected | 请求头里没有官方 CLI 的 UA / originator | 换指纹(Codex / Claude Code) |
| 400「请启用 1m 上下文」 | anthropic-beta 缺 context-1m-2025-08-07 | 勾上 Claude Code 旁边的 1M |
两个实测结论(anyrouter,2026-10):
- 改模型名没用。 claude --model 'xxx[1m]' 里的 [1m] 是官方 CLI 自己剥掉的语法糖, CLI 看到后缀才会去加那个 beta;直接把 [1m] 写进请求的 model 字段,网关照样回 400。
- 只影响 1M 模型。 claude-haiku-4-5 这类非 1M 模型带不带这个 beta 都是 200; sonnet-4-5 / opus-4-7 / fable-5-1-reversed 不带就是 400。
实现上 context1m 是规则里的一个布尔字段,只在 fingerprint: claude-code 时生效(其它指纹下这个 beta 没有可写的位置,界面也不会显示那个勾选项): json {
"anyrouter": {
"enabled": true,
"match": ["anyrouter.top"],
"mode": "proxy",
"proxy": "http://127.0.0.1:7890",
"fingerprint": "claude-code",
"context1m": true,
"headers": {},
"note": "1M 模型必须带 context-1m beta" } }
anthropic-beta 是多值累加的头:插件把「请求上原有的 beta」+「headers 里显式写的 beta」+ context-1m-2025-08-07 去重合并,而不是整个覆盖——否则打开 1M 会把 pi-ai 自己带的 thinking / tool-streaming beta 静默关掉,表现成「1M 能用了,别的功能莫名退化」。
命令行 powershell
驱动运行中的插件(复用界面里那份规则、凭据与代理池)
node scripts/probe-cli.mjs --api http://127.0.0.1:19387 --provider primerelay
node scripts/probe-cli.mjs --api http://127.0.0.1:19387 --provider primerelay --endpoint inference
node scripts/probe-cli.mjs --api http://127.0.0.1:19387 --state
独立探测(Harness 没跑着也行)
node scripts/probe-cli.mjs https://relay.example.com/v1
--api-key-ref RELAY_API_KEY --endpoint inference --model gpt-6-sol
--proxies 127.0.0.1:7890,socks5://127.0.0.1:1080
--api-key-ref NAME 会去 $DSH_HOME/.credentials.yaml 的 refs: 段取值,不必把 key 贴进 shell 历史。
探测结果的读法
| 结论 | 含义 | 下一步 |
| ok | 2xx | — |
| client-gate | 响应体里有 unauthorized client 等标识 | 换指纹(Codex / Claude Code) |
| context-gate | 400「请启用 1m 上下文」/ context-1m | 勾上 Claude Code 旁边的 1M |
| auth | 401/403 但不是客户端校验 | 检查 apiKeyEnv 指向的 key |
| model-error | 说模型不存在 | 换模型(命令行的 --model / 界面里的 provider 第一个模型) |
| timeout | 未知,不等于被拦 | 调大 inferenceTimeoutMs 后复测 |
| network-error | 连接层失败 | 检查代理地址、端口,或该地址是否该走另一个代理 |
| http-error | 其它状态码 | 看细节里的上游响应体 |
两条刻意的判定原则(都是实测踩出来的):
- 超时不等于被拦。 慢站上 8 秒超时会把「不伪装」和「Codex」两行判成失败,只剩第三行在 7.5 秒通过,于是得出「必须用 Claude Code 指纹」的假结论。
现在:只有观测到明确的 client-gate 才把「必须伪装」说成结论,超时行只作为 caveat 提示。
- 同一通路上「有的行成功、有的行直接断连」会单独提示。
有些中转对特定指纹不返回 401,而是直接掐连接——那看起来像网络抖动,其实是拒绝。
- 「指纹生效了、问题在上游」是一种独立结论。 不伪装被 unauthorized client 拦下,而带指纹的姿势拿到了另一种错误(400 协议不对、402 额度用完),说明客户端校验已经过了,剩下的问题与伪装无关。工具会把上游原文带出来,而不是笼统地说「三种指纹都没放行」。
- 1M 闸门回的是 400,不是 401。 它看起来像「请求本身不合法」,所以必须单独归一类(context-gate);否则会得出「三种指纹都没放行」的假结论,然后去改指纹——改多久都没用。
配置体检与匹配漂移
两个「探测全绿、调用照错」的坑,插件现在会主动说出来:
1. baseURL 里的空白(真实事故)
baseURL = "https://host/v1␠"(尾部多了个空格)时:
- pi-ai 用字符串拼接得到 https://host/v1␠/chat/completions,WHATWG 解析把空格编码成 %20,网关收到 /v1 /chat/completions → 404 {"message":"Invalid URL (POST /v1 /chat/completions)"}。
- 探测路径曾经是 baseURL.trim() + '/models',于是探测全绿、真实调用全炸。
现在两边共用同一条归一化(normalizeRequestUrl):把「紧贴 / 或位于路径末尾」的空白抹掉(段内部的空白不动,那可能是真文件名),探测与真实请求走的是同一段代码。
同时界面会给出红字告警,指出配置本身该修——兜底是兜底,脏值仍然是脏值。
2. 规则与 host 的漂移
规则里的 match 是按当时的 host 生成的。用户把 baseURL 从 http://127.0.0.1:17890/v1 改成 https://agentrouter.org/v1 之后,旧规则会静默失效:请求既没走代理也没带指纹,而界面上看起来「规则已经配好了」(症状特征:决策记录里 matched 一直是 0)。
现在 state 会为每个 provider 计算 rule.matchesCurrentHost / rule.stale,界面显示「匹配已失效」并给一个同步匹配按钮(rules.syncHost); apply.recommend 也会把当前 host 并进 match,所以重跑一次探测+采纳也能修好。
顺带说明一个设计边界:规则按 host 匹配,所以同一个 host 上的两个 provider 路由共享同一套通路与指纹(同一站点本来就该用同一套身份)。需要不同协议时,用 api 字段区分路由,而不是规则。
实现要点
| 文件 | 职责 |
| lib/index.js | 插件入口:配置收敛、规则装载、安装拦截、注册 /llm-net-router/api |
| lib/router.js | globalThis.fetch 包装:按 host 匹配规则 → 改写头 + 选通路;决策环与计数 |
| lib/httpclient.js | 无依赖传输层:直连 / HTTP(S) 代理(绝对 URI 与 CONNECT)/ SOCKS5,返回标准 Response |
| lib/probe.js | 探测引擎:代理可用性 + 「通路 × 指纹」矩阵 + 推荐(含复测) |
| lib/store.js | 规则归一化、匹配、原子持久化 |
| lib/fingerprints.js | 指纹表与响应分类 |
| lib/api.js | 宿主 API(界面唯一通道)+ provider 发现 + 凭据软解析 |
| client.js | 浏览器半侧:window.__ModuleLoader__ bundle,注册 settings.section |
几个刻意的取舍:
- 不改全局 dispatcher,而是自带传输层。 插件包从 profile 的 node_modules 解析, Harness 自身的 undici 在 app.asar 里,裸名 import 是 ERR_MODULE_NOT_FOUND;而「同一个进程里 A 站走代理 B 站直连」用全局 dispatcher 也表达不了。自己拿 socket,才能按 host 决策。
代价是这里要自己实现代理握手(已覆盖 HTTP(S) 与 SOCKS5,含认证、TLS、重定向、AbortSignal)。
- 只实现 HTTP/1.1。 模型 API 都是 HTTP/1.1 上的 SSE,不需要 h2 多路复用。
- 失败回退不丢伪装。 自有通路失败时回退原生 fetch,但改写后的头仍然保留。
- 决策日志不记 query。 只记 origin+path,避免把 ?api_key= 之类的写进环形缓冲。
- mode: inherit + 指纹 = 与其它传输插件共存的方式。 这个组合下插件只改头、不接管传输,因此可以和别的出网插件叠加使用(见下)。
已知边界
- FormData / Blob 请求体不承载(自有通路会回退原生 fetch);模型 API 用不到这两种形状。
- 不实现 HTTP/2、不实现 PAC、不实现 SOCKS4。 代理地址给 socks4:// 会报错而不是静默直连。
- 只管 globalThis.fetch。 node:http / node:https 直连的流量(例如遥测)不受影响。
- 界面 API 走同源校验:本机回环放行,非回环要求 Origin 与 Host 同源。
- 同一进程里不要同时挂多个接管传输的插件。 本插件的安装/卸载只在自己仍是最外层包装时还原,不会踩掉后来者;但两个都「接管 fetch」的插件叠加时,谁在谁外面会决定谁说了算。
与其它出网插件共存时,把本插件的规则设成 mode: inherit + 需要的那套指纹。
开发与测试 powershell
npm run check # 所有 .js/.mjs 过一遍 node --check(零依赖)
npm run test:unit # 离线用例
npm test # 加上联网集成用例(没有代理时自动 skip)
联网集成:验证 HTTPS 经 HTTP 代理 CONNECT 再 TLS 这条真通路
$env:DSH_NET_ROUTER_IT = "1"
$env:DSH_NET_ROUTER_PROXY = "http://127.0.0.1:7890"
npm run test:integration
CI 覆盖 Node 18 / 20 / 22 × Ubuntu / Windows,并跑 npm pack --dry-run 检查发布内容。
离线用例覆盖:规则归一化/匹配/原子落盘(含 BOM 容忍)、直连与三种代理通路的真 socket 往返、 AbortSignal/重定向/认证、拦截器的改写与回退与幂等安装、探测分类与推荐的两条判定原则、以及客户端 bundle 的加载契约与真实渲染(自带一个迷你 React,见 test/fixtures/mini-react.mjs)。
项目结构 text dsh-llm-net-router/ ├── lib/ │ ├── index.js # 插件入口:配置、规则合并、安装拦截、回环 API │ ├── router.js # globalThis.fetch 包装与决策环 │ ├── httpclient.js # 零依赖传输层(直连 / HTTP(S) / SOCKS5) │ ├── probe.js # 矩阵探测、推理端点、分类与推荐 │ ├── store.js # 规则归一化、host 匹配、原子持久化 │ ├── fingerprints.js # 指纹表与响应分类 │ └── api.js # 宿主 API + provider 发现 + 凭据软解析 ├── client.js # 浏览器半侧:注册「设置 → 网络路由」 ├── scripts/ │ ├── probe-cli.mjs # 命令行探测 │ └── check.mjs # 语法自检 ├── test/ # node:test,零依赖;含迷你 React 渲染夹具 ├── docs/ARCHITECTURE.md # 数据流、决策细节、探测分类、安全边界 ├── examples/ # patch 与 rules.json 示例 ├── cordis.patch.yml # 插件 bundle 声明与默认配置 └── package.json
更细的模块职责与请求生命周期见 docs/ARCHITECTURE.md。
贡献
见 CONTRIBUTING.md。安全问题的报告方式见 SECURITY.md。变更记录见 CHANGELOG.md。
许可证
MIT © 2026 dyh预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
dyh886/dsh-llm-net-router
按 Provider 分配代理与客户端指纹,绕开中转站拦截
相关插件 · 模型接入
API 中继安全审计toby-bridges/api-relay-audit
面向 AI API 中转与 LLM 代理的本地安全审计。
★ 897已汉化DSH
69.4
免费搜索插件DDDMUC/dsh-free-search
DeepSeek Harness 免费搜索插件 —— 无需 API key,零成本,多引擎可切换。 一个给…
★ 761已汉化DSH
68.7
免费联网搜索liustack/modsearch
在官方 App 里模型能联网,切到 API 就不能了。ModSearch 把联网补回来:网页搜索、X…
★ 863已汉化DSH
68.5
OpenCode 免费模型FishBottle7/opencode2dsh
在 DSHDeepSeek Harness里原生使用 OpenCode Zen 的免费匿名模型。
★ 379已汉化DSH
67.8
AI 模型网关tingly-dev/tingly-box
Tingly Box Web UI 演示
★ 373已汉化DSH
67.4
WorkBuddy 账号池XDTrees/dsh-xdpool
把你能白嫖的模型额度接进 DSH 同一个池:WorkBuddy 多账号自动容错、十三家扫码登录、76…
★ 359已汉化DSH
67.1