anomalyco / anomalyco/opencode
[Bug] Zen Go /v1/chat/completions strips usage.completion_tokens_details for deepseek-v4-flash - official DeepSeek API sends it
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
The Zen Go OpenAI-compatible endpoint (https://opencode.ai/zen/go/v1/chat/completions) strips usage.completion_tokens_details.reasoning_tokens from responses for deepseek-v4-flash, while the official DeepSeek API (https://api.deepseek.com/chat/completions) reports it for the same model id, same prompt, same request shape.
The thinking text itself is forwarded (choices[0].message.reasoning_content is populated), so the model clearly engages reasoning through the gateway — only the token accounting for it disappears. Two additional DeepSeek-native usage fields (prompt_cache_hit_tokens, prompt_cache_miss_tokens) are likewise absent, and usage. prompt_tokens_details arrives hollowed ({} even when populated upstream).
This is a provider-specific gap in the gateway's normalization layer rather than a model-behavior difference: on this exact endpoint/harness/prompt combination, a kimi-k2.6 request returns a fully-populated details block minutes apart from a deepseek-v4-flash request that returns none.
Expected Behavior
Per the official DeepSeek API docs, the response usage should include:
"usage": {
"completion_tokens": 0,
"prompt_tokens": 0,
"prompt_cache_hit_tokens": 0,
"prompt_cache_miss_tokens": 0,
"total_tokens": 0,
"completion_tokens_details": {
"reasoning_tokens": 0
}
and per the OpenAI chat-completions convention that Zen's own normalization code follows (see PR #24441), completion_tokens_details.reasoning_tokens is part of the standard
usage shape the gateway is expected to forward.
Actual Behavior
"usage": {
"prompt_tokens": 124,
"completion_tokens": 198,
"total_tokens": 322,
"prompt_tokens_details": {}
}
usage.completion_tokens_detailsis absent entirely on every Zen Go response fordeepseek-v4-flash(12+ attempts over two days, at different hours, trivial and reasoning-
forcing prompts), whilemessage.reasoning_contentis delivered.usage.prompt_tokens_detailsarrives as{}(hollowed) instead of the populated shape the official API sends.prompt_cache_hit_tokens/prompt_cache_miss_tokensare dropped, masking whether DeepSeek prefix caching engaged per request.- The same harness seconds apart returns a fully-populated details block for
kimi-k2.6— so the gateway is capable of forwarding it; the gap is specific to the DeepSeek path.
Plugins
No response
OpenCode version
1.18.23
Steps to reproduce
The strip is deterministic across 12+ requests spanning two days, trivial and forcing
prompts, stream=false, at different hours.
- Send the identical request body to both endpoints (fixed combinatorial prompt that forces reasoning):
FIXED='Solve step by step internally, then respond ONLY with valid JSON {"count": <int>}. How many distinct arrangements of the letters in "MISSISSIPPI" have no two S characters adjacent?'
BODY='{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"'"$FIXED"'"}],"max_tokens":8000,"response_format":{"type":"json_object"},"stream":false}'
# Zen Go — completion_tokens_details MISSING
curl -s https://opencode.ai/zen/go/v1/chat/completions \
-H "Authorization: Bearer $OPENCODE_GO_API_KEY" \
-H "Content-Type: application/json" -d "$BODY" | jq '.usage'
# Official DeepSeek — completion_tokens_details.reasoning_tokens present
curl -s https://api.deepseek.com/chat/completions \
-H "Authorization: Bearer $DEEPSEEK_API_KEY" \
-H "Content-Type: application/json" -d "$BODY" | jq '.usage'
- Zen Go result (capture F1, cf-ray
a31b27150f0aa330-SEA,2026-08-27 12:41:52 GMT):
{"prompt_tokens": 124, "completion_tokens": 198, "total_tokens": 322,
"prompt_tokens_details": {}}
choices[0].message.reasoning_content is populated (~500 chars of thinking) — the model reasoned; only the accounting vanished.
- Official result minutes later (capture D2,
2026-08-27 11:33:14 GMT), same model id + prompt:
{"prompt_tokens": 144, "completion_tokens": 221, "total_tokens": 365,
"prompt_tokens_details": {"cached_tokens": 0},
"completion_tokens_details": {"reasoning_tokens": 214},
"prompt_cache_hit_tokens": 0, "prompt_cache_miss_tokens": 144}
- Positive control through the same gateway, same harness/prompt, minutes apart (capture F2, cf-ray
a31b2b91ef3390db-SEA,2026-08-27 12:45:43 GMT) —kimi-k2.6returns
the full OpenAI details shape:
"prompt_tokens_details": {"audio_tokens": 0, "cached_tokens": 47, "cache_write_tokens": 0},
"completion_tokens_details": {"audio_tokens": 0, "reasoning_tokens": 1757}
Captures were made with plain httpx/curl POSTs (no SDK, no OpenCode client parsing) — this rules out client-side field stripping
Screenshot and/or share link
No response
Operating System
macOS 14.5.0, arm64
Terminal
Ghostty
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 by running the paired curl requests against the Zen Go and official DeepSeek /v1/chat/completions endpoints, then inspect the gateway's provider-specific response normalization path. Compare the returned usage objects for deepseek-v4-flash, using the kimi-k2.6 response as a positive control; done means the documented DeepSeek usage fields are preserved in the normalized response.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 65/100