anomalyco / anomalyco/opencode
`deepseek-v4-flash-free` (zen free tier): stream drops the final text delta before `tool_calls`; non-streaming is complete
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
deepseek-v4-flash-free via https://opencode.ai/zen/v1/chat/completions (free tier) drops the final text delta before tool_calls from streamed responses: the sentence before the tool call ends mid-word, no error, finish_reason: "tool_calls". The same request with stream: false returns the complete text. Non-deterministic — identical requests sometimes complete.
Plugins
None — raw HTTP, no client SDK involved.
OpenCode version
N/A (API-level; also reproduced via Vercel AI SDK streamText, pi's SDK streamSimple, and headless opencode run)
Steps to reproduce
repro.sh — curl + python3, no other deps:
#!/usr/bin/env bash
# Usage: ./repro.sh [runs]
set -u
KEY="${OPENCODE_API_KEY:-public}" # free tier accepts "public"
N="${1:-8}"
PHRASE="The quick brown fox jumps over the lazy dog"
req() { # $1 = stream(true/false)
curl -sS --max-time 90 \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d "{\"model\":\"deepseek-v4-flash-free\",\"messages\":[{\"role\":\"user\",\"content\":\"Say exactly '$PHRASE' then call the tool get_time with an empty object.\"}],\"stream\":$1,\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"get_time\",\"parameters\":{\"type\":\"object\",\"properties\":{}}}}]}" \
"https://opencode.ai/zen/v1/chat/completions"
}
stream_text() {
python3 -c '
import sys, json
text = ""
for line in sys.stdin:
line = line.strip()
if not line.startswith("data:"): continue
p = line[5:].strip()
if p == "[DONE]": continue
try: o = json.loads(p)
except Exception: continue
for c in o.get("choices", []):
d = c.get("delta") or {}
if isinstance(d.get("content"), str): text += d["content"]
print(text)'
}
trunc=0
for i in $(seq 1 "$N"); do
body=$(req true)
id=$(printf '%s' "$body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
text=$(printf '%s' "$body" | stream_text)
if [[ "$text" == *"lazy dog"* ]]; then s="complete"
elif [[ "$text" == *"fox"* ]]; then s="TRUNCATED"; trunc=$((trunc+1))
else s="no-text"; fi
printf 'run %2d id=%-22s %-9s ...%s\n' "$i" "${id:0:22}" "$s" "${text: -32}"
sleep 0.3
done
echo
echo "streaming: $trunc/$N truncated (expected tail: '$PHRASE')"
echo "--- non-streaming control (stream:false) ---"
req false | python3 -c 'import sys,json; o=json.load(sys.stdin); print("content:", o["choices"][0]["message"]["content"])'
Screenshot and/or share link
Output of one run:
run 1 id=router-c651e84ac345a25 TRUNCATED ...e quick brown fox jumps over the
run 2 id=router-4b3abcc145d862d TRUNCATED ...e quick brown fox jumps over the
run 3 id=router-59f37695f6c301f TRUNCATED ...
run 4 id=router-906ca39fd5009d0 TRUNCATED ...e quick brown fox jumps over the
run 5 id=router-4f990f930cbd9cd TRUNCATED ...e quick brown fox jumps over the
run 6 id=b7ea5f8c-1b71-4d5a-9d3 complete ...rown fox jumps over the lazy dog
run 7 id=router-87d80b13af7041b TRUNCATED ...e quick brown fox jumps over the
run 8 id=router-b0831d8d0f852b9 TRUNCATED ...e quick brown fox jumps over the
streaming: 7/8 truncated (expected tail: 'The quick brown fox jumps over the lazy dog')
--- non-streaming control (stream:false) ---
content: The quick brown fox jumps over the lazy dog
Second run: streaming: 6/6 truncated, non-streaming control complete.
Operating System
Linux (container); client-independent
Terminal
N/A
Additional evidence
- Raw SSE of a truncated run (verbatim capture, no parser):
content='The'→content=' quick brown fox jumps over the'(last content delta) →tool_calls→finish_reason: tool_calls→[DONE]. The tail' lazy dog'never appears as a content delta. The full sentence IS present in the same stream'sreasoning_contentdeltas and in thestream: falseresponse — the model generated it; the SSE dropped it. - Truncation correlates with the response chunk
idprefix. Across ~100 streamed runs with tools: every truncated response had arouter-*id;uuid-prefixed responses were always complete. In the systematic matrix: 28/33router-*runs truncated (~85%), 0/14uuidruns. - Request parameters don't change it: reproduced with/without
temperature,x-opencode-session,stream_options,max_tokens, and different tool-schema shapes. - Not a parsing artifact: with the Vercel AI SDK and pi's SDK, the SDK's emitted text matched the captured raw bytes byte-for-byte (both truncated).
Related: #40888 (streaming + tools truncated on the Go tier), #40176 (deepseek-v4-flash on Go drops the tool call after text — same text↔tool transition defect, opposite side).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the issue's repro.sh and compare the raw SSE for stream:true against the stream:false control, focusing on the content delta immediately before tool_calls. The issue is complete when the streamed response preserves the full text before the tool call across the affected router-* responses without regressing tool-call delivery.
Written by the indexing model from the issue text.
Assessment
- Domain
- api, backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 28/100