前言¶
在 DeepSeek Harness 里开发或加载插件时,一次工具调度崩溃(例如 Cannot read properties of undefined (reading 'prepare'))就会在会话日志里留下孤儿 tool_calls。之后每一轮请求都被 400 INVALID_REQUEST 拒绝,重试无效,会话卡死,只能放弃这个会话。
下面介绍的 dsh-messages-sanitizer 就是为这个问题准备的:它在运行时自动把 messages 数组修回合法,让对话继续。
问题是怎么发生的¶
OpenAI 兼容协议要求工具调用成对出现,且 tool 消息必须紧邻其 assistant tool_calls 消息:
assistant { content: ..., tool_calls: [{ id: "call_A", ... }] }
tool { tool_call_id: "call_A", ... } ← 必须紧跟,覆盖每个 id
当一次工具调度在「记录 assistant tool_calls / tool/call 之后、产出 tool 结果之前」崩溃时,会话日志会留下一个没有 tool 消息响应的孤儿 tool_calls。下一轮请求拼出的历史是这样的:
[..., assistant{tool_calls:[write]}, user{...}] ← 非法
API 直接返回 400 INVALID_REQUEST,且重试时历史原封不动,反复被拒。如果崩溃后还有多次失败重试,日志里还会留下多条重复的 user 消息,横在孤儿 assistant 与注入点之间——这时连「补插 tool 消息」也无法满足紧邻约束。
这是什么¶
dsh-messages-sanitizer 是一个 DeepSeek Harness 插件,作者 Leeminjing,MIT 许可证,当前版本 0.1.1。一句话定位:自动修复 messages 数组中无效的 tool_calls/tool 消息配对(孤儿 tool_calls),阻止 400 INVALID_REQUEST 导致的会话卡死。
同类故障在 DeepSeek Harness 官方仓库也有跟踪:Discussion #4843 描述了会话历史中存在无配对 result、或 id/name/arguments 残缺的 tool_calls 时 chat-completions 接口返回 400 的问题,官方从 harness 源码层给出根因修复。本插件与那个修复是互补而非替代:它不修改 DSH 源码,只在运行时自动矫正 messages 数组,相当于一个安全网。已污染的旧会话、或跑在仍带该 bug / 工具调度崩溃的 harness 版本上的用户,都能被自动救回并继续对话,而不必等待 harness 发版。
四层修复机制¶
插件按处理时机分四层工作。
1、自动续跑(agent/status,主路径)¶
工具调度崩溃后,趁 agent 回到 idle 时,把合成 error tool-result 送回报文箱并唤醒它。错误会原样上报给 LLM,下一步换工具、重试还是向用户报告,由 LLM 自己决定。这保证 AI 消息始终是最后一条,对话不卡死。
2、预防(agent/pre-step)¶
追踪每个会话中「已声明但从未被 tool/result 响应」的调用;下一轮请求构建前,把合成 error tool-result 消息插到该步消息最前面(覆盖重启后恢复的旧孤儿)。合成消息随 decision.messages 以 user/message 事件落盘,deriveMessages() 从根上恢复合法,从源头杜绝 400。
3、治愈(agent/request-error)¶
如果 API 仍因 tool_calls 配对/紧邻违规返回 400(例如旧版本已污染的会话、或孤儿 assistant 后面已横着过期消息),插件用 surface 替换完成修复,然后强制重试一次(重试基于修复后的日志重建请求)。具体动作:
- 把悬空 assistant 消息改写成不含
tool_calls的版本(剥离无响应的调用); - 把孤儿 tool 消息中和成纯文本 user 消息;
- 恢复被误剥但结果仍紧邻的 assistant(还原 tool_calls,保留历史工具上下文);
- 折叠崩溃重试留下的重复 user 消息。
修复是幂等的:第二次遇到同一违规时无事可做,自然回退下游策略,不会无限重试。
4、兜底(llm/stream)¶
对每个请求做纯数组矫正,包括配对、紧邻重排、孤儿/重复丢弃、空 assistant 丢弃。循环构建的请求是冻结的,只告警不改写;compaction、session-title 等自建 messages 的非冻结请求则直接原地替换。
安装与启用¶
先执行安装命令,再重启 harness 即生效(插件随 profile 层栈自动加载):
dsh plugin --profile web add github:Leeminjing/dsh-messages-sanitizer
更新到最新版:
dsh plugin --profile web update dsh-messages-sanitizer
配置项只有一个:enabled(boolean,默认 true),是总开关。停用时,删除 cordis.patch.yml 中的插入行,或改为:
- insert:
- id: messages-sanitizer
name: 'dsh-messages-sanitizer'
disabled: true
验证¶
插件自带测试,覆盖纯函数矫正、会话追踪、请求失败修复和真实 cordis/Session 集成:
cd dsh-messages-sanitizer
node --test # 共 40 个用例
测试均用真实 @deepseek-ai/dsh-session 的 foldSurface / Session 校验,包括真实崩溃序列的端到端模拟、污染日志修复后的幂等验证、surface 替换在真实 Session 上执行,以及请求失败修复只在 tool_calls 配对 400 时干预、修复后强制重试一次且不会无限重试。
适用场景与注意事项¶
适合两类人:一是在 DeepSeek Harness 上开发插件、遇到过工具调度崩溃导致会话卡死的开发者;二是手上还有已污染的旧会话想救回的用户。后者不需要等 harness 发版,治愈路径会在第一次请求失败时自动剥离悬空调用并重试成功。
安装和使用前注意几点:
- 插件以当前 dsh 进程权限运行,安装前应检查源码与许可证(MIT)。
- 要求 Node >= 18。插件是零构建的纯 ESM,修改插件代码后无需重新构建,重启 harness(或让 cordis HMR 重载)即生效。
- 运行期 peer 依赖
@deepseek-ai/cordis、@deepseek-ai/dsh-agent、@deepseek-ai/dsh-llm、@deepseek-ai/dsh-session、@deepseek-ai/schemastery,与 harness 运行时同源。 - 仓库目录下的 node_modules 是指向 harness 运行时
~/.dsh/profiles/node_modules的 junction,仅为本地node --test提供依赖解析,harness 运行期不依赖它。 - 它与 DeepSeek Harness 官方 Discussion #4843 的源码层修复互补而非替代:官方修复在 DSH 源码里根治,本插件不修改 DSH 源码、在运行时自动矫正 messages 数组,可救回已污染的旧会话。
结尾¶
dsh-messages-sanitizer 解决的是一个很具体的死局:一次工具调度崩溃,就让整个会话再也无法继续。它通过续跑、预防、治愈、兜底四层机制在运行时把 messages 数组修回合法,且修复幂等、不会引入新的循环问题。插件目录页在 https://www.skillhub.cn/plugins/Leeminjing/dsh-messages-sanitizer ,源码与 README 见 https://github.com/Leeminjing/dsh-messages-sanitizer 。