Replies: 3 comments
|
Proposed fix (fork PR): hungnnvidia#3 — sanitize unpaired UTF-16 surrogates to U+FFFD during wire serialization so sessions are not bricked by HTTP 400. |
|
Proposed fix (fork PR): hungnnvidia#3 — sanitize unpaired UTF-16 surrogates to U+FFFD during DeepSeek wire serialization. |
|
Cross-route A/B for exactly this poison (lone UTF-16 surrogate in history), which corroborates where the fix belongs and offers an unlock for stuck sessions: We injected an assistant history message containing a lone high surrogate ( For a session already bricked by this: re-routing it through a declared Scope note: our injection is the minimal single-lone-high-surrogate shape; if your tool result carries other malformed sequences, verify against your reproduction. |
Uh oh!
There was an error while loading. Please reload this page.
Symptom
Every new turn in a session fails immediately with:
It starts happening after one particular step and then every later turn of that session fails, even after resuming the session.
Reproduction
dsh web) withllm-deepseekpointed athttps://api.deepseek.com, modeldeepseek-v4-pro,reasoning_effort: max.\uD800–\uDFFF, e.g.\ud980,\ud9bf).Root cause
The adapter serializes the body with
JSON.stringify(packages/llm/llm-deepseek/src/adapter.ts). A lone surrogate in a message string becomes a\uXXXXescape — valid JSON per Node's parser, but the DeepSeek API's JSON parser rejects unpaired surrogate escapes with HTTP 400 and a non-JSON body:Confirmed with a minimal request:
Because the error body is plain text (not JSON),
adapter.tsfalls back to the genericDeepSeek API error (HTTP 400)message, which is why the real reason is invisible. Since the poisoned message sits durably in the session log, the whole session is bricked — the same failure as the earliercontent or tool_calls must be setnull-content bug.Expected behavior
Proposed fix
packages/llm/llm-deepseek/src/serialize.ts: addsanitizeSurrogates()(replaces lone high/low surrogates with U+FFFD, preserves valid pairs) and apply it to every string that goes on the wire (user/system/tool content,reasoning_content, tool-callarguments, tool descriptions). Verified live: the previously-failing request returns HTTP 200 and streams after the fix.I have the change ready locally (source + tests + rebuilt bundle) and can open a PR if useful.
All reactions