DeepSeek Harness 插件

kangjiazhen/dsh-workbuddy-codex

Star 数 ★ 1 分类 模型与账号接入 收录于 2026-10-22

通过挂载 dsh-llm-workbuddy 注册 workbuddy 模型 provider,托管本地 wb2api 网关,并内置 Responses 到 chat/completions 的转换桥,让 Codex CLI 也能用同一个网关。已在运行或已注册的部分会跳过,并通过状态工具汇报各环节状况。需本机有 wb2api 网关并有 WorkBuddy 账号。

安装

# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)

dsh plugin --profile web add github:kangjiazhen/dsh-workbuddy-codex

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。

README

English | 简体中文

一个 DeepSeek Harness 插件,负责把本地 WorkBuddy 反代链路维持在线 —— 模型选择器的 provider、它背后的网关,以及 OpenAI Codex CLI 需要的那条 Responses 桥。

在 WorkBuddy 模型能回答之前,有三样东西必须同时对上,而它们都不会把自己装成 Windows 服务:

端口 服务 作用
127.0.0.1:7863 wb2api 网关 OpenAI 兼容的 /v1/chat/completions
127.0.0.1:7864 Responses 桥(内置) Responses API → chat/completions,供 Codex CLI 使用
— workbuddy provider 模型选择器里的那条路由,从 dsh-llm-workbuddy 挂载而来

这两个进程过去都要手动启动,于是重启一次电脑之后,模型选择器会悄无声息地指向一个死掉的端口 —— provider 插件加载正常、模型列表正常,直到第一次请求才失败。本插件接管它们:加载时探测各自的健康端点,缺什么就拉起什么,并按定时器复查,进程崩了会自动回来。

它刻意既不重写网关,也不 fork 适配器。网关只做进程编排;provider 路由则是用 ctx.plugin() 委派给 dsh-llm-workbuddy 挂载出来的 —— 那个包自己拥有约 2500 行的 WorkBuddy 适配器、设置面板和每日签到调度器,再抄一份只会让两边一更新就分叉。所以装一次就同时得到 provider + 网关 + 桥。

这种委派是叠加式的,不是排他的:如果你同时保留独立的 dsh-llm-workbuddy,谁先注册这条路由谁就拥有它,另一个会在日志里说明后退让,两种装法都安全。

配置项

全部位于 profile 的 cordis.yml 中 workbuddy-codex 条目的 config 下;随包发布的 cordis.patch.yml 里是完整的默认值。

键 默认值 作用
gatewayDir ~/Documents/deepseek-harness/default-workspace/workbuddy2api-panel 网关存放 config.json 的目录。其中的 api_key 会同时用于桥和 provider,所以端口与密钥只需配一次。
provider.enabled true 是否注册 workbuddy 路由。
provider.baseURL (留空 → 用 gateway.baseURL) 适配器要访问的网关地址。
provider.discovery true 向网关的 /v1/models 拉取实时模型目录。
provider.autoCheckin true 领取 WorkBuddy 每日签到。
gateway.acceptStatus [200, 503] 网关在无可用账号时仍会跑着并返回 503,那是活着,不是挂了。
bridge.conversationId auto 对着 WorkBuddy 网关不是可选项:缺了它上游会回 400 / 11-128,网关随即停用该账号。
bridge.maxPromptTokens 1200000 估算提示词超过它就由桥在本地回 413,不把一个注定被上游以 400 prompt_too_long 拒绝的请求转发出去。0 关闭。见为什么要在本地设上限。

如果根本没装 dsh-llm-workbuddy,插件照旧监护网关与桥,并打印出补装它的那一条命令(dsh plugin add dsh-llm-workbuddy)。任何情况下都不会抛错。

为什么桥是内置的

codex-cli 0.1xx 之后删掉了 wire_api = "chat",只讲 OpenAI 的 Responses 协议(POST /v1/responses,SSE)。而 WorkBuddy 网关只实现 Chat Completions(POST /v1/chat/completions)。让 Codex 直连网关,每个请求都会死在这里:

