前言¶
在 DeepSeek Harness(DSH)里,一个网关往往挂多种模型,背后可能是不同上游。同一 host、同一 API key、同一请求形态下,有的模型在本地区正常,有的会因区域限制、限速或只能经公司代理访问而失败——这与「隔壁模型」无关。
进程级环境变量代理按 host 路由:HTTP_PROXY 无法区分同一 host 上的不同模型。整网关走代理会把本来正常的模型一起拖进代理;完全不设代理则受限模型一直失败。下面介绍社区插件 dsh-model-proxy,它从每次请求体里读出 model 字段,只把你指定的模型名发往代理,其余保持原有出站路径。
这是什么¶
dsh-model-proxy(GitHub:windwhiterain/dsh-model-proxy,维护者 windwhiterain)是 DSH 的 bundle 插件,分类为模型推理。一句话:在 Harness 内做按模型名的出站代理路由,并在代理 TCP 不可达时可选地回退到插件加载前的直连路径。
插件以当前 dsh 进程权限运行;安装前应自行查看仓库源码与 MIT 许可证。社区目录页见 SkillHub 插件页,与 DeepSeek / 幻方无官方从属关系。
路由方式¶
DSH、pi-ai 与各类 provider SDK 通过 globalThis.fetch 发请求,最终落到 undici 的全局 dispatcher 槽位(Symbol.for('undici.globalDispatcher.1'))。本插件占用该槽位,按顺序决策:
- Origin:目标 host 不在
origins内,或请求无 body,则保持插件加载前的路由。 - 代理可达性:在开启
fallbackToDirect且代理host:portTCP 拒绝连接时,候选请求仍走上述「先前路由」,避免代理停掉后拖垮全进程调用。 - 模型名:读取 body 头部 JSON 的
"model"字段;若匹配models中的模式,则经proxy出站。
为解析模型名,插件只缓冲 body 的前若干 chunk,并在解析前 yield 已读 chunk,不改变 chunk 边界、背压与流式响应行为。配置里的 direct 指进程在装插件前已有的路由;若启动环境未设代理,则未匹配请求与未装插件时行为一致。
核心配置项¶
bundle 自带的 cordis.patch.yml 会插入 id: model-proxy 行;也可在 <DSH_HOME>/profiles/<profile>/cordis.patch.yml 热重载编辑,或通过 settings 层覆盖同 id。无效配置会导致激活失败,而不是静默走错路由。
- id: model-proxy
config:
enabled: true
proxy: http://127.0.0.1:10793
origins:
- opencode.ai
models:
- grok-*
- gpt-*-luna
fallbackToDirect: true
probeTimeoutMs: 300
probeCacheMs: 2000
peekBytes: 16384
| 字段 | 含义 |
|---|---|
enabled |
为 false 时不安装拦截,所有请求保持原路由 |
proxy |
匹配模型使用的代理端点,支持 http: / https: 及可选凭据 |
origins |
仅对这些 host 上的请求做模型匹配 |
models |
模型名模式,* 匹配任意字符段 |
fallbackToDirect |
代理不可连时,匹配请求改走先前路由 |
probeTimeoutMs |
可达性探测 TCP 连接超时 |
probeCacheMs |
探测结果缓存时长,突发请求只探一次 |
peekBytes |
为找 model 字段最多窥视的 body 字节数 |
安装与启用¶
在目标 profile 的 package.json(路径:<DSH_HOME>/profiles/<profile>/package.json)注册依赖与 bundle:
{
"dependencies": {
"dsh-model-proxy": "github:windwhiterain/dsh-model-proxy"
},
"dsh": {
"profile": {
"bundles": ["@deepseek-ai/dsh-base", "dsh-model-proxy"]
}
}
}
本地开发可用 link:C:/path/to/dsh-model-proxy 等形式。随后安装该 profile 并重启 dsh 一次;bundle 的 cordis.patch.yml 会写入默认 model-proxy 配置。
不要同时为 dsh 进程设置 HTTP_PROXY / HTTPS_PROXY。启动环境中的代理会让 dsh 安装进程级 dispatcher,本插件会把该路径当作「非代理模型」的保留路由,与「仅部分模型走代理」的目标相反。启动环境保持直连,由本插件单独处理需要代理的流量。
典型用法¶
同一网关内,部分模型需翻墙或区域代理¶
README 示例:OpenCode Go 在同一网关提供 grok-* 与 gpt-*-luna,在受限地区上游返回 unsupported_country_region_territory,而 GLM、Kimi、DeepSeek 等同 key 同 region 正常。用上文 YAML 只把两类受限模型经支持地区的代理发出,其余直连;关闭代理后,在 fallbackToDirect: true 下直连模型不受影响。
直接调该网关时,请求仍需带 x-opencode-session 头(否则 400 MissingSessionID),与是否启用本插件无关。
单一 provider 走公司代理¶
- id: model-proxy
config:
proxy: http://proxy.corp.example:3128
origins: [api.example.com]
models: ['*']
整段网关经不同出口,进程其他 host 不变¶
与上例相同,将 models 设为 ['*'];路由仍按请求决策,之后在别处新增的 host 不会被这一条连带代理。
诊断与自测¶
决策会写入宿主 logger,例如:
dsh-model-proxy: models grok-*, gpt-*-luna on opencode.ai via http://127.0.0.1:10793, direct while it is down
dsh-model-proxy: route=proxy via=http://127.0.0.1:10793 reason=model:grok-4.7
dsh-model-proxy: route=direct via=direct reason=model-other:glm-5.3-flash
dsh-model-proxy: route=direct via=direct reason=proxy-down
dsh-model-proxy: route=direct via=direct reason=origin
卸载时会恢复被替换的 dispatcher,便于后续插件占用同一槽位。
仓库提供 probe 脚本(需配置可读凭据):
node probe/smoke.mjs # 代理可达
node probe/smoke.mjs --proxy-down # 回退路径
适用场景与注意¶
适合谁:在同一 DSH 网关混用多模型、且只有部分模型需要独立出站代理的智能体或网关维护者;需要代理宕机时其余模型仍可用的部署。
进程内生效:路由逻辑只在 dsh 进程内生效;shell 工具拉起的 curl、git 等仍遵循启动环境,本插件刻意不修改后者。
模型名来源:仅从 body 读取 model;仅写在 header、或 model 出现在 peekBytes 之后的请求不会匹配,保持直连。
代理探测范围:探测回答「端口是否在监听」,不验证上游是否可用;代理能连但上游失败时,该请求仍会失败。
单条配置一个 proxy:不同模型族要走不同代理,需要多条 model-proxy 行(README 说明当前实现对此有约束,截断处未展开完整表述)。
依赖版本:插件将 undici 固定为 ~8.10.0;8.11.0 在 Node 24 下接管全局 dispatcher 时可能破坏 content-encoding 解码。与 @deepseek-ai/dsh-http-proxy 的 undici 约束同类,锁文件升级需谨慎并重跑 probe 断言。
兼容性:依赖 Node 自带 fetch 所暴露的全局 dispatcher 槽位;除「带 fetch 的 Node」外 README 未假定更细版本。
结尾¶
dsh-model-proxy 解决的是「同一 API host、不同模型不同可达性」下的出站问题:用模型名模式选择代理,可选在代理不可连时回退,并避免整进程 HTTP_PROXY 的一刀切副作用。目录与源码: