dsh-clinepass-pin
给 DeepSeek Harness 的 cline-pass 路由硬钉一个上游 Provider(默认第一方 deepseek)。
做法是在发往网关的 JSON body 里加一个字段:
"providerOptions": { "gateway": { "only": ["deepseek"] } }
配套 dsh-clinepass 使用:那个插件只观察网关真实用了谁,本插件负责让它一直是 DeepSeek。
它解决什么
api.cline.bot 是个多上游网关。对日期快照 cline-pass/deepseek-v4.1-flash-1017,它默认在十几个渠道里挑一个(deepseek、pareto、fireworks、alibaba、baseten …),同一个进程里几分钟就能从 pareto 飘到 deepseek;稳定别名 cline-pass/deepseek-v4.1-flash 甚至可能落到匿名回退网 openai-compatible-private(contributor 档:数据可能用于训练、无 ZDR)。
实测(2026-10-19,同一把 key):
| 请求 | finalProvider |
fallbacksAvailable |
|---|---|---|
| 不钉 | deepseek,但抓到过 pareto | 14 |
only: ["deepseek"] |
deepseek | 0 |
only: ["__no_such_upstream__"] |
— | 500 No available providers match the 'only' filter |
负控制那一行说明:1017 这条快照上网关真的执行 only。稳定别名则无视它(only 无效),所以 models 里别写别名。
为什么不走官方那条路
官方做法是 profile 里写 compat.vercelGatewayRouting.only,再把 pi-ai 的 COMPLETIONS_COMPAT_GATE.vercelGatewayRouting 从 "withhold" 改成 "offer"。在 Desktop 版上这条路是死的:
- 门禁代码在
resources/app.asar/dsh/node_modules/@deepseek-ai/dsh-llm-pi-ai/lib/index.js里,而 asar header 对每个文件都带 SHA256integrity,就地改字节过不了完整性校验,且每次应用更新都会被覆盖; - 随包脚本
patch-gateway-routing.mjs找的是<DSH_HOME>/profiles/<profile>/node_modules/@deepseek-ai/dsh-llm-pi-ai/...,Desktop 安装里这个路径不存在(profiles/node_modules下的那份只是指向全局 npm 安装的 junction,不是运行中 app 加载的那份)。
所以本插件改在比配置门禁更低一层的 globalThis.fetch 上 —— 正好是 dsh-clinepass 观察用的同一个缝。
安装
环境:dsh ≥ 0.2.0-rc.2、node ≥ 20。装完要重启 dsh。
方式 A:从 GitHub 装
dsh plugin --profile desktop add github:liujuntao123/dsh-clinepass-pin
它声明了 dsh.bundle.patch,所以包被追加进 profile 的 dsh.profile.bundles 后配置行随包生效,不用手写。
挂载顺序:如果同时用
dsh-clinepass,请把这行挪到dsh-clinepass之前。顺序决定谁在最外层:本插件先装、dsh-clinepass后装,后者才继续owns()着globalThis.fetch,状态文件里hook才会是installed。反过来钉选照样生效,但dsh-clinepass的状态文件会写hook: "foreign"并告警。
方式 B:离线手装(不用 pnpm)
$prof = "$env:USERPROFILE\.dsh\profiles\desktop"
Copy-Item . "$prof\plugins\dsh-clinepass-pin" -Recurse
New-Item -ItemType Junction -Path "$prof\node_modules\dsh-clinepass-pin" -Target "$prof\plugins\dsh-clinepass-pin"
然后在 $prof\package.json 的 dsh.profile.bundles 里加一行 "dsh-clinepass-pin"(同样建议排在 dsh-clinepass 之前),重启。
配置
| 键 | 默认 | 作用 |
|---|---|---|
upstream |
https://api.cline.bot |
网关 origin;路由自己拼 /api/v1/chat/completions |
models |
['cline-pass/deepseek-v4.1-flash-1017'] |
只有这些 model id 会被加钉 |
only |
['deepseek'] |
允许的上游列表,必须非空(空列表会让网关 500) |
statusFile |
$DSH_HOME/dsh-clinepass-pin-status.json |
运行证据;false 关掉 |
log |
true |
宿主日志 |
改配置写在 profile 的 cordis.patch.yml 里(patch 是整行替换 config):
- id: clinepass-pin
config:
models: [cline-pass/deepseek-v4.1-flash-1017]
only: [deepseek]
行为边界
- 只处理
POST <upstream>/api/v1/chat/completions,且 body 是 JSON 字符串、model在models里;其它请求原样透传。 - 只加
providerOptions.gateway.only;已存在非空only时不动(让 profile 侧 compat 优先)。已有的gateway.order会保留。 - 任何解析/形状异常都回落成原请求 —— 它不该成为一次对话失败的原因(
counters.errors可见)。 - 不缓冲流、不改 headers、不重试、不加请求。
验证
npm test # 离线,不需要网络也不需要 dsh
跑起来之后:
Get-Content "$env:USERPROFILE\.dsh\dsh-clinepass-pin-status.json"
{ "hook": "installed", "counters": { "pinned": 12, "skippedRequestShape": 0, "errors": 0 } }
pinned 在涨 = 请求确实被改写了;hook 不是 installed = 有别的插件先占了 globalThis.fetch。
代价
硬钉就是硬钉:deepseek 不可用时请求直接失败(500),不会回落到 pareto / fireworks / 匿名网。想要「优先 deepseek、挂了再回落」得用网关的 gateway.order(本插件未暴露;only 是唯一实测被 1017 执行的形状)。
卸载
从 dsh.profile.bundles 去掉 dsh-clinepass-pin → 删 node_modules\dsh-clinepass-pin junction → 删插件目录 → 重启。
English
dsh-clinepass-pin pins the cline-pass route in DeepSeek Harness to a single upstream provider by adding providerOptions.gateway.only to the outgoing chat body. It exists because the documented route — a model-level compat.vercelGatewayRouting.only plus flipping pi-ai's COMPLETIONS_COMPAT_GATE — cannot be applied to the desktop build, where that gate lives inside an integrity-checked app.asar.
It is the write-side companion to dsh-clinepass, which only observes. It touches nothing but the JSON body of matching chat requests, never buffers a stream, never retries, and falls through to the untouched request on any surprise. npm test runs offline.
Trade-off: a hard pin means requests fail (500) when the pinned upstream is unavailable instead of falling back.
License
MIT
No comments yet. Be the first to write one.