unexpected status 404 Not Found, url: http://127.0.0.1:7863/v1/responses

这不是配置项能绕过的问题 —— Codex 在加载阶段就直接拒绝 wire_api = "chat"。转换层是必需品,所以本包自带一个:

Codex CLI --(Responses)--> 桥 :7864 --(ChatCompletions)--> 网关 :7863 --> WorkBuddy

它和 codex-chat-bridge 是同一份代码,以 bridge/codex-chat-bridge.mjs 内置(零依赖,跑在 Harness 自带的 Node 上),所以装这一个插件就够了 —— 不需要再 clone 第二个仓库,也没有任何路径要你填。

安装

dsh plugin add git+https://github.com/kangjiazhen/dsh-workbuddy-codex.git

或者从本地检出安装:

dsh plugin add file:/path/to/dsh-workbuddy-codex

插件自带 cordis.patch.yml,所以只要它成为 profile 依赖,profile 就会自动拿到 workbuddy-codex 这一条。

从 dsh-llm-workbuddy 迁移

本包已经覆盖了它原本的功能,所以如果 profile 里原本启用了它,需要改一处:把 profile 的 package.json 中 dsh.profile.bundles 里的那一行删掉,依赖保留。

"dsh": { "profile": { "bundles": [
  // "dsh-llm-workbuddy",   <- 删掉这一行
  "dsh-workbuddy-codex"
] } }

包本身必须留着 —— 本插件要 import 它来挂载 provider,而且适配器只有这一份,不存在第二份会分叉的代码。两个都留在 bundles 里也不会有害:先注册的那个拥有路由,另一个在日志里记一句 already-served;但那样服务你模型的是它而不是本插件,本插件「一次装好」的意义也就没了。

git 安装失败的话

除非另有指定,pnpm 会把 git+https://… 改写成 git+ssh://git@…,因此在没有配置 GitHub SSH 密钥的机器上,安装会失败并报 Permission denied (publickey):

npm config set git-protocol https

如果走代理,还得单独告诉 git —— pnpm 是通过调用 git 二进制实现的,不会把自己的代理设置传下去:

git config --global http.proxy http://127.0.0.1:7890

配合 Codex 使用

桥监听 7864,对外提供 Responses API。把 Codex 指过去:

# ~/.codex/config.toml
model = "global:gpt-5.3-codex"
model_provider = "wb"

[model_providers.wb]
name = "workbuddy"
base_url = "http://127.0.0.1:7864/v1"
wire_api = "responses"
experimental_bearer_token = "anything"
request_max_retries = 2
stream_max_retries = 2

桥本身不校验这个 bearer —— 它用自己的方式(从网关 config.json 读出的 key)向上游认证。填任意非空字符串即可,因为 Codex 没有它就不启动。

之后正常用 Codex 就行:

codex exec "列出当前目录的文件"

如果选到了你的账号档位用不了的模型,网关会返回 11158 model not allowed for user type [...]。那是账号属性,不是桥的故障 —— 见模型可用性。

两个绝对不能跳过的设置

1. conversation_id —— 能把账号搞封的那个。

WorkBuddy 上游会校验三个头(X-Conversation-ID、X-Conversation-Request-ID、X-Conversation-Message-ID),缺任意一个就返回 400 {"code":"11-128","msg":"request illegal"}。网关把这种情况归类为账号故障,处理方式是禁用账号 —— 而且是一轮里把碰到的账号全部禁用。它给出的原因是 re-login required,这句话有误导性:凭证通常仍然有效,解冻就能立刻恢复服务。

所以桥默认 conversationId: auto:按 Codex 会话(取自 prompt_cache_key)推导出一个稳定 id,作为 metadata.conversation_id 发送。不要把它置空,插件会在启动时告警。

如果不幸已经踩了,解冻账号即可,不需要重新登录:

# 网关面板:http://127.0.0.1:7863/panel/
curl -X POST http://127.0.0.1:7863/panel/api/accounts/<uid>/revive

2. bearer 必须和网关的 api_key 一致。

没有它,网关对每个请求都回 401。为了不让你手工抄密钥,插件直接从网关的 config.json 里读 api_key(gatewayDir 就是干这个的)并交给桥。只有要覆盖时才设 bridge.apiKey。

图片能不能进去,取决于模型目录

Codex 在拼请求之前会先读 ~/.codex/models.json。如果所选模型的条目里 input_modalities 没有 image,Codex 自己就会把附件替换成一句 image content omitted because you do not support image input —— 桥根本收不到图,模型只能去读文件或者瞎猜。

所以凡是网关报 supports_images: true 的模型,条目里必须写上:

{
  "slug": "cn:deepseek-v4.1-flash",
  "input_modalities": ["text", "image"]
}

桥对 input_image 是原样透传的(转成 {"type":"image_url","image_url":{"url":"data:..."}}),所以 Codex 侧图被吞掉,唯一原因就是目录条目不认图。以 GET /v1/models 返回的 supports_images 为准。

配置

默认值假设了常规目录结构,可在 profile 的 cordis.yml 中覆盖:

- id: workbuddy-codex
  name: dsh-workbuddy-codex
  config:
    intervalMs: 15000        # 复查间隔
    probeTimeoutMs: 2000     # 单次探测超时
    gatewayDir: ~/Documents/deepseek-harness/default-workspace/workbuddy2api-panel

    gateway:
      enabled: true
      autostart: true
      baseURL: http://127.0.0.1:7863
      acceptStatus: [200, 503]
      command: ~/Documents/deepseek-harness/default-workspace/workbuddy2api-panel/wb2api.exe
      args: [-config, config.json]
      cwd: ~/Documents/deepseek-harness/default-workspace/workbuddy2api-panel

    bridge:
      enabled: true
      autostart: true
      baseURL: http://127.0.0.1:7864
      acceptStatus: [200]
      command: ''            # 留空 = 运行内置桥
      upstream: ''           # 留空 = 用网关的 baseURL
      apiKey: ''             # 留空 = 读 gatewayDir/config.json
      conversationId: auto   # 必填 —— 见上文
      modelAllowlist: []     # 留空 = 放行所有模型

路径支持 ~、$VAR / ${VAR} 以及 Windows 的 %VAR%,所以同一份配置可以在机器之间直接拷。

bridge.command 留空是刻意的。一旦填了,插件就从「运行内置桥」切换成「监管这条命令」,用于指向另一个桥构建;CCB_PORT、CCB_UPSTREAM 等环境变量依然由插件推导。

acceptStatus 很关键

网关在进程活着但上游账号暂时不可用时(没登录,或所有账号都在冷却),/healthz 返回 503。那是活着,不是挂了。

只认 2xx 的监管器会把它读成「没启动」,于是拉起第二份、抢端口失败。所以网关那条默认接受 [200, 503],而桥只接受 [200]。

autostart: false

只观察、永不拉起。当生命周期由别的东西掌管时用这个 —— 计划任务、容器、包装脚本 —— 你只想拿到健康状态上报。

状态工具

当 tools 缝挂载后,插件会注册 workbuddy_codex_status:

