Bundle
@taylorswitiger/dsh-plan-bridge
Local multi-plan bridge for DeepSeek Harness: use your existing ChatGPT Codex, Claude, ZCode (GLM) and Qwen subscriptions through one loopback proxy, with a settings-page status card and one-click provisioning. No dsh core changes.
- Source
- TaylorSwitiger
- stars
- 2 stars
- License
- MIT
- Updated
- Updated 4 days ago
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。检测不到凭据的套餐在卡片上显示「不可用」并附凭据指引。
## 安装
```sh
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 段还没写 | 一键接入、重新检测 |
| 需先登录厂商 CLI | OAuth 凭据文件缺失,附 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 末段一致):
```sh
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`。
## 内置套餐
| planId | providerKey | 类型 | 凭据 | 模型 |
|---|---|---|---|---|
| codex | codex-plan | OAuth | `~/.codex/auth.json` | gpt-5.6-sol |
| claude | claude-plan | OAuth | `~/.claude/.credentials.json` | claude-opus-5 / sonnet-5 / haiku-4-5 |
| zcode | zcode-plan | API-key | `ZAI_CODING_PLAN_API_KEY` | glm-5.3 / 5.3-flash / 5.2 / 5-turbo / 5.1 / 4.7 |
| qwen | qwen-plan | API-key | `QWEN_TOKEN_PLAN_CN_API_KEY` | qwen3.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):
```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 透传、上游错误缓存都内置):
```js
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`):
```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
Install
dsh plugin --profile web add github:TaylorSwitiger/dsh-plan-bridge
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install taylorswitiger-dsh-plan-bridge from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.