Passer au contenu principal
T

dsh-plan-bridge

taylorswitiger/dsh-plan-bridge

Pont multi-plans local pour DeepSeek Harness : utilisez vos abonnements existants ChatGPT Codex, Claude, ZCode (GLM) et Qwen via un unique proxy en loopback, avec une carte d'état dans la page de réglages et un provisionnement en un clic. Aucune modification du cœur de dsh.

Installer

dsh plugin --profile web add github:taylorswitiger/dsh-plan-bridge

README

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=1 和 HTTPS_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-required 或 credential-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/coding、token-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-cn、qwen-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.js 的 normalizeForDiff),否则自己刚写的配置会被误报成冲突。
  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

Plugins associés