gateway: up (http://127.0.0.1:7863/healthz, pid 14592)
bridge: up (http://127.0.0.1:7864/healthz)

每行都带实时探测结果、被监管子进程的 pid,以及最近一次拉起失败的错误 —— 这样「模型列表是空的」和「端口死了」不用翻日志也能分清。

如果 @deepseek-ai/dsh-tools 无法导入,工具会记一条警告然后跳过,而不是用原始描述符硬塞:tools.register() 只接受 defineTool 造出来的形状(必须带 output { schema, render }),塞原始描述符会被静默拒绝,模型永远看不到这个工具。监管与 provider 不受影响 —— 它们都不依赖这个工具。

生命周期

  • 第一次监管在 apply() 之后异步立刻执行,冷启动不会阻塞 Harness。
  • 定时器是 unref 过的 —— 空转的监管器不会把进程吊住。
  • dispose 时停掉定时器,并终止本插件自己拉起的子进程。加载插件之前就在跑的服务只探测上报、绝不接管,所以卸载插件不会杀掉别人启动的进程。

模型可用性

哪些模型能应答,取决于你的 WorkBuddy 账号档位,与本插件和桥无关。一个 personal_free_user 账号在 2026-10-21 实测能触达:

域 可用
global: gpt-5.3-codex、glm-5.3、glm-5.2、kimi-k3、kimi-k2.6、hy4-preview、step-5-preview、deepseek-v4.1-flash、minimax-m3.1-flash
cn: deepseek-v4.1-flash、deepseek-v4-pro、glm-5.3

其余 GPT 模型(gpt-5.4、gpt-5.5、gpt-6-* 全系……)一律返回 11158 model not allowed for user type [personal_free_user]。这份名单随你的订阅变动,请当作快照而非承诺。

modelAllowlist 的用途是让已知不可达的模型快速失败:桥直接本地回 400 model_not_allowed,而不是让网关把拒绝归类成账号故障、进而禁用整个池子。留空是诚实的默认。

为什么要在本地设上限

上游的 400 prompt_too_long 不只是拒绝 —— 它不返回 usage。靠 usage 得知自己大小的客户端因此看不到超了多少,只能拿最后一次成功的数字反复重发同一份历史,永远到不了触发自动压缩的阈值。实测一个真实会话:最后一次成功是 662,937 输入 token,被拒的请求体是 1,049,429,而客户端计数器整个会话冻结在 662,937(950k 窗口的 70%)。超出量只有 849 个 token,却完全无法恢复。

两件事桥直接处理了:

  • 工具结果里的图片是搬运,不是序列化。 Chat Completions 的 role: "tool" 消息只能装文本,工具返回的图片无处可放;把它 JSON.stringify 会把整个 data: URL 变成字面文本,上游分词器按约 1 token/字符计费。一张 1080p 截图(约 570k 个 base64 字符)实测 280k–594k 输入 token,一个工具结果就能填满 1M 上下文。现在桥把文本留在工具消息里、注明图片已移动,并在下一条 user 消息上(放在整批工具结果之后,以免破坏 assistant tool_calls 的配对)作为真正的 image_url part 重新挂上。修复后同一张 762,772 字符的截图只花 1,100 输入 token,模型也能正常读到图。单次最多搬运 8 张,其余按数量报告。
  • maxPromptTokens 在本地就拒绝。 超过上限时桥直接回 413 并给出估算值,完全不接触上游:不消耗账号、不触发限流冷却,报错里写清楚了该怎么做。

把它设在真实上限之上一点(1048576 的上游用 1200000),估算偏高时才不会误伤本来能过的请求。估算刻意做得很粗 —— 文本约 4 字符/token、长 base64 约 1 字符/token、每张图一个固定视觉开销 —— 所以它是保险丝,不是上下文管理器。

这个默认值放在桥自己的代码里,而不是某个启动器的环境变量里:桥会被 DSH supervisor 拉起、被 start-bridge.vbs 拉起、调试时还会手工 node bridge.mjs,只有把默认值写进代码,换一条启动路径才不会静默失去保护。

范围与限制

  • 只做进程监管加请求翻译。它不启动、停止或配置上游 provider,也不对你账号能用哪些模型发表意见。
  • 没有重启退避:瞬间死掉的子进程每个 intervalMs 会被拉起一次。如果二进制缺失或端口被别人占着,日志每轮都会说明,不会空转。
  • spawn 时设了 windowsHide: true,被监管的控制台子进程不会在 Windows 上闪窗。
  • Linux 和 macOS 用同一份代码即可;只有 cordis.patch.yml 里的默认路径是 Windows 专用的。
  • 上游 WorkBuddy 项目已停止维护,并正在收紧反代检测。本插件能让链路活着,但保不住上游通道本身。

相关

  • codex-chat-bridge —— 本插件内置的那座桥,想单独用可以去这里。
  • LiteLLM、one-api、new-api —— 完整网关,如果你需要路由与密钥管理,而不是本地进程监管。

许可

MIT

内容来自项目 README(GitHub)↗

链接

同类插件

查看整个分类 →

社区评论

评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。