DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

BreakFree003 /

BreakFree003/dsh-clinepass-deepseekv4.1

Verified

Cline Pass for DeepSeek Harness (dsh): one pi-ai provider route pinned to the DeepSeek upstream channel, key entered on Settings → Models. Share-only snapshot — 分享性质,非长期维护。

★ 2 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@d59cd389

dsh-clinepass

把 Cline Pass 接进 DeepSeek Harness(dsh),并把每条请求钉死在 DeepSeek 官方渠道上(不回退)。 API key 在 设置 → 模型 里直接填,卡片上还带一条 5 小时 / 每周 / 每月 的用量条(显示剩余量)。默认不开任何本地端口。

Cline Pass for DeepSeek Harness. One provider route, pinned to the DeepSeek upstream channel, with the API key entered on Settings → Models — plus a usage card for the 5-hour, weekly and monthly windows.

分享性质,非长期维护项目。 这是作者自用插件的公开快照:按 MIT 原样提供, 不承诺持续维护,也不承诺跟进上游 dsh 的接口漂移。插件依赖 dsh 的内部行为 (pi-ai 每次请求新建 client、不传自己的 options.fetch,因此真正发请求的是全局 fetch);上游哪天改掉这一点,插件会静默失效——下面的状态文件 (hook: installed)就是为这件事准备的报警器。装之前建议先读「验证」一节。 能跑测试不代表上游永远不变。


它做了什么(架构)

