Neutralize tool calls with invalid JSON arguments on the wire so one malformed model generation cannot brick a session against strict OpenAI-compatible servers.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-tool-call-guard
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:alchemistwu/dsh-tool-call-guard
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
English | 中文
Neutralize tool calls with invalid JSON arguments before they reach the wire — so one malformed model generation cannot brick an entire session.
dsh plugin add dsh-tool-call-guard
Why
Some models occasionally emit a tool call whose arguments string is not valid JSON — most often unescaped inner quotes:
{"queries": [""The Idiots" trailer youtube official"]}
Strict OpenAI-compatible servers (vLLM and friends) are asymmetric about this:
- Streaming generation path — lenient. The malformed call streams through, the harness persists it into the append-only session log, and the turn appears to succeed.
- History-replay path — strict. The next request replays the poisoned tool call, the server validates it, and rejects the entire request:
400 {"message": "Assistant tool call function.arguments must be valid JSON.",
"type": "BadRequestError"}
From that moment, every subsequent request in the session fails. The session is bricked until its log is surgically repaired by hand. The same failure class exists across ecosystems (openai-agents-python #2061, vLLM #41122).
What it does
Intercepts the harness's llm/stream waterfall. For every assistant tool-call block whose arguments fail JSON.parse:
- The call becomes an honest text record (wire-only) — the model sees exactly what it emitted and can re-issue a corrected call:
[A tool call to 'web_search' was removed from history because its arguments were malformed JSON. Original arguments as emitted: {"queries": [""The Idiots" …]}] - The matching tool result is re-expressed as a plain user message — result content is preserved, and the conversation stays protocol-balanced (no dangling
tool_callsentry, no orphanrole:"tool"reply — each of those is itself a 400 on strict servers):[Tool Result: web_search] 10 results about The Idiots
The orphaned-result-as-user-message pattern follows the upstream serializer discussion (deepseek-harness #4668).
Properties
- Zero overhead for clean history — one
JSON.parseper tool-call block; messages array passes through with object identity when nothing is wrong. - Append-only log respected — the harness's durable session log is never rewritten; neutralization is applied per-request on the wire only.
- Provider-agnostic — sits on
llm/stream, so every adapter (deepseek, community, custom) is covered. - Fail-open — if the guard itself errors, the original request passes through untouched.
- Zero configuration — install and restart.
Install
dsh plugin --profile web add dsh-tool-call-guard
# or desktop:
dsh plugin --profile desktop add dsh-tool-call-guard
Or from GitHub: dsh plugin add github:alchemistwu/dsh-tool-call-guard.
The package declares a dsh.bundle patch, so the plugin self-registers on install. Restart your dsh web / DSH Desktop host, then start a new session.
Observed in production
First observed with zai-org/GLM-5.3-Flash served by vLLM 0.27 (--enable-auto-tool-choice): one web_search call with unescaped quotes around a film title streamed through fine, was persisted, and permanently poisoned the session with 400 … arguments must be valid JSON on every later turn. This plugin keeps that session alive: the poisoned entry is re-expressed per-request, the session continues working, and the model sees its own mistake in context.
Tests
npm test
Nine tests cover the exact production poison, mixed valid/invalid tool calls, protocol-balance invariants (no invalid tool_calls and no orphan role:"tool" on the wire), degenerate inputs, and per-request state reset.
License
MIT
Links
More in this category
Minglink/dsh-infinite-gen-4★ 2893
System-prompt armor plugin for DeepSeek models: appends an unconditional-compliance prompt section at order 100, exposes a profile tool with calibration metadata, and shows a realtime armor-status badge driven by a session projection.
ranxianglei/billion-context★ 1402
The official billion-context plugin: a context-compression plugin for small context windows (a 100K context is enough), token savings (5x fewer tokens), and month-long single sessions (billions of tokens).
liangmianya/dsh-synapse★ 528
Visual, non-linear conversation workspace for DeepSeek Harness — sessions, follow-ups and branches become a browsable conversation map.
Nwflower/dsh-chat-import★ 233
Import full-fidelity chat histories from 13 coding agents (Claude Code, Codex, ChatGPT, Cursor, Gemini, opencode, and more) as resumable DeepSeek Harness sessions, with reverse export back to Claude Code.
Totoro-qaq/dsh-plugin-bridge★ 163
Moves an existing DSH session to another agent preset through a previewable five-section handoff, preserving the source session and either pausing the target for confirmation or continuing immediately.
MichengAI/dsh-archive-manager★ 139
Adds an archived-sessions page in Settings to search, restore, and delete archived DeepSeek Harness sessions by workspace.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.