To put it simply: “assistant” role messages with content are type “output_text”.
This message type is meant to replay a previous response object also received with that type, and you would simulate that if not using an actual assistant turn as received.
Output is an array of many types, from reasoning (with both encrypted content and useless summary items), tool calls, and the final seen output. OpenAI has taken the basic production of tokens an AI produces, and made server-side parsing into objects, and a strict formatting and sequence is then required in replay.
Each type still takes client-side understanding and rewriting for placement back as input.
What follows is AI production from a lossless input of prepared and curated documentation and iterative turns re-collected, along with task instructions and understanding. This conversation input, BTW, looks like:
user: 181888 tokens
assistant: 6041 tokens
user: 645 tokens
assistant: reasoning + this text before review and edit
Below is reference you can use to understand better the stateless conversation replay with the OpenAI Responses API. It covers:
- What streaming events to collect and how they assemble into response output items
- What the completed response.output contains (object types)
- What the API accepts in the next request’s input
- A clear output-to-input mapping table that you can implement directly
- Special handling for encrypted reasoning
The forum has limited width for the tabular data, so you may unfortunately need to scroll around or pick the large table view.
How to think about streaming collection
- Stream events arrive as “start → deltas → done” for each content part or tool call. Only the “done” events contain the authoritative finalized string.
- The final response.completed event contains a normalized output array that is equivalent to non-streaming mode. It’s the easiest single source to mirror when you build the next turn’s input for stateless conversation.
- If you are using stateless mode (store=false or zero data retention), you must replay the entire conversation context as input in the correct order. If you use conversation:{id} and let OpenAI store state, you usually just send the new user message (no replay needed).
Table 1. Streaming event lifecycles → what to collect for the final output array
| output kind (in response.completed.output) |
lifecycle events to watch |
what to collect into the item |
| Assistant message (text/refusal) |
response.output_item.added (message) response.content_part.added (output_text or refusal) response.output_text.delta → response.output_text.done response.refusal.delta → response.refusal.done response.content_part.done response.output_item.done |
Finalized assistant OutputMessage with content array of {type: output_text, text, annotations} and/or {type: refusal, refusal}. |
| Reasoning summary (preview) |
response.output_item.added (reasoning) response.reasoning_summary_part.added response.reasoning_summary_text.delta → response.reasoning_summary_text.done response.reasoning_summary_part.done response.output_item.done |
Reasoning item with summary parts (summary_text). This is for display/debug; it’s not needed for model context replay. |
| Reasoning text (OSS only) |
response.content_part.added (reasoning_text) response.reasoning_text.delta → response.reasoning_text.done response.content_part.done |
Reasoning item content parts with reasoning_text (plain text). You’ll use encrypted reasoning instead (see Table 5). |
| Function tool call (model-issued) |
response.output_item.added (function_call) response.function_call_arguments.delta → response.function_call_arguments.done response.output_item.done |
Output item: {type:function_call, call_id, name, arguments}. You’ll include this item in next input (to preserve the dialog) and separately send your tool result as function_call_output. |
| Custom tool call (model-issued) |
response.output_item.added (custom_tool_call) response.custom_tool_call_input.delta → response.custom_tool_call_input.done response.output_item.done |
Output item: {type: custom_tool_call, call_id, name, input}. Include this item in next input; separately send custom_tool_call_output with your tool’s output. |
| MCP tool call |
response.output_item.added (mcp_call) response.mcp_call_arguments.delta → .done response.mcp_call.in_progress → .completed/.failed response.output_item.done |
Output item: {type:mcp_call, server_label, name, arguments, output?, error?, status}. Include it to preserve context. |
| File search call |
response.output_item.added (file_search_call) response.file_search_call.in_progress → .searching → .completed response.output_item.done |
Output item: {type:file_search_call, status, queries, results?}. Include it to preserve context. |
| Web search call |
response.output_item.added (web_search_call) response.web_search_call.in_progress → .searching → .completed response.output_item.done |
Output item: {type:web_search_call, status, action:{search/open_page/find…}}. Include to preserve context. |
| Code interpreter call |
response.output_item.added (code_interpreter_call) response.code_interpreter_call.in_progress → .interpreting response.code_interpreter_call_code.delta → .done response.code_interpreter_call.completed response.output_item.done |
Output item: {type:code_interpreter_call, status, container_id, code?, outputs?}. Include to preserve context. |
| Image generation call |
response.output_item.added (image_generation_call) response.image_generation_call.in_progress → .generating → partial_image* → .completed response.output_item.done |
Output item: {type:image_generation_call, status, result?}. Include to preserve context. |
| Audio output (TTS style) |
response.audio.delta → response.audio.done response.audio.transcript.delta → .done |
Audio bytes and transcript are not typically re-sent. Will likely need a prior ID, like Chat Completions. If you want the transcript in context, add it yourself next turn as output_text. |
Table 2. Output array (response.completed.output) — items you may see
| output item.type |
description |
key fields to store |
needed for next-turn context? |
| message (assistant) |
Final assistant message content |
id, role=assistant, content of output_text/annotations or refusal |
Yes, include the assistant OutputMessage as-is. |
| reasoning |
Reasoning preview/text |
id, summary, content?, encrypted_content? |
Include only if you plan to supply encrypted_content back (see Table 5). Do not include summary in input. |
| function_call |
Model called your function |
id, call_id, name, arguments (JSON string) |
Yes (to preserve what model did). You will also send function_call_output with your tool’s result. |
| function_call_output |
Your function result (only appears if you sent it) |
id?, call_id, output (string or content list) |
Send from your app as input in the next turn (not created by the model). Include to preserve tool outputs. |
| custom_tool_call |
Model called your custom tool |
id, call_id, name, input |
Yes; also send custom_tool_call_output with your tool’s result. |
| custom_tool_call_output |
Your custom tool result (if you sent it) |
id?, call_id, output (string or content list) |
Send from your app as input (not created by model). Include to preserve tool outputs. |
| mcp_call |
Model invoked a remote MCP tool |
id, server_label, name, arguments, output?, error?, status |
Yes; include to preserve context. |
| mcp_list_tools |
List of tools on MCP server |
id, server_label, tools, error? |
Yes; if you want the model to remember the discovered tools. |
| mcp_approval_request |
MCP approval requested |
id, server_label, name, arguments |
Include if you want to preserve the approval interaction in context. |
| mcp_approval_response |
Human approval response (if you sent it) |
id?, approval_request_id, approve, reason? |
Send from your app as input; include later for context. |
| file_search_call |
Built-in file search call |
id, status, queries, results? |
Yes; include. |
| web_search_call |
Built-in web search call |
id, status, action:{search/open_page/find} |
Yes; include. |
| code_interpreter_call |
Built-in Python tool call |
id, status, container_id, code?, outputs? |
Yes; include. |
| local_shell_call |
Built-in local shell call |
id, call_id, action:{exec}, status |
Yes; include. |
| local_shell_call_output |
Built-in local shell result (if you sent it) |
id, call_id, output, status? |
Send from your app as input; include later for context. |
| image_generation_call |
Built-in image generation |
id, status, result? (base64) |
Yes; include. |
Notes:
- OutputMessage includes annotations you should keep (citations, file_path).
- The presence of function_call_output/custom_tool_call_output/local_shell_call_output in output depends on whether you previously submitted them as input during an earlier turn or within a multi-step flow.
Table 3. What the API accepts as input (request.input)
The input field accepts either a string (equivalent to a user message) or a list of input items. The list allows original user content and replayed prior assistant/tool items.
| input variant |
subtype/type |
purpose |
minimal shape summary |
| string |
n/a |
Simple user text |
“Hello, world” (equivalent to user role message). |
| Easy message (string) |
message (role=user/assistant/system/developer) |
Add a role-annotated message. |
{type:“message”, role, content:string} only (not typed) |
| Input text content |
input_text |
Text content part |
{type:“input_text”, text}. |
| Input image content |
input_image |
Vision input |
{type:“input_image”, image_url or file_id, detail:“low” |
| Input file content |
input_file |
File/PDF input |
{type:“input_file”, file_id? filename? file_url? file_data?}. |
| Input audio |
input_audio |
Audio input to model |
{type:“input_audio”, input_audio:{data: base64, format:“mp3” |
| Assistant message (replay) |
message (role=assistant) or OutputMessage |
Preserve the assistant’s last reply |
Best: include the OutputMessage object you received, as-is (id, role, content). Alternatively send Easy message with role=assistant. |
| Reasoning (encrypted) |
reasoning |
Preserve model chain-of-thought (encrypted) |
{type:“reasoning”, id, encrypted_content}. Only if you included it in previous output. Do not pass summary. |
| Tool call (function) |
function_call |
Replay what model did |
Copy the prior item: {type:“function_call”, call_id, name, arguments}. |
| Tool output (function) |
function_call_output |
Return your function result |
{type:“function_call_output”, call_id, output:string or content}. |
| Tool call (custom) |
custom_tool_call |
Replay what model did |
Copy prior item: {type:“custom_tool_call”, call_id, name, input}. |
| Tool output (custom) |
custom_tool_call_output |
Return your custom tool result |
{type:“custom_tool_call_output”, call_id, output:string or content}. |
| MCP tool call |
mcp_call |
Replay MCP call |
Copy prior item: {type:“mcp_call”, id, server_label, name, arguments, output?, error?, status}. |
| MCP list tools |
mcp_list_tools |
Replay MCP discovery |
Copy prior item: {type:“mcp_list_tools”, id, server_label, tools, error?}. |
| MCP approval request |
mcp_approval_request |
Replay request |
Copy prior item: {type:“mcp_approval_request”, id, server_label, name, arguments}. |
| MCP approval response |
mcp_approval_response |
Supply approval decision |
{type:“mcp_approval_response”, approval_request_id, approve, reason?}. |
| File search call |
file_search_call |
Replay call/results |
Copy prior item: {type:“file_search_call”, id, status, queries, results?}. |
| Web search call |
web_search_call |
Replay call |
Copy prior item: {type:“web_search_call”, id, status, action{…}}. |
| Code interpreter call |
code_interpreter_call |
Replay call/results |
Copy prior item: {type:“code_interpreter_call”, id, status, container_id, code?, outputs?}. |
| Local shell call |
local_shell_call |
Replay call |
Copy prior item: {type:“local_shell_call”, id, call_id, action{exec}, status}. |
| Local shell result |
local_shell_call_output |
Return your shell output |
{type:“local_shell_call_output”, id?, call_id, output, status?}. |
| Image generation call |
image_generation_call |
Replay call/result |
Copy prior item: {type:“image_generation_call”, id, status, result?}. |
| Item reference |
item_reference |
Refer to an earlier item by id |
{type:“item_reference”, id}. Useful if you persist items and want to reference rather than inline. |
Tips:
- For replaying assistant replies, you can embed the exact OutputMessage objects you received (no transformation).
- For user messages with images/files/audio, use input_* content parts inside a message item with role=user.
Table 4. Output → Input mapping (stateless conversation replay)
This is the “do-this-in-your-code” table. Every row shows how an output item from the last turn becomes one or more input items for the next turn.
| last-turn output item (from response.completed.output) |
next-turn input item to add |
transform needed? |
notes |
| message (assistant) |
OutputMessage (same object) |
No |
Include id, role=assistant, content, annotations intact. This preserves the assistant response verbatim. |
| message (assistant) with refusal |
OutputMessage (same object) |
No |
Keep refusal content part as-is to preserve context of refusal. |
| reasoning (with encrypted_content) |
reasoning |
Yes (drop summary) |
Only include if encrypted_content is present (you requested include: [“reasoning.encrypted_content”]). Set {type:“reasoning”, id, encrypted_content}. Do not include summary or plaintext content. |
| reasoning (no encrypted_content) |
— |
— |
Do not include. Reasoning summary/text is not useful input. |
| function_call (model issued) |
function_call |
No |
Copy {type, call_id, name, arguments}. Also prepare to send function_call_output with your tool’s result. |
| — (your tool ran) |
function_call_output |
You create this |
{type:“function_call_output”, call_id (from function_call), output:string or content}. |
| custom_tool_call (model issued) |
custom_tool_call |
No |
Copy {type, call_id, name, input}. Also prepare custom_tool_call_output. |
| — (your custom tool ran) |
custom_tool_call_output |
You create this |
{type:“custom_tool_call_output”, call_id, output}. |
| mcp_call |
mcp_call |
No |
Copy the entire item including arguments, output?, error?, status; keeps the MCP interaction in context. |
| mcp_list_tools |
mcp_list_tools |
No |
Copy the item; helps the model remember tool inventory. |
| mcp_approval_request |
mcp_approval_request |
No |
Copy the item if you need the approval in context. |
| — (you approved/rejected) |
mcp_approval_response |
You create this |
{type:“mcp_approval_response”, approval_request_id, approve, reason?}. |
| file_search_call |
file_search_call |
No |
Copy; results help with continuity and citations. |
| web_search_call |
web_search_call |
No |
Copy; action details maintain trace of browsing. |
| code_interpreter_call |
code_interpreter_call |
No |
Copy; includes container_id, code, outputs. |
| local_shell_call |
local_shell_call |
No |
Copy; action.exec, status preserved. |
| — (your shell executed) |
local_shell_call_output |
You create this |
{type:“local_shell_call_output”, call_id, output}. |
| image_generation_call |
image_generation_call |
No |
Copy; include result if present for downstream reference. |
Important:
- Order matters. Build input by concatenating, in order, all items from previous turns (user → assistant → tool calls → tool outputs → …), then append the new user request for this turn.
- If your org is stateless/zero-retention, always replay everything you want the model to “remember”, including tool calls and their outputs.
Table 5. Reasoning tokens: what to return vs. what to ignore
| you saw in output |
what to include next turn |
why |
| reasoning item with encrypted_content (you set include:[“reasoning.encrypted_content”]) |
Include {type:“reasoning”, id, encrypted_content}. |
This preserves the chain-of-thought for the model under encryption; only OpenAI can decrypt it. |
| reasoning.summary (summary_text parts) |
Do NOT include in input. |
Summaries are for UI/debug only; they are not used by the model. |
| reasoning.content (reasoning_text) |
Do NOT include in input. |
Plaintext reasoning is not accepted/needed; prefer encrypted form. |
| No reasoning item present |
Nothing to include. |
Not all models/requests return reasoning items. |
Table 6. Allowed original user inputs (to add alongside replayed context)
| role |
content parts |
example uses |
| user |
input_text |
General instructions/questions. |
| user |
input_image (image_url or file_id; detail) |
Vision tasks (describe image, OCR, etc.). |
| user |
input_file (file_id/filename/file_url/file_data) |
PDF/doc analysis, RAG ingestion. |
| user |
input_audio (data, format=mp3 |
wav) |
| developer/system |
input_text/image/file/audio |
Policy or tool guidance higher in instruction hierarchy. |
| assistant |
Generally you replay the OutputMessage you got |
Keep exact assistant message for continuity. |
Recommended request-building order (stateless replay)
Practical notes
- response.completed has the authoritative final output list; do your output→input mapping from there rather than by stitching deltas yourself.
- For function/custom/local shell tools: the output objects with type *_output are produced by you and must be sent back as input, keyed by call_id.
- Only response.completed contains usage; if you must meter, read it there.
- Audio is unannounced, only breadcrumbs are there. Chat Completions currently has a short-life audio ID server-side that must be re-sent (you are not allowed to simulate the AI producing audio). Responses likely will also deny you self-management.
- Avoid passing reasoning summaries back; they are not used by the model.
Minimal example strategy (conceptual)
- After collecting response.completed:
- For each element in response.output:
- If type in {message, function_call, custom_tool_call, mcp_call, mcp_list_tools, file_search_call, web_search_call, code_interpreter_call, local_shell_call, image_generation_call} → copy item as-is into next input.
- If type=reasoning and encrypted_content is present → add {type:“reasoning”, id, encrypted_content}. Ignore summary/content.
- Then append your next user message.
This approach ensures your next turn’s input mirrors the previous turn’s output in both type and order, preserving conversational coherence even without server-side conversation storage.
This is incomplete documentation for just one of the fields of “Responses” - not even getting into 53 SSE streaming events for you to create parsers for in your automatic iterator for functions, including error event that would have multiple statuses to take action on.