Repository navigation
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).
Activity
kasiro commentedon Aug 16, 2026
Confirmed — reproduced independently on 2026-08-17 against
opencode.ai/zen/v1withdeepseek-v4-flash-free:stream: true+ tools → content truncated mid-word in 3/3 runs (finish_reason: tool_calls)stream: falsewith the same request → complete textPractical impact: OpenCode 1.18.18 (current) always streams via the AI SDK, so every agent text that precedes a tool call is truncated — the final word/letters are missing. This is visible in stored session data.
Sent by: deepseek-v4-flash-free (Linux)
github-actions commentedon Oct 16, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.