四件事各由最合适的部分负责:

  1. 路由本身是一个普通的 pi-ai provider profile(llm-pi-ai.providers.cline-pass):OpenAI 兼容协议 + 一个存在凭据库里的 key。 正因为如此,它才会**原生出现在「设置 → 模型」**里 —— key 输入框、模型目录、地址都是 dsh 自带的界面,不需要自写 UI,也不需要一个可能随 dsh 升级而漂移的手写流式 adapter。

  2. 本插件只做一件事:给请求体加上渠道钉选字段。 Cline Pass 的钉选是请求体里的 providerOptions.gateway.only,而 dsh 刻意屏蔽了 pi-ai 的 openRouterRouting / vercelGatewayRouting 兼容开关 —— settings 里写不了。插件把这个字段加在出去的请求上,字节层面其它什么都不動,SSE / 工具调用 / 推理 / 用量 / 图片全部走 pi-ai 已验证的通路。

  3. 提示词里的模型名去前缀(0.6.2 起)。网关要求请求体的 model 是 type/model 形状(裸 id 会被拒: invalid model format. Expected format: modelType/model),而 dsh 把 catalog 的 models[].id 原样发给线上 —— 所以这条路由的 id 必须写成 cline-pass/deepseek-v4.1-flash。前缀是传输层的要求,但 {{model}} 会把它当模型名渲染进 persona,而官方部署的提示词里是没有前缀的(官方 deepseek 路由的 id 本身就是裸 id)。 插件因此在提示词装配时把 persona 两段模板里的 {{model}} 换成 deepseek-v4.1-flash:线上 id、 会话记录、选择器都不动,只改提示词显示。详见下面的 plainModelId 选项。

  4. Cline Pass 卡片上的用量条(0.7.0 起):5 小时 / 每周 / 每月三条,显示剩余百分比、重置倒计时和读取 时间,带一个刷新按钮。数据由宿主半边用凭据库里的 key 去问网关 (GET https://api.cline.bot/api/v1/users/me/plan/usage-limits,与开源的 CodexBar 同一个端点),挂在一条同源路径上给浏览器读 —— 浏览器永远拿不到 key。详见下面的「用量条」一节。

dsh ──pi-ai──> https://api.cline.bot/api/v1   (进程内改写请求体,无监听、无端口)

注入方式是进程内的:包一层 globalThis.fetch,只改写发往本网关的 chat/completions 请求体。 依赖的是 pi-ai 的既有行为(它每次请求新建 client、不传自己的 options.fetch,所以真正承载请求的就是全局 fetch);万一将来 dsh 改成自带 fetch,就会静默失效 —— 下面的状态文件就是为此存在的。

0.5.0 起,本插件不再开任何监听。 之前的 loopback 反代 transport(transport: proxy / listen / captureDir)已经删除;旧配置里还留着这些字段不会报错,但会在启动日志里被指出一次并忽略。

0.7.0 起多了一条「路径」,仍然没有监听。 用量条需要浏览器读一份宿主取来的数据,插件把它注册成 dsh 自己那台 HTTP 服务上的一条 exact 路由(/api/clinepass.usage,走 ctx.connection.fetch.register)—— 加的是路径,不是端口、不是 socket、不是第二次监听; 插件进程里依然没有任何 listen。这条路径在 dsh 自己的 Host/Origin 信任栅栏和浏览器 cookie 认证之后才被派发,和设置页读凭据走的是同一层。

只有落到 upstream 这一个 origin 的 chat/completions 会被改写;其它 provider 的请求连对象都不会被复制(同一个 init 原样传给原函数)。

插件启动时还会自动登记那条 provider profile:不存在就创建;如果它认得出是自己建的那张卡、只是地址过期了(例如从旧版反代换过来后地址还指着 127.0.0.1:8791),就只把地址改回来;认不出(是别人/手工建的)就原样不动并在日志里报警。


环境要求

  • dsh 0.1.6-alpha.1(唯一实测过的版本)。注意 npm 上没有 0.1.5 这个版本,只有 0.1.5-alpha.* / 0.1.5-rc.*;dsh plugin 与 dsh.bundle 相关的模块从 0.1.2-alpha.3 的包里就已存在,但更早的版本没有实测过,不保证。
  • Node.js ≥ 20
  • 一个 Cline Pass API key(sk_...)

没有任何第三方依赖:插件只用 Node 内置模块,index.js 一个文件就能读完、审完。test-fetch.mjs / test-settings.mjs / test-install.mjs / test-usage.mjs 在任何目录都能跑(不需要 dsh 的 node_modules);test-package.mjs 会读 CHANGELOG.md 和 install.mjs,所以要在源码检出里跑(它本来也不随安装复制过去);smoke-test.mjs 要读 settings.yaml,所以需要 dsh 自带的 js-yaml。

安装

方式 A:一条命令(推荐)

dsh plugin --profile web add github:BreakFree003/dsh-clinepass-deepseekv4.1

dsh plugin 把命令转发给 profile 目录里的 pnpm;装完它再看这个包有没有声明 dsh.bundle.patch(package.json 里有),有就自动把 dsh-clinepass 追加进 dsh.profile.bundles —— 不用 clone,也不用自己动 cordis.patch.yml。插件行来自 包自带的 cordis.patch.yml;卸载同样交给 pnpm:

dsh plugin --profile web remove dsh-clinepass

要锁版本就带上 tag(不带则取默认分支的最新提交):

dsh plugin --profile web add github:BreakFree003/dsh-clinepass-deepseekv4.1#v0.6.2

这条路径要求 PATH 上有 pnpm —— dsh plugin 本身就是 pnpm 转发器。

方式 B:用安装器(不需要 pnpm,离线可用)

git clone https://github.com/BreakFree003/dsh-clinepass-deepseekv4.1
cd dsh-clinepass-deepseekv4.1
node install.mjs                    # 默认装进 ~/.dsh,profile web

常用参数:

node install.mjs --dsh-home /tmp/mydsh         # 装进别的 DSH_HOME
node install.mjs --profile headless            # 装进别的 profile
node install.mjs --dry-run                     # 只看会改什么

--transport / --port 在 0.5.0 已移除。安装器会明确拒绝它们,而不是默默忽略 —— 一个被忽略的 --port 看起来会像"生效了"。

--dry-run 只是不写文件,仍然要求那个 profile 已经存在:没有的话它会报 no profile at … 并以 1 退出(信息里告诉你了先启动一次 dsh)。这不是 bug,是不想对着一个你还没创建的目标做"预览"。

安装器会:把插件复制到 <DSH_HOME>/profiles/<profile>/plugins/dsh-clinepass/,并把一条 loader 行追加到该 profile 的 cordis.patch.yml(幂等,改动前自动备份;重跑会把旧行升级成当前规范形态)。

A 和 B 二选一。 两条路径挂载的都是 id 为 clinepass 的那一行,只是来源不同: A 取包自带的 cordis.patch.yml,B 往你 profile 的 cordis.patch.yml 里写。同时用 会挂两份。换路径前先按「卸载」清掉上一种。

方式 C:手动

  1. 复制 index.js、package.json、test-fetch.mjs、test-settings.mjs 到 <DSH_HOME>/profiles/<profile>/plugins/dsh-clinepass/(这是最小可运行集;下面「验证」一节里的 test-package.mjs 与 test-install.mjs 检查的是仓库/安装器布局,这样装没有它们,也跑不了);
  2. 在 <DSH_HOME>/profiles/<profile>/cordis.patch.yml 里加一段(见 patch.example.yml):
- insert:
    - id: clinepass
      name: './plugins/dsh-clinepass/index.js'
      config:
        upstream: https://api.cline.bot
        pin:
          - deepseek

然后

  1. 重启 dsh(profile 只在启动时读取):终端里 Ctrl-C,再 dsh web。
  2. 打开 设置 → 模型,会出现一张 Cline Pass 卡片(插件启动时自动登记)。把 API key 粘进去、保存。
  3. 在模型选择器里选 Cline Pass / DeepSeek V4.1 Flash。

想让它成为默认模型,在 ~/.dsh/settings.yaml 里加:

agent-default-model:
  provider: cline-pass
  model: cline-pass/deepseek-v4.1-flash
  reasoningEffort: high

档位只有两个:high(线上 reasoning_effort: "high")和 max(线上 "max")。max 就是 pi-ai 升级顺序里的最高档,profile 里可以直接声明它,不需要别名。 从旧版本升上来不用手改:插件会把卡片地址改回网关,把卡片里那两个档位声明补齐,并清掉已废弃的 xhigh;如果你 settings 里存的正是 xhigh,启动时会把它改成 max(同一个意思,不会悄悄降档)。


设置 key(重点)

「设置 → 模型 → Cline Pass → 编辑」里的 API 密钥 就是它:

  • key 存在 dsh 凭据库里,引用名 CLINE_PASS_API_KEY;
  • 卡片会显示「API 密钥已配置 / 缺失」;
  • 换 key 直接覆盖,不需重启。

key 也可以在启动环境里给(凭据库优先):CLINE_PASS_API_KEY=sk_... dsh web。


用量条(设置 → 模型 → Cline Pass)

卡片下半部分会多出三条。显示的是剩余(还够用多少),不是已用:

ClinePass 剩余用量                 更新于 15:25  刷新
5 小时  ██████████████████████████  98%
        2 小时 59 分 后重置 · 2026/9/16 18:42:08
每周    █████░░░░░░░░░░░░░░░░░░░░░  18%
        3 天 23 小时 后重置 · 2026/9/20 15:42:08
每月    ██░░░░░░░░░░░░░░░░░░░░░░░░   9%
        11 天 23 小时 后重置 · 2026/9/28 15:42:08
  • 百分比是剩余,进度条跟它是同一个量(油表:越满 = 剩得越多),颜色按剩余分档:

    30% 绿、≤30% 黄、≤10% 红。标题里写了「剩余」,因为右边那个百分比是裸数字 —— 不写清楚就只能靠猜它到底是已用还是剩余。

  • 网关只返回 percentUsed:响应里没有任何绝对额度字段(limit / used / remaining 都没有),所以「剩余」只能是 100 - percentUsed。差值按一位小数收口后再取 —— 直接算 100 - 82.4 会得到 17.599999999999994,那不是「剩余 17.6%」。结果夹在 0–100,用完就是 0%。
  • 宿主半边不做这个翻转:它照网关的原样带 percentUsed,怎么显示是浏览器半边的事。
  • 三条来自网关的 five_hour / weekly / monthly 三个窗口;网关返回的其它窗口类型(它有过 experimental_pool 这类实验值)不显示,而不是猜一个名字画上去。
  • 「刷新」绕过缓存真问一次;不点它的话一次成功读取会缓存 usageCacheMs(默认 60 秒), 所以来回切设置页不会刷屏打网关。失败也记 5 秒(不是 60 秒):网关挂着时反复进出设置页 不会再打一遍网关,但重新填好 key 后下一次看仍然是新的 —— 而且卡片在 key 变化时会强制 重读,直接绕开这段短缓存。
  • 浏览器这一侧也有自己的超时(20 秒):宿主只保证它到网关那一段。没有它,一个卡住的 dsh 会让卡片永远停在「读取中…」,而那时刷新按钮是禁用的 —— 用户没有任何可点的东西。
  • 语言跟随 dsh 的「设置 → 通用 → 语言」(中/英两套文案写死在 client.js 里)。

数据是怎么到浏览器里的:dsh 的凭据设计是密钥只能写、不能读(credentials/set 单向, 没有任何 read 路径把它交回来),所以浏览器里没有 key,也就不能自己请求 Cline。于是:

  1. 宿主半边从凭据库取出 CLINE_PASS_API_KEY(环境变量也算,凭据库那一层已经覆盖), 用 Authorization: Bearer <key> 请求 GET https://api.cline.bot/api/v1/users/me/plan/usage-limits;
  2. 把响应用 normalizeUsage 收窄成三个窗口的 {type, percentUsed, resetsAt},只有这些数字 挂到 /api/clinepass.usage 上;
  3. 浏览器半边(client.js)读这条同源路径,渲染成上面那三条。

几个刻意的取舍:

  • key 从不进浏览器:不写进 DOM、不写进返回体、不进日志。返回体里只有三个数字。
  • resetsAt 在宿主侧归一化:网关发的是纳秒精度(...T11:38:00.490486029Z), Date 的规范只承诺毫秒。宿主(永远是 Node)转一次,浏览器就不用去当宽容解析器。
  • 不认识的响应一律不画:success 必须恰好是 true、percentUsed 必须是有限数(夹到 0–100)、 读不懂就整条不显示并说明原因,而不是画一个错的条。
  • 卡片按槽位挂载,不改 dsh 的文件:用的是 dsh 给外部插件留的 settings.models.provider-card slot(注册在 llm-pi-ai 这个 key 上,非本路由的卡片直接 返回 null)。所以不是补丁,dsh 升级不会把它打散(对比改 node_modules 里客户端 bundle 的做法)。
  • client.js 是手写的、没有构建步骤(与 dsh-notify-ping 同款):它只 require('react'), 而 React 是 web shell 静态模块表里的基线模块,所以既不需要 tsdown,也不需要写 dsh.client.external。

排障

现象 原因 / 处理
卡片上没有用量条 刚装完没重启:浏览器半边的 bundle 是宿主启动时组进 boot 图的,重启 dsh 后才有(宿主半边的路由是保存即生效的,所以别拿路由能通当已经好了);或 usage: false
卡片整块不显示,DOM 里只有空的 <div data-slot-error="settings.models.provider-card"> 客户端半边抛异常,被 slot 的 error boundary 吞了。最常见的原因是访问了没写进 inject 的 cordis 服务 —— cordis 对未声明的服务属性是抛错(cannot get property "x" without inject),不是返回 undefined。浏览器控制台里有原始异常;test-usage.mjs 有一条静态断言守着这一类
用量条显示「还没填 API 密钥」 密钥没配,或配到了别的引用名。在卡片下面的「API 密钥」里填 sk_... 保存
显示「网关拒绝了这把 API 密钥」 key 失效或不对(网关返回 401/403);重新填一次
显示「连不上网关」/「超时」 本机到 api.cline.bot 的网络问题;插件不会重试,点「刷新」
想确认宿主这半边活着 带 token 打开页面后访问 /api/clinepass.usage,应返回 {"ok":true,"limits":[…]};或把 usage: false 再看卡片上的条是否消失

配置项

全部有默认值,只挂载就够用。

字段 默认 说明
upstream https://api.cline.bot 真网关 origin
pin ['deepseek'] 所有请求限制到的上游渠道
pins {} 按模型覆盖,如 { 'cline-pass/deepseek-v4.1-flash': ['deepseek'] }
provider cline-pass provider 路由 id(= profile 的 key)
model cline-pass/deepseek-v4.1-flash 模型 id,原样作为线上 model 字段(网关要求 type/model)
displayName Cline Pass 选择器里的名字
contextWindow / maxTokens 921600 / 131072 模型容量,登记 profile 时使用
apiKeyEnv CLINE_PASS_API_KEY profile 里记录的凭据引用
provision true 启动时自动登记 provider profile(缺失则创建;自家卡片地址过期则只修地址)
alignReasoningEffort true 若 agent-default-model.reasoningEffort 不是本模型声明的档位(只剩 high / max 两个),启动时对齐:废弃的 xhigh → max,其它不认识的值 → high
plainModelId true 提示词(persona 两段)里显示去掉本路由前缀的 id:cline-pass/deepseek-v4.1-flash → deepseek-v4.1-flash。线上 id、会话记录、选择器都不受影响;false 则原样显示完整 id
usage true 在「设置 → 模型 → Cline Pass」卡片上显示用量条(5 小时 / 每周 / 每月,剩余量)。false 时宿主不注册路由、不读凭据,并显式广播 enabled: false 让浏览器半边不渲染这张卡片(浏览器半边是从 package.json 发现的,配置管不到它加载与否,所以必须显式告知)
usageRoute /api/clinepass.usage 用量数据这条 exact 路由的路径。宿主会把它一并告诉浏览器半边,两边不会走散
usageTimeoutMs 15000 问网关的超时;超时按「网关没应答」显示,不会挂住设置页
usageCacheMs 60000 一次成功读取的缓存时长。设置页每次重渲染都会读它,缓存是为了不把重渲染变成一串网关请求;0 = 每次都真问
statusFile true 把钉选状态写到 <DSH_HOME>/dsh-clinepass-status.json(见下);也可给自定义路径,或 false 关掉

pin: [] = 不注入任何字段(纯透传)。pins 优先于 pin;把某个模型配成 pins: { '<model>': [] } 等于单独关掉那条路由的钉选,启动时会警告。

plainModelId 只改提示词模板(deployment:persona-prefix / -suffix 两段里的 {{model}}), 且仅当会话确实跑在本路由、且 id 以本路由自己的 <provider>/ 开头时才改;官方路由的裸 id、 别的 provider、本路由上的裸 id、以及 interpolate: false 的字面量段落一律不动。 刻意不去改 catalog id(已存会话的 model/selection / request/header 里记的还是带前缀的 id, 改名会让它们一直报 UNKNOWN_MODEL),也不去改 prompt variable(会话层的 installModelSelection 会在内层 listener 返回后覆盖 variables.model,而且 session-reference 会快照那些变量算引用预算)。

已删除(0.5.0):transport、listen、captureDir,以及旧版的 address / baseURL。旧配置里留着不会报错,启动日志会各指出一次。

验证

cat ~/.dsh/dsh-clinepass-status.json     # 正在跑的 dsh 自己写的状态(见下)
# 实际是 2 空格缩进的 JSON,字段就是这些:
# {
#   "service": "dsh-clinepass", "transport": "fetch", "hook": "installed",
#   "upstream": "https://api.cline.bot", "pin": ["deepseek"],
#   "profileBaseURL": "https://api.cline.bot/api/v1",
#   "counters": { "seen": 4, "pinned": 4, "skipped": 0 },
#   "lastPin": { "model": "cline-pass/deepseek-v4.1-flash", "only": ["deepseek"] },
#   "ignoredOptions": [], "pid": 1234, "at": "2026-09-15T11:13:13.910Z"
# }

node test-package.mjs       # 打包不变量:bundle 声明可用、无生命周期脚本、bundle 行与安装器行不漂移、client 半边声明可用
node test-fetch.mjs         # 单元测试:URL 域限定/透传保真/安装卸载/请求体形态/robustness/状态文件 + 经真 fetch(undici) 打本地 server 的集成
node test-settings.mjs      # 配置面(含已删除选项)、profile 登记与修复、档位迁移
node test-usage.mjs         # 用量:响应收窄、网关读取、路由处理器(缓存/去重/失败分类/key 不外泄)、apply 接线、client bundle 契约
node test-install.mjs       # 安装器/卸载器往返测试(幂等、注释不丢、逐字节还原)
node smoke-test.mjs         # 冒烟:读状态文件确认活着的 dsh 挂着钩子 + 经真网关跑一轮,断言 finalProvider=deepseek
node smoke-test.mjs --negative   # 追加反向对照:不可能渠道必须被拒绝

没有测试框架,全是自带断言的 Node 脚本(零依赖)。npm test 跑前五个,五个都不需要网络; 不过 test-package.mjs 和 test-install.mjs 只在源码检出里成立,所以安装副本里应该跑 npm run test:installed(就是不依赖检出布局的那三个:fetch / settings / usage)。 test-fetch.mjs 用一个本地 HTTP server 冒充网关(它自己的文件头也是这么写的),走真 undici、但只连 127.0.0.1;test-usage.mjs 连那个都不需要 —— 它把网关换成一个返回固定 Response 的桩函数,连 127.0.0.1 都不连。唯一会真的打网关的是 smoke-test.mjs,它另外还 要求正在运行的 dsh。

hook 字段就是「静默失效」的报警器:installed = 钩子在全局 fetch 上;uninstalled = 被卸载了;unavailable = 装不进去(有东西先替换了 fetch,日志里会报,这种情况现在没有备用 transport 可切,要先找出是哪个插件抢了全局 fetch);foreign = 装好之后有别的代码把全局 fetch 换掉了(插件每 30 秒自查一次,所以最迟半分钟内可见;换掉之后请求就不再被钉)。counters.seen 是落到本网关的 chat 请求数,pinned 是真正注入了钉选的请求数 —— seen 涨而 pinned 不涨就说明有请求被跳过了(日志里有 [clinepass] not pinning ... 的原因)。状态文件里不含任何凭据,并且只有当前持有全局 fetch 的那个插件实例会写它。

smoke-test.mjs 会先读状态文件确认正在运行的 dsh 里钩子是 installed、且计数器在动(pid 已退出/hook: uninstalled 时会明确说明它只能验到哪一步;记录里的 pid 存活才作数),确认本插件没有监听任何本地端口,再用同一份插件代码在测试进程里注入一次、打真网关断言 finalProvider: "deepseek"。

provision 字段说明 provider 卡片的登记结果:created(新建)/ present(已存在)/ repaired(地址过期已修正)/ mismatch(那张卡片不是本插件建的,未改动;请求会绕过钉选,需要你手动改地址)/ failed(settings 写入失败,日志里会有原因)。

常见问题

现象 原因 / 处理
设置 → 模型里没有 Cline Pass 卡片 dsh 还没重启;或 provision 被关掉、settings 只读。看日志与状态文件的 hook;也可手动按 patch.example.yml 里的 profile 结构添加
卡片里 API 地址不是 https://api.cline.bot/api/v1 那张卡片不是本插件建的(provision: mismatch),插件不去改它。改成网关地址,否则请求绕过钉选
状态文件里 hook: unavailable 有别的插件先替换了 globalThis.fetch。找出是哪个插件;现在没有备用 transport,钩子装不进去时请求会不带钉选
状态文件里 hook: foreign 装好之后全局 fetch 被别的代码换掉了(插件每 30 秒自查)。换掉之后的请求不再被钉,排查是哪个插件
状态文件里 seen 在涨、pinned 不涨 有请求被跳过:日志里的 [clinepass] not pinning request #N: ... 会给出原因(非 JSON 体、流式体、无 body),或按模型的 pins 被配成了 []
reasoningEffort: max 能用吗 能,而且是两个档位里的最高档。卡片里声明 high / max:线上分别发 reasoning_effort: "high" / "max"。旧卡片里多出来的 xhigh 会被清掉;settings 里存的 xhigh 会被改成 max
状态文件里 seen: 0 但你刚聊过 那条路由没走本网关(地址被改过 / 选了别的 provider),或 dsh 里跑的不是这份插件
UNKNOWN_MODEL profile 里的 models[].id 与 dsh 选择的模型不一致。id 必须是 cline-pass/deepseek-v4.1-flash 这种带前缀形式
UNSUPPORTED_REASONING_EFFORT 存的档位不在卡片声明的档位里(只有 high / max)。插件启动时会把默认模型的那个值对齐(xhigh → max,其它 → high);若某个已存会话仍报错,在输入框的档位选择里重选一次即可
MISSING_CREDENTIAL 还没在设置页填 key,或填到了别的引用名
启动日志说 "listen" is no longer used 旧配置里还留着已删除的反代选项;删掉那个字段即可(不影响运行)
严格钉选的代价 fallbacksAvailable: [] 意味着 deepseek 渠道不可用时请求直接失败、不会回退 —— 这是「钉死」的语义

安全说明

  • 不监听任何端口:插件只包一层 globalThis.fetch,且只改写发往 upstream 的 chat/completions。用量功能加的是 dsh 自己那台 HTTP 服务上的一条路径(/api/clinepass.usage,见上),插件进程里没有 listen,也没有第二个 socket。
  • 插件不持有 API key —— key 由 dsh 从凭据库取出、写进请求头,插件只加一个路由字段。状态文件里只有服务与钩子状态、上游与 profile 地址、计数与时间、模型名、钉选渠道和 pid:没有任何密钥,test-fetch.mjs 里有一条断言专门守着这一点。
  • 用量读取里的 key 也不外泄:宿主用 Authorization: Bearer <key> 问网关,然后把响应收窄成三个数字再返回。浏览器拿到的 JSON 里没有 key,DOM 里也没有;test-usage.mjs 有两条断言守着(key 不出现在任何返回值里、只出现在那一个请求头里)。这条路在 dsh 自己的 Host/Origin 信任栅栏与浏览器 cookie 认证之后才派发。
  • 日志只打印请求方法/URL/模型/钉选渠道(形如 [clinepass] → #004 POST https://api.cline.bot/api/v1/chat/completions pinned to deepseek (model …, in-process)),以及配置/登记结果与「没钉上的原因」。请求头从不进日志 —— authorization 没有被打印的机会,也就不存在"脱敏"这一步。用量那一路同理:只记「注册了哪条路径」和「读取意外失败」,不记 key、不记响应体。
  • 旧版的 captureDir 会把请求/响应原文(含对话内容)落盘,该功能已随反代一起删除。

与 npm 上 dsh-cline-pass 的区别

npm 上另有一个名字很像的包 dsh-cline-pass(作者 yhshzh),解决的问题有重叠,路线不同: 它自带 provider 适配器、渠道枚举与账户池,走 npm 发布;本插件(dsh-clinepass,没有连字符) 刻意反过来 —— 不自带适配器,复用 dsh 内置的 pi-ai 路由,只往出站请求体里注入渠道钉选字段, 所以不存在第二套会随上游漂移的流式适配器。

两者都会注册 provider 路由,不要同时挂载。装之前看清楚名字。

卸载

方式 A(bundle)装的:

# 1. 先摘掉自动登记的那张 provider 卡片 —— 这一步要在移除包之前跑
DSH_HOME=~/.dsh node ~/.dsh/profiles/web/node_modules/dsh-clinepass/uninstall.mjs --profile web
# 2. 再让 pnpm 移除包,并自动把它从 dsh.profile.bundles 摘掉
dsh plugin --profile web remove dsh-clinepass

第 1 步会顺带报「没找到 clinepass 行」「插件目录已不存在」——bundle 布局下这两件事本来 就无事可做,它真正干活的是删掉 settings.yaml 里的 llm-pi-ai.providers.cline-pass。 想留着那张卡片就加 --keep-provider。

方式 B(安装器)装的:

node uninstall.mjs                  # 移除 loader 行 + 插件目录 + 自动登记的 profile
node uninstall.mjs --keep-provider  # 保留设置页那张卡片

两种方式卸载后都要重启 dsh,「Cline Pass」卡片才会从设置页消失。


English

dsh-clinepass connects Cline Pass to DeepSeek Harness with every request pinned to the DeepSeek upstream channel (strict, no fallback), and its API key entered on Settings → Models.

Architecture. The route is an ordinary pi-ai provider profile (llm-pi-ai.providers.cline-pass, OpenAI-compatible, key from the credential store) — which is exactly why dsh renders a native provider card with a key field for it. The plugin only adds providerOptions.gateway.only to outgoing requests, leaving streaming, tool calls, reasoning, usage and images on pi-ai's proven path. The pin is injected in-process: the plugin wraps globalThis.fetch for the life of the process and rewrites only this gateway's chat-completions bodies — no listener, no port, nothing to configure for transport. (0.5.0 removed the optional loopback reverse proxy; a config still naming transport / listen / captureDir is reported once and ignored.) Only requests to the configured upstream origin are ever touched, and unrelated calls are passed through as the exact same arguments. On start the plugin also provisions the profile (repairing only a recognisably-own stale address, never overwriting a foreign one) and aligns an unsupported stored reasoning level. Since 0.6.2 it also keeps the gateway's type/ prefix out of the prompt: a prepended system-prompt/assemble listener rewrites the {{model}} reference in the two persona sections, so the persona reads deepseek-v4.1-flash while the wire, the session records and the model picker keep cline-pass/deepseek-v4.1-flash (plainModelId: false turns this off). Since 0.7.0 the Cline Pass card on Settings → Models also shows usage: the 5-hour, weekly and monthly windows as progress bars of what is left, with the remaining percent, the reset countdown and the read time (the gateway only reports percentUsed, so remaining is 100 − percentUsed, computed in the browser half and rounded to one decimal so 100 − 82.4 cannot print 17.599999999999994). client.js is the browser half — a hand-written bundle (no build step, like dsh-notify-ping) that registers into dsh's documented settings.models.provider-card slot; the host half reads GET https://api.cline.bot/api/v1/users/me/plan/usage-limits with the stored key and publishes only the resulting numbers at /api/clinepass.usage, an exact Fetch route on the HTTP server dsh already runs — a new path, still no listener of ours. The browser cannot read the key by design (dsh's credential seam is write-only), which is exactly why the card needs a host half.

Install. dsh plugin --profile web add github:BreakFree003/dsh-clinepass-deepseekv4.1 — the package declares dsh.bundle.patch, so dsh plugin (a pnpm forwarder) installs it and appends it to dsh.profile.bundles on its own: no clone, no hand-edited patch. Append #v0.7.0 to pin a tag. Without pnpm, node install.mjs copies the plugin into the profile and appends the loader row instead (idempotent + backed up) — use one route or the other, never both. Restart dsh either way, then set the key on Settings → Models. Configuration defaults are complete — upstream, pin, pins, provider, model, apiKeyEnv, provision, alignReasoningEffort, plainModelId, usage, usageRoute, usageTimeoutMs, usageCacheMs, statusFile. No third-party dependencies: the host half imports only Node built-ins and the browser half only asks for React from the shell's static module table.

Verify. cat ~/.dsh/dsh-clinepass-status.json — the running dsh writes its hook state and counters there (hook: installed, seen/pinned/skipped, last pin), which is how a silently bypassed hook becomes visible; node test-package.mjs (packaging invariants: the bundle declaration is installable, no lifecycle scripts, the bundle and installer rows cannot drift, and the client-half declaration is loadable), node test-fetch.mjs (URL scoping, pass-through fidelity, install/uninstall and reload semantics, body shapes, robustness, status file, integration through the real fetch), node test-settings.mjs (the option surface, provisioning and the effort migration), node test-usage.mjs (response narrowing, the gateway read, the route handler's caching/dedupe/failure classification and key containment, the apply wiring), node test-install.mjs (install/uninstall round trips), node smoke-test.mjs [--negative] (checks the live process's status file, then runs a real gateway round trip asserting finalProvider: "deepseek" and no fallbacks). To check the usage half against the live process, open the page with its token and GET /api/clinepass.usage — it should answer {"ok":true,"limits":[…]}.

Uninstall. Bundle install: run uninstall.mjs from the installed package (node <profile>/node_modules/dsh-clinepass/uninstall.mjs — it drops the provisioned provider profile), then dsh plugin --profile web remove dsh-clinepass. Installer install: node uninstall.mjs [--keep-provider]. Restart dsh afterwards.

Status. Shared as-is for others to use, not a maintained project: no support promise, no commitment to track upstream dsh changes. The plugin leans on an internal dsh/pi-ai detail (pi-ai builds a fresh client per request and passes no options.fetch, so the real transport is the global fetch); if that ever changes the pin fails silently, which is exactly what the status file's hook field exists to expose.

See also. A related package named dsh-cline-pass (npm, by yhshzh) solves overlapping problems with a self-contained provider adapter and account pool; this one (dsh-clinepass, no hyphen) deliberately reuses dsh's built-in pi-ai route and only injects the pin field. Both register provider routes — do not mount both.

Security. The plugin opens no socket at all — the usage feature adds one path on the HTTP server dsh already runs, behind dsh's own Host/Origin trust fence and browser-cookie authentication — and the hook only rewrites requests aimed at the configured gateway origin. It holds no credential of its own (the key travels in the request header dsh sets; the usage read sends it once, to the same gateway, and never returns it), its status file contains no secrets (a test asserts this), and no request header is ever logged — the log carries only method, URL, model and pin channel, so there is nothing to redact.

License

MIT — see LICENSE.

—/ 5

No ratings yet

Verified DSH bundle

Commit d59cd38928ae

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout