Skip to main content
Version: v2.0

Chat message format

Messages travel in and out of an agent at data.inputs.messages and data.outputs.messages. Two wire representations exist, selected by a request header.

x-ag-messages-formatRepresentation
absent, or agentaAgenta messages and Agenta event frames
vercelVercel AI SDK UIMessage objects and UI Message Stream parts

The header is independent of Accept, which selects the transport. See Invoke an agent.

Agenta message

The agent runtime reads two fields from every inbound message.

FieldTypeRequiredDefaultDescription
rolestringYesThe message author. A message with no role is dropped.
contentstring or array of content blocksNo""The message body. A bare string is treated as a single text block.
{"role": "user", "content": "Summarize the release notes for 0.106."}

The agent workflow's inputs schema declares messages as the messages catalog type, whose roles are developer, system, user, assistant, tool, and function. Fetch it with GET /api/workflows/catalog/types/messages.

Content blocks

A content block is an object with a type and the fields that type uses. Keys are camelCase on the wire; the parser also accepts the snake_case spellings.

FieldTypeUsed byDescription
typestringalltext, image, resource, tool_call, or tool_result.
textstringtextThe text.
datastringimage, resourceBase64 payload.
mimeTypestringimage, resourceMedia type.
uristringimage, resourceLocation of the payload.
toolCallIdstringtool_call, tool_resultTies a call to its result.
toolNamestringtool_call, tool_resultThe tool's name.
inputanytool_callThe arguments the model produced.
outputanytool_resultWhat the tool returned.
isErrorbooleantool_resulttrue when the call failed.

Unset fields are omitted rather than sent as null.

{
"role": "assistant",
"content": [
{"type": "tool_call", "toolCallId": "call_019d952f", "toolName": "bash", "input": {"command": "ls docs"}},
{"type": "tool_result", "toolCallId": "call_019d952f", "toolName": "bash", "output": "index.md\n", "isError": false}
]
}

A tool_call block followed by its tool_result block is how a completed tool turn is replayed on a later turn, so the model resumes from the result instead of calling the tool again.

Vercel UIMessage

With x-ag-messages-format: vercel, inbound messages are Vercel AI SDK UIMessage objects.

FieldTypeRequiredDescription
idstringNoMessage id. When the last message is an assistant message, its id is reused as the reply's messageId.
rolestringYesThe message author.
partsarrayYesThe message body. A message with no parts falls back to the Agenta message shape.

Agenta converts each part to content blocks before the run:

Inbound part typeBecomes
texta text block
filean image block when mediaType starts with image/, otherwise a resource block
tool-<name>, dynamic-toola tool_call block, plus a tool_result block when the part carries output, errorText, or an approval decision
tool-output-available, tool-output-error, tool-output-denieda tool_result block
tool-approval-responsea tool_result block keyed by toolCallId
tool-approval-requestdropped
anything else, including reasoningdropped

A tool part's state decides the result block:

stateResult
output-availabletool_result with output, isError: false
output-errortool_result with output set to errorText, isError: true
output-deniedtool_result carrying {"approved": false}
approval-respondedtool_result carrying {"approved": <bool>} and, when present, interactionToken
input-availabletool_call only, no result

Answering an approval

When a tool call needs approval the turn ends and the stream carries a tool-approval-request frame. To resume, send the paused assistant message back with the decision recorded inline on the gated tool part.

{
"id": "019d952f-0000-0000-0000-000000000000",
"role": "assistant",
"parts": [
{
"type": "tool-bash",
"toolCallId": "call_019d952f",
"input": {"command": "rm -rf build"},
"state": "approval-responded",
"approval": {"id": "019d952f-0000-0000-0000-000000000001", "approved": true}
}
]
}

Replay every part of the paused message, not only the gated one. The runner fingerprints the ordered tool-call ids in the history; a missing id evicts the warm session and the next turn starts cold.

Batch response messages

With Accept: application/json, data.outputs.messages is the folded turn. Every message carries a role and a content.

Produced byShape
assistant text{"role": "assistant", "content": "<text>"}
a tool call{"role": "tool", "content": "", "tool_call_id": "...", "tool_name": "...", "input": {...}}
a tool result{"role": "tool", "content": "<output>", "tool_call_id": "...", "is_error": false}

Reasoning, usage, file, and data events produce no message.

data.outputs also carries stop_reason when the turn produced one, and pending_interaction when stop_reason is paused.

With x-ag-messages-format: vercel, the same message list is projected into UIMessage objects before the response is returned.

Streamed frames: Agenta format

With Accept: text/event-stream and no x-ag-messages-format, each frame is one SSE data: line holding {"type": ..., "data": {...}}. There is no terminating sentinel.

data: {"type": "message_delta", "data": {"id": "m1", "delta": "Here are "}}

data: {"type": "done", "data": {"stopReason": "end_turn"}}

With Accept: application/x-ndjson or application/jsonl the same objects are newline-delimited JSON instead.

typedata fields
messagetext
message_startid
message_deltaid, delta
message_endid
thoughttext
thought_startid
thought_deltaid, delta
thought_endid
tool_callid, name, input, rawInput, render
tool_resultid, output, data, isError, denied, render
interaction_requestid, kind (user_approval, user_input, or client_tool), payload
interaction_responseid, kind, payload
dataname, data, transient
fileurl, mediaType
usageinput, output, total, cost
errormessage
donestopReason, traceId

A tool_call may be emitted more than once for the same id. The first carries the announcement; a later one refreshes the arguments. Prefer rawInput over input when it is present.

Streamed frames: Vercel format

With Accept: text/event-stream and x-ag-messages-format: vercel, each frame is one SSE data: line holding a UI Message Stream part, and the stream ends with data: [DONE]. Lines starting with : are keepalive comments and carry no data.

Response headers on this path include x-vercel-ai-ui-message-stream: v1, x-ag-messages-format: vercel, and x-ag-messages-version: v1.

Part typeFields
startmessageId, messageMetadata.sessionId
start-step
text-start / text-delta / text-endid; delta on the delta part
reasoning-start / reasoning-delta / reasoning-endid; delta on the delta part
tool-input-starttoolCallId, toolName
tool-input-availabletoolCallId, toolName, input
tool-output-availabletoolCallId, output
tool-output-errortoolCallId, errorText
tool-output-deniedtoolCallId
tool-approval-requestapprovalId, toolCallId
data-input-requestid, data
data-interactionid, data.kind, data.payload
data-renderdata.toolCallId, data.render
data-committed-revisiondata.variantId, data.revisionId, data.version
data-<name>data, transient
fileurl, mediaType
errorerrorText
finish-step
finishfinishReason, messageMetadata.usage, messageMetadata.traceId

A turn always opens with start and start-step and always closes with finish-step and finish, including when it fails. A turn that produced no content parts emits an error part reading The agent produced no output.

tool-input-available is emitted more than once for one tool call while its arguments stream in, and toolName can change case between frames. Only the last frame for a given toolCallId holds the final arguments.

finishReason

The model's raw stop reason is mapped onto the AI SDK set before it is sent.

Raw stop reasonfinishReason
end_turn, stop_sequencestop
max_tokenslength
tool_use, tool_calls, function_calltool-calls
refusal, content_filtercontent-filter
paused, cancelledother
anything elseunknown

Values already in the set (stop, length, content-filter, tool-calls, error, other, unknown) pass through unchanged. A turn paused for an approval finishes with other.