Skip to content

后台 LLM 调用在 OpenCode 路由上必然 400(缺 x-opencode-session);kbEnabled 无 UI 入口 #668

Open

Description

@YingshuoZhang

概要

两个可用性问题,都是「面板/文档说了,代码里没有」:

  1. OpenCode 系模型路由下,后台 LLM 调用必然 400:总结提炼(summarize)、记忆巩固(dream)、实体抽取(entityExtract)都不传 sessionId,而 pi-ai 的 opencode / opencode-go 适配器只在 options.sessionId 存在时才注入 x-opencode-session 头,于是被网关拒绝。表现是「记忆 → 状态」页全是失败。
  2. kbEnabled 没有任何 UI 入口:键在 config schema 与 settings 白名单里,但 v0.13.0 的 FEATURE_GROUPS 不含它;而知识库空态文案仍写着「打开 设置 → 记忆(mneme) → 功能 → 知识库」,用户照做找不到,只能调 HTTP 接口才能开。

环境

  • DeepSeek Harness 桌面客户端 0.2.2-rc.1(Windows 11)
  • @modusensus/dsh-mneme 0.13.0
  • 模型路由:opencode-go / deepseek-v4.1-flash(pi-ai 的 opencode-go provider)
  • 未配置 dreamProvider / summarizeProvider / entityExtractionProvider,走 agent 默认模型

问题一:后台 LLM 全部 400 MissingSessionID

现象

「记忆 → 状态」里「总结提炼」「记忆巩固」每条都是失败,点开错误是 llm stream error 或完整 400。

证据

llm_audit_logs(截取):

id trigger_source operation_type model_id status error_message
283 autoSummarize summarize_compress opencode-go:deepseek-v4.1-flash error llm stream error
282 autoSummarize summarize_compress 同上 error llm stream error
281 autoSummarize summarize_compress 同上 error llm stream error
280 autoDream dream_consolidate 同上 error llm stream aborted or errored (INVALID_REQUEST: 400: {"type":"MissingSessionID",...})
279 entityExtract entity_extract 同上 error INVALID_REQUEST: 400: {"type":"MissingSessionID","message":"Request is missing x-opencode-session ..."}

distill_runs 里同一条错误原文:

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

根因

pi-ai 的 opencode-go 适配器 dist/providers/opencode-headers.js:

const OPENCODE_SESSION_HEADER = "x-opencode-session";
function withSessionHeader(options) {
  if (!options?.sessionId || hasHeader(options.headers, OPENCODE_SESSION_HEADER)) return options;
  return { ...options, headers: { ...options.headers, [OPENCODE_SESSION_HEADER]: options.sessionId } };
}

即只有 options.sessionId 存在才注入该头。

  • 主对话正常:@deepseek-ai/dsh-agent-loop 在请求里带 sessionId: this.session.id(lib/index.js:1090)。
  • 插件后台调用失败:mneme 的 6 处 ctx.llm.stream(...) 都没有传 sessionId:
    • lib/dream.js:291
    • lib/dream/sleep.js:87
    • lib/summarize.js:967
    • lib/index.js:152(实体抽取)
    • lib/pre-inject-gate.js:239
    • lib/api.js:830

建议

后台调用传一个稳定标识,例如 sessionId: "dsh-mneme"(或按 run id 派生)。它只用于网关路由,不参与会话归属,和现在「LLM 审计行 session_key 恒 NULL」的语义一致。

另一条路是宿主侧兜底(见下方「临时绕过」),但那需要用户自己装插件,不该是默认体验。

问题二:kbEnabled 没有 UI 入口

  • lib/config.js:1181 定义了 kbEnabled: z.boolean().default(false)
  • lib/settings.js 的 FEATURE_FLAG_BOOLEANS 白名单包含 kbEnabled
  • 但 lib/client.js 的 FEATURE_GROUPS、FEATURE_ADVANCED_BOOLS、FEATURE_STRINGS 都不含它 → 设置页永远不会渲染这一项
  • 空态文案 kb.empty.disabled(lib/client.js:372)却写着「打开 设置 → 记忆(mneme) → 功能 → 知识库,打开开关后重启 DSH 生效」

