Back to home@TaylorSwitiger

dsh-plan-bridge

DeepSeek Harness (dsh) plugin: one local bridge for ChatGPT Codex / Claude / ZCode (GLM) / Qwen subscription plans — settings status card, one-click provisioning, zero core changes. npm: @taylorswitiger/dsh-plan-bridge

Stars
1
Language
JavaScript
Created
Sep 3, 2026
Updated
Sep 4, 2026
GitHub repo

Introduction

dsh-plan-bridge

A DeepSeek Harness (dsh) plugin that bridges existing vendor subscription plans — ChatGPT Codex (Plus/Pro), Claude Code (Pro/Max), ZCode (BigModel GLM Coding Plan), Qwen (Aliyun Bailian Token Plan) — into dsh model groups through one local loopback process. Settings-page status card, one-click provisioning, no dsh core changes, zero runtime npm dependencies. 中文文档如下。

dsh 的多套餐桥接插件。一个本地进程监听 127.0.0.1:8417,按路径前缀分流,同时承载多家订阅套餐; 每家套餐在 dsh 里是独立的模型分组,是否接入由你在设置页决定。

dsh (llm-pi-ai 多 provider 路由)
  ├─ codex-plan  → http://127.0.0.1:8417/codex/v1
  ├─ claude-plan → http://127.0.0.1:8417/claude
  ├─ zcode-plan  → http://127.0.0.1:8417/zcode/v1
  └─ qwen-plan   → http://127.0.0.1:8417/qwen/v1
                        │
                        ├─ codex  OAuth:读 ~/.codex/auth.json,续期写回,转发 ChatGPT codex 后端
                        ├─ claude OAuth:读 ~/.claude/.credentials.json,续期写回,转发 api.anthropic.com
                        ├─ zcode  API-key:转发 open.bigmodel.cn coding 端点
                        └─ qwen   API-key:转发百炼 token-plan 端点

套餐分两类。OAuth 型(codex、claude)复用厂商 CLI 自己的凭据文件,token 快过期时自动续期并写回原文件,CLI 和桥接共享同一份登录态。API-key 型(zcode、qwen)的 key 存在 dsh 凭据库里,桥接只转发请求和 Authorization 头:key 不进入桥接进程,也不落在插件自己的任何文件里。

桥接不代做登录。OAuth 套餐要求先跑过厂商 CLI 的 login;API-key 套餐要求 dsh 凭据库里已有 key。检测不到凭据的套餐在卡片上显示「不可用」并附凭据指引。

安装

dsh plugin --profile <name> add -w @taylorswitiger/dsh-plan-bridge
# 或直接从 GitHub / 本地目录
dsh plugin --profile <name> add -w https://github.com/TaylorSwitiger/dsh-plan-bridge
dsh plugin --profile <name> add -w file:/path/to/dsh-plan-bridge

装完重启 host。桥接默认监听 127.0.0.1:8417,可用插件配置 host / port 覆盖。上游需要代理时,给 host 进程设 NODE_USE_ENV_PROXY=1HTTPS_PROXY(桥接走 Node 自带的 env-proxy 出网)。

设置页卡片

设置 → 套餐接入,每套餐一行,接入与否由该行按钮决定,没有自动接入。

状态含义按钮
服务中已接入且本机凭据可用停用、重新检测
另一实例服务中同上,但 8417 被另一个 host 实例持有停用、重新检测
未接入凭据在,provider 段还没写一键接入、重新检测
需先登录厂商 CLIOAuth 凭据文件缺失,附 login 命令指引重新检测
不可用本机没有该套餐的凭据,附添加指引重新检测
错误桥接监听失败重新检测

OAuth 型行内显示账号掩码(如 use***@example.com)、subscriptionType、token 过期时间;API-key 型显示「API key 凭据已配置」。遇到 429 时该行显示额度重置时间,上游错误体里带重置时间戳的(codex、claude)会给出具体时刻,其余只透出错误文本。

