Repository navigation
Zen /v1/responses drops array-format assistant content -> 400 "Invalid assistant message: content or tool_calls must be set" #41766
Description
Summary
The Zen endpoint (https://opencode.ai/zen/v1/responses) rejects standard Responses API requests whose assistant message content is an array of content parts (e.g. [{"type":"output_text","text":"..."}]). It returns:
400 invalid_request_error: Error from provider (Console): Upstream request failed: [invalid_request_error] Invalid assistant message: content or tool_calls must be set
The same request works fine when the assistant content is a plain string. This affects all models on /zen (verified with deepseek-v4-flash-free and mimo-v2.5-free) and breaks any standards-compliant Responses API client (OpenAI SDK, Hermes, Codex-style clients, cc-switch + Claude Code).
Steps to Reproduce
Send this minimal request to https://opencode.ai/zen/v1/responses with any Zen API key:
{
"model": "deepseek-v4-flash-free",
"input": [
{"type": "message", "role": "user", "content": [{"type": "input_text", "text": "hi"}]},
{"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "hello"}]},
{"type": "message", "role": "user", "content": [{"type": "input_text", "text": "again?"}]}
],
"max_output_tokens": 20
}Result: HTTP 400 Invalid assistant message: content or tool_calls must be set
Change only the assistant content to a plain string:
{"type": "message", "role": "assistant", "content": "hello"}Result: HTTP 200 OK.
Expected Behavior
The Responses API spec explicitly allows assistant message content as an array of content parts (output_text, refusal, etc.). The Zen /v1/responses endpoint should accept the same format that the official OpenAI Responses API accepts — the array format is what every Responses-API SDK sends on multi-turn history replay.
Actual Behavior
Array-format assistant content is silently dropped before reaching the model, producing an empty assistant message, which the upstream model validator then rejects with content or tool_calls must be set.
Root Cause (source reference)
In packages/console/app/src/routes/zen/util/provider/openai.ts, the assistant branch only copies content when it is a string:
if ((m as any).role === "assistant") {
const c = (m as any).content
const out: any = { role: "assistant" }
if (typeof c === "string" && c.length > 0) out.content = c // array content is dropped
if (Array.isArray((m as any).tool_calls)) out.tool_calls = (m as any).tool_calls
msgs.push(out)
continue
}Note the user branch above it does handle arrays (text / input_text / image parts), so this looks like an oversight in the assistant branch specifically: it should extract text from output_text parts (or pass the array through) instead of dropping it.
Impact
- All Zen models on
/zen(free tier:deepseek-v4-flash-free,mimo-v2.5-free; likely all others too, since it's a shared code path) - Any third-party client using the standard Responses API against Zen: OpenAI SDKs, Hermes Agent, Codex/cc-switch, Cherry Studio Claude Code mode, etc.
- The
/zen/goendpoint (different implementation) handles array content correctly, so this is/zen-specific
Related Issues / PRs
- GLM-5.2 via OpenCode Go returns 400: messages.content should be a valid string #32821 — GLM-5.2 via OpenCode Go:
messages.content should be a valid string(same root cause family: array content rejected) - Assistant messages in history replay are missing
tool_callsfield, breaking OpenAI-compatible providers #24090 — assistant messages in history replay missingtool_callsfield (conversion drops assistant fields) - PR fix(opencode): filter empty assistant content for @ai-sdk/openai-compatible #27914 — filter empty assistant content for openai-compatible (related guard)
- fix(zen): guard against empty tool_result content in Anthropic provider #15375 — empty tool_result content guard in Anthropic provider (same "empty content gets rejected" class)
Workaround (for affected users)
Convert the responses request to chat completions before hitting Zen (any relay/gateway can do this), or flatten assistant content arrays to plain strings client-side. Neither is ideal — this should be fixed server-side.
Activity
yxl-lab commentedon Aug 11, 2026
I reproduced this on the current dev branch: fromOpenaiRequest drops array-form output_text content from assistant messages, producing an assistant message without content. I’d like to fix the conversion while preserving tool calls and add a focused regression test. May I work on this?
yxl-lab commentedon Sep 22, 2026
Hi @cjjdaq — thanks for the update. I noticed this issue was closed as completed, while PR #41792 was closed by automated cleanup and was not merged. Could you share what resolved the issue (for example, a commit, another PR, or a Zen backend change)? This will help confirm whether #41792 is now obsolete. Thanks!