Skip to content

[BUG] OpenAI Responses: assistant messages sent as {role:"assistant", content:[{type:"output_text"}]} rejected by strict OpenAI-compatible servers #42613

Description

@j34ni

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

  1. Configure a custom provider/model pointing at an OpenAI-compatible /responses
    server.
  2. Switch an existing session to that model so the history (including prior
    assistant text) is resent.
  3. 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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions