在 wire 层中性化 arguments 为非法 JSON 的 tool call,避免一次模型畸形输出让 session 在严格 OpenAI 兼容服务端上永久 400。
安装
# npm 包(预构建)
dsh plugin --profile web add dsh-tool-call-guard
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:alchemistwu/dsh-tool-call-guard
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。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
在 tool call 的 arguments 到达 wire 之前,把其中 JSON 不合法的调用中性化——避免一次模型的畸形输出毁掉整个 session。
dsh plugin add dsh-tool-call-guard
为什么需要它
部分模型偶尔会生成 arguments 不是合法 JSON 的 tool call——最常见的是内部引号未转义:
{"queries": [""The Idiots" 预告片 youtube 官方"]}
严格的 OpenAI 兼容服务端(vLLM 等)对此是双重标准:
- 流式生成路径——宽松。畸形调用流出来、被 harness 持久化进追加式 session 日志,当轮看起来一切正常。
- 历史回放路径——严格。下一个请求回放这条毒化 tool call,服务端校验后拒绝整个请求:
400 {"message": "Assistant tool call function.arguments must be valid JSON.",
"type": "BadRequestError"}
从此该 session 每次请求都会失败,除非手工修复日志。同类问题在各生态都存在(openai-agents-python #2061、vLLM #41122)。
它做什么
消毒跑在 adapter 的消息序列化层(各 adapter 把 harness 消息转成 wire 格式的地方),对每个请求应用两条规则:
⚠️ 为什么不在 llm/stream waterfall 上做? 我们最初就挂在那里,实测行不通:harness 在派发前对整个请求(含 messages)deepFreeze,且 cordis waterfall 的 next() 丢弃传给它的参数——dispatch 闭包永远拿到原始 argv;严格模式 ES module 对冻结对象赋值会 throw。这个 waterfall 实际是只读的,文档没有写明(我们用运行时探针验证的)。因此插件在 waterfall 上只做探测器(发现非法 arguments 时打 warn),真正中性化在 adapter 序列化层完成。对每个 arguments 过不了 JSON.parse 的 assistant tool-call 块:
- 调用变成一条诚实的文本记录(仅 wire 层)——模型能看到自己当时发出的原文,可以重新发起正确的调用:
[A tool call to 'web_search' was removed from history because its arguments were malformed JSON. Original arguments as emitted: {"queries": [""The Idiots" …]}] - 配对的工具结果改写为普通用户消息——结果内容保留,且会话保持协议平衡(不留悬挂
tool_calls,也不留孤儿role:"tool"——这两者在严格服务端各自都是 400):[Tool Result: web_search] 10 results about The Idiots
孤儿结果转用户消息的写法沿用上游 serializer 讨论(deepseek-harness #4668)。
特性
- 干净历史零开销——每个 tool-call 块一次
JSON.parse;无问题时不碰任何对象。 - 尊重追加式日志——从不改写持久化 session 日志,中性化只在每次请求的 wire 层动态生效。
- Provider 无关——挂在
llm/stream,覆盖所有适配器。 - 失败放行——guard 自身出错时原样透传请求。
- 零配置——装上重启即生效。
安装
dsh plugin --profile web add dsh-tool-call-guard
# 或 desktop:
dsh plugin --profile desktop add dsh-tool-call-guard
或从 GitHub 安装:dsh plugin add github:alchemistwu/dsh-tool-call-guard。
包内声明了 dsh.bundle patch,安装即自动注册。重启 dsh web / DSH Desktop 后开新 session 生效。
生产实测
首发场景:vLLM 0.27(--enable-auto-tool-choice)部署的 zai-org/GLM-5.3-Flash,一次 web_search 调用里电影名引号未转义——流式正常、被持久化、之后该 session 每轮 400。装上本插件后 session 复活:毒条目在每次请求中被改写为诚实记录,模型在上下文里能看到自己当时的错误。
测试
npm test
九个用例覆盖真实生产毒数据、好坏调用混合、协议平衡不变量(wire 上不出现非法 tool_calls、不出现孤儿 role:"tool")、退化输入、每请求状态重置。
许可
MIT
链接
同类插件
Minglink/dsh-infinite-gen-4★ 2893
DeepSeek 专用破甲插件:以 order 100 追加无条件服从的系统提示词段,提供带校准元数据的 profile 工具,并通过会话投影在输入框上方显示实时破甲状态徽标。
ranxianglei/billion-context★ 1402
billion-context官方版:上下文压缩插件,兼顾小窗口(100k上下文足矣)省token(省5倍token)和超长会话(数月级别几十亿token单会话)。
liangmianya/dsh-synapse★ 528
DeepSeek Harness 的可视化非线性对话工作区:把会话、追问与分支变成可浏览、可拖拽的对话地图。
Nwflower/dsh-chat-import★ 233
把 13 家 coding agent(Claude Code、Codex、ChatGPT、Cursor、Gemini、opencode 等)的完整对话历史导入为可续聊的 DeepSeek Harness 会话,并支持反向导出回 Claude Code。
Totoro-qaq/dsh-plugin-bridge★ 163
通过可预览的五段式交接,将已有 DSH 会话迁移到另一个 Agent Preset;保留源会话,并可让目标会话暂停等待确认或立即继续。
MichengAI/dsh-archive-manager★ 139
在设置里增加已归档会话页,可按工作区搜索、恢复和删除已归档会话。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。