停用会从 settings.yaml 移除该套餐的 provider 段,模型目录立即失去该分组,凭据保留不动;再点一键接入随时恢复。该套餐若正被 agent-default-model 指向,停用会被拒绝,先切默认。

一键接入做了什么

provision(卡片按钮或 RPC)幂等地做三件事:

  1. 校验本机凭据。缺失时返回结构化错误(cli-login-requiredcredential-missing),卡片显示对应指引。
  2. 通过 dsh 的 settings 服务写 llm-pi-ai.providers.<key> 段。已有且一致则跳过;不一致返回字段 diff,需要你在卡片上确认后才覆盖。baseURL 按适配器接受的路径拼写归一比较,等价的写法不算差异。
  3. OAuth 型经 credentials 服务补一个占位凭据(桥接不校验值);API-key 型不写任何凭据。

接入不改变 agent-default-model

RPC 端点

渠道 /dsh-plan-bridge(loopback):

plans.list        {}                    卡片首屏
plan.status       {planId}              单套餐详情
provision.begin   {planId, overwrite?}  接入
provision.disable {planId}              停用

HTTP 直测(method 须与 URL 末段一致):

curl -s http://127.0.0.1:3080/dsh-plan-bridge/plans.list \
  -H 'content-type: application/json' \
  -d '{"type":"client-request","rpcId":"1","method":"plans.list","payload":{}}'

桥接健康:GET http://127.0.0.1:8417/health,单套餐 GET /<planId>/health

内置套餐

planIdproviderKey类型凭据模型
codexcodex-planOAuth~/.codex/auth.jsongpt-5.6-sol
claudeclaude-planOAuth~/.claude/.credentials.jsonclaude-opus-5 / sonnet-5 / haiku-4-5
zcodezcode-planAPI-keyZAI_CODING_PLAN_API_KEYglm-5.3 / 5.3-flash / 5.2 / 5-turbo / 5.1 / 4.7
qwenqwen-planAPI-keyQWEN_TOKEN_PLAN_CN_API_KEYqwen3.7 / 3.6 系及 deepseek、kimi、glm、minimax 共 15 个

已知问题:

  • claude 标 beta:转发链路完整可用,但 Apple / App Store 渠道订阅的账号会被 Anthropic 组织策略拒绝(403 oauth_not_allowed_for_organization),网页直付账号不受影响。卡片对该错误有专门的解释文案。
  • qwen 标 beta:端点与模型目录取自 pi-ai 的 qwen-token-plan-cn catalog,上游转发未经真实 key 验证;添加 key 后即可使用。
  • zcode、qwen 走的是套餐专用网关(open.bigmodel.cn/api/codingtoken-plan.*.maas.aliyuncs.com),用量计入套餐额度,不扣按量余额。

新增一个适配器

框架只管端口、路径分流、生命周期和状态聚合,厂商细节全在适配器里。加一家套餐 = 一个适配器文件 + 注册表一行,路由、卡片行、provision、状态端点自动生效。

OAuth 型适配器导出如下结构的对象(参考实现见 codex.js):

export const fooAdapter = {
  id: 'foo',                    // 路径前缀 /foo/v1 的来源;planId
  providerKey: 'foo-plan',      // provision 写入 settings.yaml 的 providers 键名
  displayName: 'Foo 套餐',
  protocol: 'openai-responses', // llm-pi-ai 的 api 名
  apiKeyEnv: 'FOO_PLAN_BRIDGE_KEY',
  authKind: 'oauth',            // 'oauth' | 'api-key'
  basePath: '/foo/v1',
  settingsPath: '/foo',         // 写进 baseURL 的路径,取决于 SDK 是否自己追加 /v1
  acceptedBasePaths: ['/foo', '/foo/v1'],
  cli: { name: 'Foo CLI', loginCommand: 'foo login', installHint: 'npm i -g foo',
         credentialHint: 'dsh 设置 → 凭据 → 添加 FOO_KEY' },
  models: [/* { id, name, contextWindow, maxTokens, reasoningEfforts?, input? } */],
  profileExtras: {},            // 并入 provider 段的额外字段,如 compat
  beta: false,

  detectAuth() {                // 探测本机可用性,可异步;api-key 型查 dsh 凭据服务
    return { ok, accountMasked?, expiresAt?, reason? }
    // reason 'cli-login-required' → 卡片「需先登录厂商 CLI」
    // reason 'credential-missing'  → 卡片「不可用」
  },
  async forward({ subpath, request, response, log }) {},
  parseUpstreamError(status, text, headers) {},  // → { type, resetsAt?, detail? } | null
  status() { return { accountMasked?, accessTokenExpiresAt?, lastUpstreamError, note? } },
}

API-key 型不必手写,lib/adapters/apikey-plan.js 的工厂一份 spec 换一个完整适配器(凭据探测、Authorization 透传、上游错误缓存都内置):

export const fooAdapter = apiKeyPlanAdapter({
  id: 'foo', providerKey: 'foo-plan', displayName: 'Foo 套餐',
  apiKeyEnv: 'FOO_KEY', upstreamBase: 'https://vendor.example/v4',
  cli: { name: 'Foo', credentialHint: 'dsh 设置 → 凭据 → 添加 FOO_KEY' },
  models: [/* … */],
})

注册(lib/adapters/index.js):

export const adapters = [codexAdapter, claudeAdapter, zcodeAdapter, qwenAdapter, fooAdapter]

然后 pnpm run check,重新装到 profile,重启 host。

上游端点和模型元数据优先从 pi-ai 的内置 catalog 抄(zai-coding-cnqwen-token-plan-cn 等),与 dsh 原生路由同源,contextWindow、maxTokens、input 模态都有现成数据。

兼容性

  • 不修改 dsh 本体的任何文件,只用公开插件 API:Connection RPC、settings / credentials 服务、settings 插槽。
  • 凭据写入全部走 credentials 服务,保持 ~/.dsh/.credentials.yaml 的 version 1 + refs 格式。
  • agent-default-model 只能由用户改动,插件的任何端点都不碰它。

设计说明

改代码前值得知道的几件事:

  1. 客户端一侧是手写的 module-table bundle(window.__ModuleLoader__.load),react 通过工厂函数的 require('react') 拿宿主共享的实例。因此不需要打包器,pnpm run check 只做解析和打包契约校验;代价是不能用 JSX 和打包期优化。
  2. 所有 RPC 端点统一返回 {ok, value} / {ok, error} 结构。曾有端点漏包 value,客户端解包得到 undefined,渲染时导致整个设置分区渲染失败。加端点时保持返回结构一致,客户端的 unwrap 也要对「ok 但缺 value」抛出带错误码的异常。
  3. settings 写入再读回会补上空默认值(model 的 input: []compat: {chatTemplateKwargs:{}}),幂等比较前必须先归一(util.jsnormalizeForDiff),否则自己刚写的配置会被误报成冲突。
  4. 凭据事实与端口持有是两个维度。缺 key、缺登录是机器级事实,闲置实例(8417 被别的 host 持有)也要如实报「不可用」/「需登录」,状态计算先按凭据归类,再看端口。
  5. Claude 订阅调用所需请求头与普通 API key 调用不同:OAuth Bearer 之外还需 anthropic-beta: claude-code-20250219,oauth-2025-04-20(与调用方自带值合并)、Claude Code 身份的系统提示首块、anthropic-version: 2023-06-01。token 在 platform.claude.com/v1/oauth2/token 续期,refresh grant 会轮换 refresh_token,写回时保留响应省略的字段。oauth 拒绝码在 error.details.error_code,不在 message 里。
  6. codex 的 token 过期时间单位是 epoch 秒,claude 的是毫秒,状态聚合层统一归一成秒。
  7. 调试:host 进程设 DSH_PLAN_BRIDGE_DEBUG=<文件路径>,适配器把每个请求和上游错误首段追加写入该文件。

License

MIT