- Accueil
- Plugins
- Modèles et fournisseurs
- dsh-workbuddy2api
dsh-workbuddy2api
tearslee/dsh-workbuddy2api
Supervise un processus local de la passerelle workbuddy2api et expose ses modèles comme fournisseur LLM, en lisant les fenêtres de contexte et les niveaux d'effort de raisonnement depuis le propre endpoint de modèles de la passerelle.
Installer
dsh plugin --profile web add github:tearslee/dsh-workbuddy2apiREADME
dsh-workbuddy2api
[!WARNING]
⚠️ 项目状态:已停止维护(2026-10-20)
本仓库不再积极维护。 不再开发新功能,不再响应 issue / PR。已安装的用户可以继续使用,但不再获得修复与适配。
原因:原始上游
Sliverkiss/workbuddy2api已于 2026-09-24 从 GitHub 消失(API 返回 404,git ls-remote报Repository not found,并非改名重定向)。本插件的生命线直接绑在它身上 —— 网关二进制由本仓库 CI 从上游源码交叉编译,上游消失后,这条链路只能靠延续仓库续命。我已改用 magpie 接入 WorkBuddy 账号:
- magpie 的社区插件
@magpie-community/opencode-workbuddy-auth直接对接 WorkBuddy(国内版workbuddy)与 WorkBuddy AI(国际版workbuddy-ai)两个 provider,走浏览器 OAuth 登录,不需要自建网关、不需要 Go / Python / bash;- magpie 是一套网关同时代理多家订阅(WorkBuddy / Trae / ZCode / …),账号池、熔断、冷却、用量统计由 magpie 统一负责,不再需要为每家订阅各养一个网关;
- 本机已切到 magpie 方案并弃用本插件的自动拉起链路。
给你的建议:
- 新用户:请直接用 magpie(magpie-community/plugins),本插件不再推荐安装;
- 已装用户:不影响继续使用。若日后遇到上游接口变动导致失效,可自行改用 magpie,或参考下方文档自行编译网关(
go build路径仍然有效);- 若你希望接手本插件的维护,欢迎提 issue 或直接 PR —— 但请先看下面的上游状态,接手前务必确认你要依赖的网关来源还活着。
下方内容全部保留,作为历史文档与自行构建的参考。
把 workbuddy2api 网关接进 DeepSeek Harness(dsh) 的插件。
装上之后:
- dsh 启动时自动拉起网关,退出时自动回收;端口上已有健康网关在跑则直接复用,不重复拉起,也不会去杀别人的进程。
- 模型目录自动同步 —— 模型列表、上下文窗口、输出上限、思考档位全部从网关
GET /v1/models实时读取,不必再手写settings.yaml里的模型元数据。 - 6 条管理命令:
/wb2api-setup·/wb2api-status·/wb2api-start·/wb2api-restart·/wb2api-login·/wb2api-account。 - 一条命令完成安装:
/wb2api-setup会取回网关可执行文件(校验 SHA256)、准备运行目录、引导登录、启动网关并同步模型 —— 不需要装 Go,也不需要 python3 / bash。
插件本身很薄:协议适配、账号池、加权选号、分级熔断、定时任务、登录与 token 刷新全部留在 Go 网关侧。它只负责两件事 —— 进程生命周期,和模型目录自动化。
它解决什么
直接用手工配置把 workbuddy2api 接进 dsh(在 ~/.dsh/settings.yaml 里写 llm-pi-ai.providers.workbuddy2api)也能用,但有几处手工负担:
| 问题 | 手工接法 | 本插件 |
|---|---|---|
| 模型元数据 | 每个模型的 contextWindow / maxTokens / reasoningEfforts / input 全手抄;网关增删模型必须手改文件 | 运行时读 /v1/models 自动映射 |
| 进程管理 | dsh 启动前必须另行常驻 wb2a-server.exe,退出也不联动 | 插件托管子进程,生命周期绑定 dsh |
| 获取网关二进制 | 装 Go 工具链 → git clone 上游 → go build | /wb2api-setup 自动下载 + SHA256 校验 |
| 账号登录 | 上游 login.sh 需要 bash + python3(Windows 上基本劝退) | 插件内置同款 OAuth 流程,纯 Node 实现 |
| 可分发 | 配置只存在于本机,别人拿不到 | 一个 npm 包,装上即可用 |
字段映射
网关的 /v1/models 已经透出全部模型元数据(internal/server/handler.go 的 modelList(),对应上游 issue #84),因此插件无需内置静态兜底表:
网关 /v1/models | dsh LlmResolvedModelInfo | 说明 |
|---|---|---|
context_length | context.contextWindow | 网关保证有值(四级查找链,见下) |
max_output_tokens | defaultMaxTokens | 网关未收录时不下发该键,插件随之不声明 |
supports_images | inputModalities | 缺席即仅 ['text'] |
reasoning_supported_efforts | reasoning.efforts | 空则不声明整个 reasoning |
reasoning_default_effort | reasoning.defaultEffort | 插件先校验它确实包含于 efforts 内 |
内置的
openai-completions栈不认这些非标准字段 —— 这正是当初必须手写元数据的根因,也是本插件的核心价值。网关侧对
context_length/max_output_tokens走四级查找链:上游动态值 → 静态种子表 →model.json缓存 → models.dev 按需拉取;全未命中时窗口兜底 1M、输出上限省略。cn:与global:两域同口径。
为什么不破坏上游的「按轮聚合」
本插件的请求序列化有两条硬约束:工具结果必须发成 role:"tool"、用户消息的内容与顺序在轮内逐字不变。破坏任一条,上游按对话轮聚合 RequestID 的兜底键就会在每个 step 漂移,用量明细重新碎片化 —— 且是静默的(网关照常回包)。
原理、实测截图与端到端证据:docs/turn-level-aggregation.md。
前置条件
⚠️ 上游仓库已删库(2026-09-30 确认,CI 已迁走):原
Sliverkiss/workbuddy2api现已 404(并非改名重定向)。 延续仓库为HanawaBanana/workbuddy2api(MIT,描述明确写着"原 Sliverkiss/workbuddy2api 已删库")。
release-binaries工作流已改为从延续仓库构建,并把 ref 钉死在UPSTREAM_REF = 1c9aef9ab378217316ee2ddff7eace78381b46c0(2026-09-20)—— 这正是 已发布二进制内嵌的vcs.revision(可用go version -m wb2a-server.exe复核), 因此重新构建出的产物与既有版本同源、可复现。- 升级上游时改工作流里的那一行即可;不要改回
Sliverkiss/...,那个仓库已不存在。- 本机开发时若自行
git clone,同样应 clone 延续仓库。2026-10-20 复核:延续仓库
HanawaBanana/workbuddy2api仍在维护(当日 master 最新提交af5e779,2026-10-16;未归档、无 Release)。因此"自己go build出来源"这条路目前仍走得通 —— 但UPSTREAM_REF钉在 2026-09-20 的快照上,已发布二进制比延续仓库落后约一个月。 想跟最新上游就自行go build并把binaryPath指向你的产物(插件不会覆盖它)。
-
dsh,全局安装:
npm i -g @deepseek-ai/dsh本插件在 dsh
0.2.0-rc.2上验证通过;package.json声明的兼容区间是>=0.1.2-rc.1 <0.2.0-0 || >=0.2.0-rc.1 <0.3.0-0(前一段覆盖0.1.x,后一段覆盖0.2.x)。装插件时 dsh 会拿运行时的 dsh 版本去比对这个区间(
dsh-app-boot的evaluatePluginCompatibility,内部用semver.satisfies(version, range, { includePrerelease: true }))。 因此0.2.0-rc.2这类预发布版本也能正确命中 —— 这一点很容易踩坑:若按普通 semver 语义 写>=0.1.2-rc.1 <0.2.0-0,它反而匹配不上任何0.1.7+预发布版,安装会被拒绝并回滚。 -
一个 WorkBuddy 账号(国内外账号体系相互独立,选你有的那个)。
-
网关二进制 —— 不用自己准备,
/wb2api-setup会自动下载并校验。
安装
⚠️ 先看顶部公告:本仓库已停止维护,新用户不建议安装。若你只是想用上 WorkBuddy 订阅, 直接上 magpie +
@magpie-community/opencode-workbuddy-auth(浏览器 OAuth 登录,无需自建网关 / Go / Python / bash)更省事。 下面三种安装方式仅供已决定继续使用本插件的人参考。
方式一:一条命令直装(推荐)
不需要先下载文件 —— pnpm 支持直接以 Release 的 tarball URL 作为依赖(已实测:装出来是真实目录、非 link:):
dsh plugin --profile web add https://github.com/tearslee/dsh-workbuddy2api/releases/download/v0.3.2/dsh-workbuddy2api-0.3.2.tgz
升级时把 URL 里的版本号换掉即可。
方式二:先下载再本地安装
在 Releases 下载 dsh-workbuddy2api-<版本>.tgz,然后:
dsh plugin --profile web add file:/绝对路径/dsh-workbuddy2api-0.3.2.tgz
方式三:从源码安装
git clone https://github.com/tearslee/dsh-workbuddy2api
cd dsh-workbuddy2api
npm install
node node_modules/typescript/bin/tsc -p tsconfig.json # 必须先构建出 lib/
npm pack --pack-destination dist
dsh plugin --profile web add file:./dist/dsh-workbuddy2api-0.3.2.tgz
不要用
dsh plugin install <源码目录>:pnpm 会写成link:依赖,而 Windows 上工具进程创建的 junction 不可遍历,会导致 pnpm 全面失效。用npm pack/pnpm pack出的 tarball(解包成真实目录)可规避。
关于 npm registry(重要,别装错):npm 上的
dsh-workbuddy2api这个包名已被他人占用 —— 发布者是 Boiser,线上版本为0.4.9~0.6.8(latest = 0.6.8), 与本仓库(tearslee/dsh-workbuddy2api,当前0.3.2)不是同一个项目。因此:
- ❌ 不要执行
dsh plugin add dsh-workbuddy2api—— 它会装到那个同名包,而不是本插件;- ✅ 请用上面三种方式之一,从本仓库的 GitHub Release 取
dsh-workbuddy2api-<版本>.tgz;- 装完请核对来源与版本:本仓库的版本线是
0.1.x/0.3.x(0.4.x及以上均非本仓库产物)。本仓库未在 npm 发布,也无意与同名包竞争该名称 —— 安装时认 URL / 认 tarball,不要只认包名。
装完必须重启一次 dsh(为什么)
dsh plugin add 只做两件事:把包装进 ~/.dsh/profiles/<profile>/node_modules/,以及把包名加进该 profile 的 dsh.profile.bundles。正在运行的 dsh 进程不会重新读取这个列表 —— bundles 只在启动时解析一次,插件的代码是那时被 import 进进程的。
所以:装完请重启 dsh(关闭再启动),插件与它的 workbuddy2api provider 才会出现。这不是本插件的缺陷,而是 dsh 加载模型决定的 —— 新代码还没进进程,插件自己也没有机会运行。
重启后执行 /wb2api-setup 完成初始化。
关于免重启安装
dsh 的 profile 补丁层本身是热重载的(web profile 的 patchReload 为 live),已有插件能通过宿主的热挂载能力在运行中生效。但新装的包不行:dsh plugin add 是纯磁盘操作,而运行中的进程里没有任何存活代码会去 import 这个新包。
若你的 profile 里装了 dshmarket,它具备在运行中热挂载插件的能力(hotMount)—— 前提是该插件已在其收录的 registry 里(它的安装入口只接受收录过的来源,不接受任意 URL)。本插件暂未收录,因此目前请按上面的方式安装并重启一次。
让本插件支持免重启安装是可行的(把插件提交到 dshmarket 的收录列表,或做适配),但那是另一项工作。
dsh 起来之后,在对话里执行:
/wb2api-setup
它会依次:① 定位或下载网关可执行文件(校验 SHA256)→ ② 准备运行目录(生成 config.json,随机 api_key,仅监听 127.0.0.1)→ ③ 引导你完成账号登录 → ④ 启动网关并同步模型目录。每一步的结果都会逐条汇报,失败会停在那一步并说明原因。
首次执行会问你要登录国内版还是国际版,也可直接指定:/wb2api-setup cn 或 /wb2api-setup global。
配置
插件配置写在 profile 的 cordis.patch.yml 里(或任何引用 id: workbuddy2api 的补丁)。全部字段都可省略:
- insert:
- id: workbuddy2api
name: 'dsh-workbuddy2api'
config:
baseURL: http://127.0.0.1:7863/v1
binaryPath: /path/to/workbuddy2api/wb2a-server.exe
repoPath: /path/to/workbuddy2api
autoStart: true
realmPrefixPolicy: strip-cn
| 字段 | 默认值 | 说明 |
|---|---|---|
baseURL | http://127.0.0.1:7863/v1 | 网关 OpenAI 兼容端点根 |
apiKeyRef | WORKBUDDY2API_API_KEY | dsh 凭据库里的 ref 名 |
binaryPath | '' | 网关可执行文件绝对路径;为空则自动探测 |
repoPath | '' | 网关源码目录(也用作默认工作目录) |
workingDir | '' | 子进程工作目录;为空时取 repoPath,再取插件运行目录 ~/.dsh/wb2api/ |
listenPort | 7863 | 仅用于端口占用判定与状态展示 |
autoStart | true | dsh 启动时是否自动拉起网关 |
autoDownloadBinary | false | dsh 启动时若缺二进制是否自动下载(见下) |
binaryReleaseRepo | tearslee/dsh-workbuddy2api | 预编译二进制的发布仓库 |
binaryReleaseBase | '' | 覆盖下载根地址(自建镜像 / 钉版本) |
defaultRealm | '' | 登录默认域(cn / global);为空则每次询问 |
realmPrefixPolicy | strip-cn | 模型 id 的前缀策略,见下 |
modelsTtlSeconds | 600 | /v1/models 缓存 TTL |
requestTimeoutSeconds | 600 | 单次请求整体超时 |
idleTimeoutSeconds | 300 | SSE 帧间空闲超时(超时报可重试的 TIMEOUT) |
firstTokenTimeoutSeconds | 120 | 首个 token 等待超时 |
healthTimeoutSeconds | 3 | 单次探活超时 |
graceMs | 5000 | 子进程优雅退出宽限期 |
crashRestartLimit | 3 | 崩溃自动重启上限 |
env | {} | 传给网关子进程的显式环境变量 |
网关二进制与运行目录
/wb2api-setup 会把东西装在这些位置(都在插件自己的目录下,不碰你的其它文件):
| 用途 | 位置 |
|---|---|
| 可执行文件 | ~/.dsh/wb2api/bin/wb2a-server[.exe] |
| 配置文件 | ~/.dsh/wb2api/config.json |
| 账号凭证 | ~/.dsh/wb2api/auths/ |
| 池状态 | ~/.dsh/wb2api/data/state.json |
产物来自本仓库的 Releases(GitHub Actions 从上游源码原样交叉编译,未改一行上游代码),下载后会与 SHA256SUMS.txt 比对,不匹配就拒绝安装。
生成的 config.json 有两处刻意选择:
- 只监听
127.0.0.1(上游示例的:7863会绑0.0.0.0,等于把带账号池的网关暴露到局域网); - 随机
api_key(网关在api_key为空时完全不鉴权,而/status会暴露账号与积分)。
已存在的 config.json 绝不会被覆盖 —— 你改了池参数、冷却策略或 auth_dir 都会被保留。
autoDownloadBinary 为什么默认关
隐式时机的联网下载在离线/内网环境只会刷报错,而用户从未要求联网。显式执行 /wb2api-setup 时无视该开关(那是明确意图)。想彻底零步骤的话把它设为 true。
apiKey 的解析顺序
- dsh 凭据库(
~/.dsh/.credentials.yaml的refs.WORKBUDDY2API_API_KEY) - 同名环境变量
- 网关自己的
config.json里的api_key(含插件自动生成的那份)
同机部署时第 3 条即可命中,通常无需任何额外配置。
子进程环境会被 dsh 清洗(凭据形名称与
DSH_*全部移除),所以网关需要的变量必须经上面的env显式传入。
realmPrefixPolicy:模型 id 的前缀策略
网关的模型 id 带 realm 前缀(cn:deepseek-v4.1-flash / global:gpt-5.4)。
strip-cn(默认) —— 剥掉cn:,保留global:。模型 id 与你手工配置时期一致,历史会话与预设无需迁移。keep—— 原样透出cn:/global:。双域账号并存时可显式路由到国际版账号。
只有 CN 账号时两种策略行为一致:网关的
resolveModel对无前缀名一律判为cn域。线格式与展示是两件事:无论哪种策略,插件发出请求前都会把无前缀 id 补成
cn:<id>,避免 realm 解析歧义。
命令
| 命令 | 作用 |
|---|---|
/wb2api-setup [cn|global] | 一键就绪:准备可执行文件、运行目录、账号,启动网关并同步模型目录 |
/wb2api-status | 网关状态、账号可用性(healthy/total)、各 realm 可服务性、模型数量、账号清单(启用/禁用 + 积分/冷却) |
/wb2api-start | 启动网关(幂等:已在运行则直接复用) |
/wb2api-restart | 重启网关并刷新模型目录 |
/wb2api-login [cn|global] | 登录一个 WorkBuddy 账号(浏览器授权,凭证写入 auths/) |
/wb2api-account | 查看/切换网关使用的账号(不带参数时弹出可点选列表,见下) |
关于「复用外部网关」
启动前插件会先探 GET /healthz(该端点无需鉴权):
- 端口上已有健康网关 → 直接复用,状态记为
external,插件不会再拉一个进程。这正是「以前手工常驻了一个 wb2api」的迁移场景:装完插件一切照旧可用,但那个进程仍归你管 —— dsh 退出时不会去杀它(插件不杀自己没启动的进程)。 - 端口空闲 → 解析可执行文件并 spawn,此后它的生命周期归 dsh,
dispose时一并回收。 - 端口被非网关程序占用 → 明确报错,而不是静默失败。
所以:想让网关真正由 dsh 托管,先停掉你手工启动的那个进程,再重启 dsh。/wb2api-status 的 状态: 一行会告诉你当前是哪种情形。
登录
/wb2api-login 会向 WorkBuddy 申请一条授权链接并弹窗给你打开;你在浏览器里完成授权后,它自动取回凭证、写入 auths/ 并重启网关加载。
这套流程与上游
login.sh+cmd/login同款端点、同款凭证格式(插件用 Node 复刻,因此不需要 bash 与 python3)。国内版会顺手做一次每日签到,失败不影响登录。
不带参数时会问你要登录国内版还是国际版;也可直接写 /wb2api-login cn 或 /wb2api-login global。想固定下来就设配置项 defaultRealm。
切换账号
/wb2api-account 决定网关能看见哪些账号。不带参数时弹出可点选列表(当前选择 + 每个账号的昵称、realm、积分、冷却状态),带参数则直接切:
/wb2api-account auto # 恢复全部账号参与加权轮换(默认)
/wb2api-account 2 # 只启用列表里第 2 个账号
/wb2api-account oneid_3000 # 只启用 uid 以该前缀开头的账号(前缀必须唯一命中)
机制:重命名凭证文件,不改网关一行代码。 网关只加载匹配 workbuddy*.json 的文件(internal/auth/auth.go 的 AuthFileGlob),因此把其余文件改名成 workbuddy-<uid>.json.disabled 就等于「它们不存在」。上游升级零冲突。只改名、从不删除,auto 即把后缀去掉 —— 所以状态即文件:重启 dsh、换机器、用户手工改名,读出来的选择都一致。
为什么走「停网关 → 改名 → 启网关」(所以命令不用 supervisor.restart()):网关运行中会因 token 临近过期把凭证原子写回,落点取自它内存里的旧路径 —— 可能把刚改名的文件重新创建出来,于是切换「看着成功,一分钟后又变回去」。先停进程,写回就不会发生。
较新的网关(含本插件 Release 的二进制)已加入
auths/目录热加载(每 5 秒轮询),改名本身也会被感知;但上面这个顺序仍然保留 —— 它对旧版自编译二进制是必需的,对新版也消除了改名与 token 刷新的竞态窗口。代价只是一次 1~3 秒重启。
复用外部网关时不支持切换。 此时进程不归插件管(/wb2api-status 的 状态: 一行显示 复用外部已运行的网关),停不掉 ⇒ 必然踩上面那条。命令会直接拒绝并提示:先停掉那个进程让插件托管,或改用 /wb2api-login 加号。
取舍与风险:
- 被禁用的账号不参与轮换,也不再刷新 token。长期禁用后其
refresh_token可能失效;恢复时若报会话失效,重跑/wb2api-login即可(同一 uid 覆盖同一份凭证文件)。 - 被禁用的账号不再出现在网关
/status里,因此列表里它的积分显示为「未知」(昵称与 realm 仍从文件读出)。 - 「只启用一个号」不等于「优先用这个号」:池里只剩它一个,网关无从回退。它正在冷却时请求可能 429 或 503 —— 这是「没有别的号可换」的必然表现,不是切换失败。要恢复自动兜底就
/wb2api-account auto。 - 切换只影响网关的账号集,与你在 WorkBuddy 桌面端登录的是哪个账号无关(那是另一条路线,见 Python 版 codebuddy2api)。
已知限制
- 窗口/输出上限是「上游实际值 + 本地估计」的混合。 网关对
context_length走四级查找链:上游动态值权威 → 静态种子表 →model.json缓存 → models.dev 按需拉取;全未命中时兜底 1M。因此冷门模型的窗口可能是兜底值而非真实值。max_output_tokens同理,未收录时网关不下发该键,此时插件不声明defaultMaxTokens,由网关自行决定。 - 无账号时网关「启动 ≠ 可用」。 进程起来但
/healthz报healthy: 0,插件会把状态标为「运行中(无可用账号)」而不是假装成功。 - 切换账号仍会重启网关。 切换靠重命名凭证文件实现(见上)。较新的网关有 5 秒热加载能自行感知,本插件仍走一次重启以确保新旧二进制行为一致,代价是中断正在进行的请求 1~3 秒。
- 预编译产物来自本仓库的 CI,可能与上游最新提交有延迟。 想紧跟上游就自行
go build(把binaryPath指向你的产物,插件不会覆盖它)。上游若改动凭证格式或路由,本插件的适配可能滞后。 - 模型不出现在「设置 → 模型」页,只在聊天窗口的模型选择器里。 这是 dsh 的既定设计,不是本插件的缺陷:设置页本质是配置文件编辑器,
dsh-client-ui-settings-models只认llm-deepseek与llm-pi-ai两个 settings namespace,其余不渲染 provider 行;而本插件的 provider 是代码注册的运行时路由,模型目录来自网关/v1/models,本就不属于那个界面管辖。要确认模型可用:看聊天窗口的模型选择器,或执行/wb2api-status。
常见问题
网关起不来,日志只有 load config: read config: open config.json: ...
网关在找不到 config.json 时直接退出(上游那段「缺配置就用默认值+环境变量」的兜底因为 os.IsNotExist 判不透包装错误而永远进不去 —— 见「为什么有几处写法看起来多余」)。
先跑 /wb2api-setup:它会补建运行目录并生成配置。若你希望自己管这份目录,就把插件配置的 repoPath 指到网关源码目录(那里有你的 config.json),插件即不会再碰 ~/.dsh/wb2api/。
网关起来了但 /healthz 一直是 healthy: 0
进程没问题,是没有可用账号。执行 /wb2api-login 登录一个账号;若已有账号,用 /wb2api-status 看它是不是正在冷却(单一账号时冷却就等于全部请求失败,见下)。
启动日志出现 provider 注册被拒(DUPLICATE_ADAPTER)
插件注册 workbuddy2api 路由后,如果 ~/.dsh/settings.yaml 里还留着同名 provider,会看到:
[workbuddy2api] provider 目录注册被拒:configurable provider "workbuddy2api" is already declared
[workbuddy2api] 适配器注册被拒(DUPLICATE_ADAPTER):an adapter for provider "workbuddy2api" is already registered
dsh 仍会正常启动(插件故意不把冲突抛出去,避免把一个本来可用的环境弄成起不来),但此时模型请求走的是 settings.yaml 里那份手工配置,自动元数据不生效。/wb2api-status 也会显示这条警告。
处理:从 ~/.dsh/settings.yaml 删除整段 llm-pi-ai.providers.workbuddy2api(连同它下面那批模型元数据),然后重启:
llm-pi-ai:
providers:
workbuddy2api: # ← 从这一行删到该 provider 段的末尾
其余引用保持不变 —— provider id 没变,这些无需改动:
agent-default-model:
provider: workbuddy2api # 保持不变
subagent-model-selection:
allowedModels:
- provider: workbuddy2api # 保持不变
仓库自带脚本 scripts/migrate-settings.ps1 可按缩进精确摘除该段并自动备份(不重写整个文件,dsh 自己的注释与键顺序都保留):
# 先预览要删什么
pwsh -File scripts/migrate-settings.ps1 -WhatIf
# 确认后执行(请在 dsh 已关闭时做)
pwsh -File scripts/migrate-settings.ps1
脚本会打印回滚命令(把自动备份拷回来即可)。
为什么必须在 dsh 关闭时执行:
settings.yaml是被热监听的(dsh-settings-file 用 chokidar 监视)。在 dsh 运行中删掉该段会立刻生效,而插件那份 provider 要等重启才会注册 —— 中间窗口里模型会不可用,正在进行的会话可能直接失败。
合规声明
- 本插件不包含、不转发 workbuddy2api 的任何源码。网关源码始终来自上游仓库。
- 本仓库的 Releases 提供由本仓库 CI 从上游源码原样交叉编译的可执行文件(构建过程见
.github/workflows/release-binaries.yml:先断言上游工作区未被修改,再校验产物内嵌的vcs.modified=false)。上游不提供预编译产物,这些二进制是为了让用户不必自装 Go 工具链;许可证与免责声明仍归上游。介意第三方构建的话,请自行go build并把binaryPath指向你的产物。 - workbuddy2api 的许可与免责声明由其自身条款约束,使用前请自行阅读。
- 本插件以 MIT 许可发布,与上游无隶属关系。
开发
npm install
node node_modules/typescript/bin/tsc -p tsconfig.json # 构建(不要用 pnpm build)
node node_modules/vitest/vitest.mjs run # 单元测试
集成测试会真实发起对话(消耗账号额度),需显式开启:
$env:DSH_WB2API_E2E='1'; $env:DSH_WB2API_E2E_CONFIRM='yes'
$env:DSH_WB2API_E2E_API_KEY='<网关 api_key>'
node node_modules/vitest/vitest.mjs run --config vitest.e2e.config.ts
代码结构
| 文件 | 职责 |
|---|---|
src/index.ts | 插件入口:注册 provider、命令、生命周期 |
src/config.ts | 插件配置 schema 与默认值 |
src/models.ts | /v1/models 拉取、映射、TTL 缓存 |
src/gateway-adapter.ts | LlmAdapter 实现:请求组装 + SSE → StreamChunk |
src/gateway-supervisor.ts | 子进程托管:探测、spawn、探活、重启、回收 |
src/accounts.ts | 账号开关:扫描 auths/ 凭证文件、按切换目标重命名(停/启网关的编排在 index.ts) |
src/gateway-binary.ts | 预编译二进制的下载、SHA256 校验、运行目录(config.json)准备 |
src/login.ts | 用 Node 复刻上游 OAuth 登录(取授权链接 → 轮询 token → 落盘凭证) |
src/setup.ts | /wb2api-setup 的编排与逐步骤汇报 |
src/zip.ts | 极简 ZIP 读取(stored/deflate),避免引入解压依赖 |
src/sse.ts | SSE 空闲超时读取、工具参数归一化、孤儿工具配对清理 |
tests/unit/turn-key-contract.spec.ts | 轮级聚合键的跨语言契约 |
docs/ | 设计文档与截图 |
为什么有几处写法看起来"多余"
这些都有具体原因,改动前请先读对应注释:
- 工具结果必须发成
role:"tool"—— harness 把结果搭载在 user 角色消息里,原样发出会让网关的TurnKey取到工具输出、每个 step 换一个聚合键,RequestID 碎片化因此静默复发。详见 轮级聚合 与tests/unit/turn-key-contract.spec.ts。 Config必须用Schema.object({...})构造 ——settings.describe()会对每个注册项调用schema.toJSON(),传裸函数会抛TypeError,连带让模型设置页、主题、sidebar 的 settings API 全部失效。- SSE 按
\n切行而不是\n\n切事件 —— 网关一帧 = 一行data:。 - 工具
id靠 Map 按index复用 —— 只有首个分片带id。 function.name只在非空时更新 —— 后续分片带""(不是undefined),直接覆盖会导致unknown tool ""。- 残缺工具参数报
max-tokens而不是tool-calls—— 报tool-calls会让 harness 执行半截 JSON 并把脏参数写进会话历史,报max-tokens才会丢弃并重试。 readWithIdleTimeout的 abort 分支也要 reject —— 只清定时器会让Promise.race永远悬空,generator 既不产出也不返回。- provider 注册冲突只记日志、不抛错 —— 冲突意味着用户还没迁移旧配置,此时抛错会让 dsh 起不来,把一个可用环境变成完全不可用。
- 切换账号的顺序必须是「停 → 改名 → 启」 —— 网关运行中会用内存里的旧
FilePath把凭证原子写回,改名会被它撤销。所以/wb2api-account刻意不用supervisor.restart()(它无法在停与启之间插一步动作);改名本身失败时也会把网关拉回来,不把环境留在「网关已停」。 - 自动下载必须同时生成
config.json—— 上游main.go写了「配置不存在就用默认值+环境变量」的兜底,但它用os.IsNotExist()判断一个已被fmt.Errorf("read config: %w", …)包装过的错误,永远为假,于是进程直接log.Fatalf退出(实测:二进制放进空目录执行,4 秒内退出,只留一行load config: read config: open config.json: …)。所以插件必须自己写这份配置。 - 生成的配置只监听
127.0.0.1且必带随机api_key—— 上游示例的:7863会绑0.0.0.0(实测netstat可见),而网关在api_key为空时完全不鉴权,/status会暴露账号与积分。 - 装二进制前必须校验 SHA256,且拿不到校验和就拒绝安装 —— 下载的是会被执行的文件。校验和清单缺失时"下都下了就装"等于没有校验。
auths/目录必须先于网关启动而存在 —— 网关只在启动时探测该目录,不可读就永久跳过auths热加载(实测日志:[watch] auths 目录 ./auths 不可读,跳过热加载监听(加账号后需手动重启)),之后新增账号不会再被自动加载。
延伸阅读
- 轮级聚合:让一次对话只有一个 RequestID —— 为什么要按现在的方式序列化请求
- 轮级聚合键的跨语言验证工具 —— 用上游真实 Go 实现验证适配器产出
- 方案与调研记录(2026-09-15) —— 设计取舍与上游能力调研
许可
MIT
Plugins associés
dsh-our-free-model
ebony-vinyl/dsh-our-free-model
dsh-routing-suite
yjh051108/dsh-routing-suite
dsh-workbuddy-connect
corrinehu/dsh-workbuddy-connect
dsh-connect-workbuddy
dingminhua/dsh-connect-workbuddy