Summary
When a conversation history contains an assistant text message and opencode sends it
to an OpenAI Responses (/responses) endpoint, the request body uses:
{"role": "assistant", "content": [{"type": "output_text", "text": "..."}]}
This shape is not valid per OpenAI's Responses input schema. Real api.openai.com
tolerates it, but strict OpenAI-compatible servers that validate with the official
OpenAI SDK types (e.g. a self-hosted Qwen server exposing /v1/responses) reject it
with a 422/400 validation error, so switching a session to such a model fails.
Environment
- opencode 1.18.18 (Snap, stable)
- Default runtime (AI SDK path),
@ai-sdk/openai 3.0.84
- Server: self-hosted OpenAI-compatible
/v1/responses endpoint (validates with
openai Python SDK pydantic types)
Repro
- Configure a custom provider/model pointing at an OpenAI-compatible
/responses
server.
- Switch an existing session to that model so the history (including prior
assistant text) is resent.
- The request fails with a pydantic
ValidationError on the input array, e.g.:
loc: ('body', 'input', 'list[union[EasyInputMessageParam, Message, ResponseOutputMessageParam, ...]]', 2, 'ResponseFunctionToolCallParam', 'call_id')
msg: 'Field required'
input: {'role': 'assistant', 'content': [{'type': 'output_text', 'text': '...'}]}
Root cause
@ai-sdk/openai builds assistant input items as
{ role: "assistant", content: [{ type: "output_text", text }], id }
(convertToOpenAIResponsesMessages, dist/index.js ~3127).
Per the official OpenAI schema (openai-python types):
EasyInputMessageParam allows role: "assistant" but content must be
str | list[input_text | input_image | input_file] — output_text is not
a valid easy-input content part.
output_text is only valid inside a full output item
{ type: "message", role: "assistant", id, status, content: [...] }
(ResponseOutputMessageParam, where type/id/status are required).
OpenAI's own API is lenient and accepts the non-conformant shape, which is why
this has gone unnoticed. The same bug also exists in the native llm protocol
(packages/llm/src/protocols/openai-responses.ts, lowerMessages).
Proposed fix
Emit assistant text as an easy-input message with input_text parts:
{"role": "assistant", "content": [{"type": "input_text", "text": "..."}]}
or, when an item id is available, the complete
{"type": "message", "role": "assistant", "id", "status": "completed", "content": [{"type": "output_text", "text": "..."}]}.
A local fix for the native protocol is ready and tests pass
(59 tests, packages/llm). The AI SDK path will additionally need a fix in
vercel/ai (or a bumped @ai-sdk/openai) to fix the released builds.
Summary
When a conversation history contains an assistant text message and opencode sends it
to an OpenAI Responses (
/responses) endpoint, the request body uses:{"role": "assistant", "content": [{"type": "output_text", "text": "..."}]}This shape is not valid per OpenAI's Responses input schema. Real
api.openai.comtolerates it, but strict OpenAI-compatible servers that validate with the official
OpenAI SDK types (e.g. a self-hosted Qwen server exposing
/v1/responses) reject itwith a 422/400 validation error, so switching a session to such a model fails.
Environment
@ai-sdk/openai3.0.84/v1/responsesendpoint (validates withopenaiPython SDK pydantic types)Repro
/responsesserver.
assistant text) is resent.
ValidationErroron theinputarray, e.g.:Root cause
@ai-sdk/openaibuilds assistant input items as{ role: "assistant", content: [{ type: "output_text", text }], id }(
convertToOpenAIResponsesMessages, dist/index.js ~3127).Per the official OpenAI schema (
openai-pythontypes):EasyInputMessageParamallowsrole: "assistant"butcontentmust bestr | list[input_text | input_image | input_file]—output_textis nota valid easy-input content part.
output_textis only valid inside a full output item{ type: "message", role: "assistant", id, status, content: [...] }(
ResponseOutputMessageParam, wheretype/id/statusare required).OpenAI's own API is lenient and accepts the non-conformant shape, which is why
this has gone unnoticed. The same bug also exists in the native llm protocol
(
packages/llm/src/protocols/openai-responses.ts,lowerMessages).Proposed fix
Emit assistant text as an easy-input message with
input_textparts:{"role": "assistant", "content": [{"type": "input_text", "text": "..."}]}or, when an item id is available, the complete
{"type": "message", "role": "assistant", "id", "status": "completed", "content": [{"type": "output_text", "text": "..."}]}.A local fix for the native protocol is ready and tests pass
(59 tests,
packages/llm). The AI SDK path will additionally need a fix invercel/ai(or a bumped@ai-sdk/openai) to fix the released builds.