DeepSeek V4 DSML tool-calls are not parsed — raw markup leaks into assistant content and tools never execute
Environment
- Claurst master @595b0eb (also present in v0.1.7 release)
- Any route serving DeepSeek V4-family models over an OpenAI-compatible API: DeepSeek-V4-Flash/Pro direct, SiliconFlow, Chutes, OpenCode Zen
Problem
DeepSeek V4 models emit tool calls as tagged DSML envelopes inside plain
delta.content SSE text instead of OpenAI-style tool_calls JSON:
<|DSML|tool_calls>
<|DSML|invoke name="question">
<|DSML|parameter name="questions" string="false">[{"q": "..."}]</|DSML|parameter>
</|DSML|invoke>
</|DSML|tool_calls>Grammar notes: fullwidth vertical bars (|, U+FF5C); an ASCII <|tool_call|>
variant exists; multiple <|DSML|invoke> per envelope; string="true|false"
selects raw-string vs JSON parameter encoding.
Claurst has no DSML handling, so the raw envelope is rendered to the user as assistant content and the tool never executes — the agent loop breaks exactly like it did in every other agent before they added parsers.
Verified absent at 595b0eb: zero hits for dsml / <|tool_call|> across
api/src/providers/*, query/src/runner/stream.rs, tools/src/lib.rs,
tui/src/app.rs; zero matching issues/PRs in this repo.
Steps to reproduce
- Point Claurst at a DeepSeek V4 model (e.g.
deepseek-v4-flashon SiliconFlow, or a V4-derived preview via OpenCode Zen) - Ask anything that requires a tool call
- Observe: envelope text appears verbatim in the transcript; no tool runs
Reference implementations (prior art)
- opencode: anomalyco/opencode#24566 — "can not support DeepSeek tool calling method: DSML" (fixed); follow-up hardening: anomalyco/opencode#26498 (malformed argument JSON), anomalyco/opencode#29412 (tool-input shape repair), anomalyco/opencode#34695 (session hangs on malformed tags)
- CodeWhale: Hmbown/CodeWhale#3718 — "strip fullwidth DSML tool wrappers", Hmbown/CodeWhale#3431 — "Pin SiliconFlow DSML regression fixtures", Hmbown/CodeWhale#938 — strict tool mode hardening
- zeroclaw: zeroclaw-labs/zeroclaw#9723 —
canonical fullwidth grammar spec (nested invokes,
string=attribute encodings, char-boundary-safe buffering of fragments split across SSE chunks) - Infra-side confirmation: vllm-project/vllm#53227, vllm-project/vllm#53228, sgl-project/sglang#35736
Suggested fix scope
- Tagged-envelope parser (fullwidth + ASCII forms, nested invokes,
string=attribute) routed into the existing tool-call pipeline - Streaming text guard: buffer/suppress partial DSML fragments split across SSE chunk boundaries so protocol text never reaches the transcript mid-stream (plain text merely containing the prefixes must still pass through)
- Regression fixtures pinned to captured SiliconFlow/Zen streams (cf. Hmbown/CodeWhale#3431)
Note: DSML arrives as plain delta.content, so no provider wiring changes are
needed — parser + stream guard are sufficient (same scope boundary ZeroClaw drew
in zeroclaw-labs/zeroclaw#9723).
Happy to test a patch against a live V4 route.
Source: Kuberwastaken/claurst