Bundle
dsh-tool-call-guard
Neutralize tool calls with invalid JSON arguments on the wire, so one malformed model generation cannot brick a session against strict OpenAI-compatible servers (vLLM et al).
- Source
- alchemistwu
- License
- MIT
- Updated
- Updated 6 days ago
Readme
# dsh-tool-call-guard
English | [中文](README.zh.md)
Neutralize tool calls with invalid JSON **arguments** before they reach the wire — so one malformed model generation cannot brick an entire session.
[](LICENSE)
[](https://github.com/deepseek-ai/deepseek-harness)
[](https://www.npmjs.com/package/dsh-tool-call-guard)
[](#tests)
```sh
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:
```json
{"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](https://github.com/openai/openai-agents-python/issues/2061), [vLLM #41122](https://github.com/vllm-project/vllm/issues/41122)).
## What it does
Intercepts the harness's `llm/stream` waterfall. For every assistant tool-call block whose `arguments` fail `JSON.parse`:
1. **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" …]}]
```
2. **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_calls` entry, no orphan `role:"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](https://github.com/deepseek-ai/deepseek-harness/discussions/4668)).
### Properties
- **Zero overhead for clean history** — one `JSON.parse` per 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
```sh
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
```sh
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
Install
dsh plugin --profile web add github:alchemistwu/dsh-tool-call-guard
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-tool-call-guard from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.