Replies: 1 comment
|
Verified against rc.2 (HEAD b150a55) — your reproduction is line-exact, and this is a genuinely new mechanism for the tool-identity family (it is not covered by the null/empty-id blueprint in #4365/#4370 — more below). Two additions from the source, one of which sharpens your fix. 1. The fusion is worse than argument concatenation: id is last-writer-wins
const partial = this.ensure(chunk.index, 'tool-call') // :69 keyed by index only
if (partial.block) return
partial.toolCallId = chunk.id // :71 OVERWRITES, not guards
if (chunk.name) partial.toolCallName = chunk.name
partial.toolCallArguments += chunk.argumentsDelta // :73 concatenatesSo the fused block carries the last delta's id ( 2. It's a sibling mechanism, not another instance of the null/"" familyThe identity-loss family (#4365/#4370 and the 4-layer blueprint: translate truthy guards, 3.
|
Uh oh!
There was an error while loading. Please reload this page.
Reporting a bug here per the README pointer to Discussions (issues are disabled on this repo).
Summary
Providers behind OpenAI-compatible proxies may emit multiple logical tool
calls under the same
tool_calls[].index, distinguishing calls only byid.BlockAssembler.push()accumulatestool-call-deltas strictly byindex and concatenates their argument fragments, producing a single block
whose
argumentsare concatenated JSON objects:The agent loop's
parseArguments()preserves this as a raw string, schemavalidation rejects it, and the turn burns a round-trip on an error result.
Separately, the web api-proxy's
viewForcallsJSON.parse(raw)un-guardedand logs one error per event ("presenter failed for tool/call, falling back
to generic"), including on every history page render of affected sessions.
A related but unfixable-client-side sibling: some proxies send
finish_reason: "stop"on max-token-truncated streams, bypassing theassembler's documented max-tokens tool-call drop; truncated arguments then
dispatch and fail validation.
Status: still present in the latest published release. Verified
2026-08-24 by downloading the
@deepseek-ai/dsh-llm@0.0.1-rc.1tarball fromthe npm registry: the vulnerable
this.ensure(chunk.index, "tool-call")accumulation is present and no id-based split exists. Upgrading
@deepseek-ai/dsh(latest dist-tag0.1.1-rc.2) does not resolve this.Reproduction
Feed the assembler two same-index deltas with different ids:
Observed in production session logs (DeepSeek-hosted proxy models); full
trace available on request.
Suggested fix
In
BlockAssembler.push(),case "tool-call-delta": when the existingpartial at
chunk.indexis an open tool-call whose recordedtoolCallIddiffers from
chunk.id(both defined, non-empty), re-key onto a fresh index(
max(order)+1) before accumulating. Open blocks assemble from accumulateddeltas, so both calls complete correctly in stream order.
Defensive companion for the presentation path (
viewFor): attempt strictparse; on failure, extract the first balanced JSON object and parse that;
if still unsalvageable, return no view (generic card) without logging per
event.
Both changes verified against captured production streams; happy to share
the regression suite (8 cases incl. id-less continuation deltas, which must
NOT split).
Environment
https://github.com/deepseek-ai/deepseek-harness/issues)
@deepseek-ai/dsh@0.1.0-rc.8, Linux, node v24; vulnerable code confirmedidentical in
@deepseek-ai/dsh-llm@0.0.1-rc.1(latest)native DeepSeek official endpoints were not observed fusing.
All reactions