对比:M.O. 那组下架时 client.js:4197 留了注释说明「随时可恢复」,知识库没有。

建议

二选一:

  1. 把 kbEnabled 加回 FEATURE_GROUPS(比照 chorographyEnabled 放在 enhance 组),恢复 UI 开关;
  2. 或把空态文案改成实际可操作的说法,例如「在 PUT /api/dsh-mneme/features 里置 kbEnabled: true 后重启」。

临时绕过(供其他用户参考)

补头(宿主侧插件,参加 llm-pi-ai/request-headers waterfall):

ctx.on('llm-pi-ai/request-headers', (request, headers, next) => {
  if (request?.provider === 'opencode-go' && !headers.has('x-opencode-session')) {
    headers.set('x-opencode-session', String(request.sessionId ?? 'dsh-background'))
  }
  return next()
})

开知识库:

curl -X PUT http://127.0.0.1:<port>/api/dsh-mneme/features \
  -H 'content-type: application/json' \
  -d '{"kbEnabled":true}'
# 然后重启 DSH;GET /api/dsh-mneme/kb/collections 返回 200 即生效

验证补头是否成功,可用 POST /api/dsh-mneme/test-model {} —— 它走的正是后台无会话调用路径,修复前 400,修复后返回 {"ok":true,...,"reply":"ok"}。

Activity

  1. modusensus commented on Nov 12, 2026

    @modusensus
    Member

    两半都看了,结论分开说。

    一、x-opencode-session 缺头:属实,已修(PR #670)

    你说得对,而且原因比标题更宽一点:不是某个调用点漏了,是七处后台调用全都没传 sessionId。

    • src/dream.js / src/dream/sleep.js 的本地 streamText(覆盖全部 7 个 dream/sleep 相位)
    • src/summarize.js(蒸馏)
    • src/index.js(实体抽取流)
    • src/pre-inject-gate.js(注入门控)
    • src/api.js(连通性探测)
    • src/image-describe.js(图像描述)

    pi-ai 的 dist/providers/opencode-headers.js 里 withSessionHeader 只在 options.sessionId 存在时才注入 x-opencode-session,没有就直接 400——所以这条在多数网关上完全无声,只有 OpenCode 这类严格网关会炸出来。

    修法:新增 src/llm-session.js,BACKGROUND_SESSION_ID = "dsh-mneme" + withBackgroundSession(options),七处统一包一层。两个刻意选择:

    • 固定标识,不按 run id 派生——按 run id 会让网关把每次整理当成新会话,限流按会话计数时反而更容易撞墙,排障时也分不清请求来自哪一层。
    • 宿主已给 sessionId 时不覆盖——注入链路上可能带真实用户会话过来,那时换掉 id 等于伪造来源。

    PR:#670 (测试与五轮变异自证在正文里)

    二、kbEnabled 没有 UI 入口:只对 0.13.0 成立,beta 起已修

    我核了 tag 差异:v0.13.0 的 lib/client.js 只有 group.basic / core / enhance / dream / scope 五组,确实没有扩展组的入口。

    v0.14.0-beta.1 起的提交 97317871(#607「三个扩展子系统的总闸提到设置页首屏」)新增了 group.ext: ["chorographyEnabled", "kbEnabled", "diaryEnabled"],beta.1 / beta.2 / beta.3 都带着它。

    所以这条升级到 0.14.0-beta.3 就好,不用改代码。顺带说明一句:这个版本整体是预发布,npm i dsh-mneme 装的还是 0.13.0,需要显式装 beta。

    一点补充

    本机库里 MissingSessionID 记录是 0 条——因为当前走的是 xdpool/workbuddy,不受这个头影响。也就是说 #670 修的是你换到 OpenCode 或其它严格网关时的硬失败,不是现在就能观测到的回归。这个边界我写进 PR 的「已知边界」了,免得以后有人以为是修了一个线上正在发生的故障。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions