跳到正文
SL Blog 技术探索 · 工程实践 · AI 时代思考
返回

DSH 接入 OpenCode Go 报 400 MissingSessionID:给请求补上 x-opencode-session 即可

文章目录
  1. 一、修复:两条命令
  2. 二、根因:DSH 的适配器把这个头「故意」漏掉了
  3. 三、插件做了什么
  4. 四、验证:网关其实只查「有没有」
  5. 五、排障方法与其他备注

症状:DSH(DeepSeek Harness)把对话转发到 OpenCode Go 网关(opencode.ai/zen/go/v1)时返回 HTTP 400 MissingSessionID,任何模型都发不出去。 结论:不是 Key 配错、也不是模型问题。OpenCode Go 从 2026-09-05 前后开始强制要求每个请求带 x-opencode-session 头,而 DSH 当前版本(直到 0.1.2-rc.1)都没有在出站请求里带这个头。装一个负责补头的社区插件、重启一次服务即可,零配置。

一、修复:两条命令

# 1) 给 DSH 的 web profile 装上插件(会自动追加进 bundle 层)
dsh plugin --profile web add "@gausszhou/dsh-opencode-session-id"

# 2) 重启 dsh,让插件随服务加载

重启命令取决于你 DSH 的部署方式,不要照抄网上到处写的 systemctl --user restart dsh-web:

部署方式重启命令
系统级 systemd 服务(unit 在 /etc/systemd/system/)sudo systemctl restart dsh-web
用户级 systemd 服务systemctl --user restart dsh-web
手动 nohup/setsid 起的孤儿进程找到原启动参数重新拉起(`ps -ef

插件默认配置即可工作,重启后日志里应能看到挂载信息:

[opencode-session-id] mounted: providers=[opencode, opencode-go]
  headers=[x-opencode-session, x-session-affinity, x-client-request-id, x-session-id]
  hosts=[opencode.ai] sessionId=nanoid8(alphanumeric)

二、根因:DSH 的适配器把这个头「故意」漏掉了

OpenCode Go 网关从 2026-09-05 左右开始,把 x-opencode-session 从「建议携带」升级成「强制要求」——缺了就拒绝路由:

{
  "type": "error",
  "error": {
    "type": "MissingSessionID",
    "message": "Request is missing x-opencode-session and cannot be routed efficiently. Please see https://opencode.ai/docs/go/#where-can-i-use-it"
  }
}

DSH 这边其实是「接得住」这个能力的:它的 agent 循环会给每次对话填一个会话 ID(session-<uuid>),llm-pi-ai 适配器也会把这个 ID 转发到内部。但适配器在生成 provider 兼容配置时刻意不下发 sendSessionAffinityHeaders / sessionAffinityFormat,同时也丢掉了请求级的 options.headers——于是会话 ID 到了 HTTP 层就断了,请求空手出站。

我核对过:上游至今没有内置修复。当前最新版 @deepseek-ai/dsh@0.1.2-rc.1(2026-09-03 发布)的打包代码里搜不到任何 x-opencode-session / session-affinity 逻辑。所以短期内不是「升级 DSH」能解决的,装插件是目前最省事且可回滚的路。

三、插件做了什么

@gausszhou/dsh-opencode-session-id 从两个层面补上这个洞,且只动请求头:

  1. 监听 llm/stream 这条官方拦截链路,用 AsyncLocalStorage 把每个流式调用按会话 ID 圈起来——多个会话并发时取到的 ID 也不会串;
  2. 包装 globalThis.fetch,只对 host 匹配 opencode.ai(默认)的请求补头。URL、方法、请求体、响应一律不动,不匹配的请求原样透传。

补的头默认是:

  • x-opencode-session —— 网关真正 key 的那个头;
  • x-session-affinity / x-client-request-id / x-session-id —— 顺手带上,无副作用。

头值默认取会话 uuid 去掉 session- 前缀后做 SHA-256,再映射成 纯字母数字的 nanoid(8)(如 0RpJJnxJ)。同样的会话永远得到同样的 token,进程重启也不变,所以网关可以把多轮请求归到同一个会话。

四、验证:网关其实只查「有没有」

我用 Key 直接 curl OpenCode Go 的 /zen/go/v1/responses 做了对照:

请求结果
不带 x-opencode-sessionHTTP 400 MissingSessionID
带 x-opencode-session: TESTdsh01(任意合法值)HTTP 200,正常返回模型输出

关键结论:网关只校验「头存在且值格式合法」,并不校验会话是否真的注册过。所以插件派生出来的确定性 token 完全够用,装完即通。

五、排障方法与其他备注

  • 先别怀疑 Key:这类错误发生在鉴权之后。如果 Key 真的无效会先收到 401 Missing API key。400 MissingSessionID 说明鉴权已过,纯粹是这个头的事。
  • 改 profile 前先备份:dsh plugin ... add 会改写 ~/.dsh/profiles/<name>/package.json 并把包追加进 dsh.profile.bundles。动手前把 package.json / cordis.patch.yml / 锁文件拷一份到自己的备份目录,方便回滚。
  • 想盯着每次注入看:在 profile 的 cordis.patch.yml 里给 id: opencode-session-id 加 config.verbose: true,重启后 journalctl -u dsh-web -f 就能看到每次请求实际补了什么头(会多打日志,验证完建议关掉)。
  • Key 的存放:如果 DSH 以 systemd 跑但 unit 里没写 API Key,别在 /proc/<pid>/environ 里找——DSH 启动时会把 ~/.dsh/.credentials.yaml 读进进程内环境,运行后写入的 env 不会出现在 environ 快照里。

关联阅读:Claude Code 接入 OpenCode Go:可直接抄的配置(附踩坑清单) —— 同一网关,协议、认证头、Base URL 三处坑的对照表。


RAG 智能问答

针对本文继续提问:《DSH 接入 OpenCode Go 报 400 MissingSessionID:给请求补上 x-opencode-session 即可》