Skip to main content

Content Parts

Agent responses (and user messages) use content_parts to carry structured data alongside the plain content string. There are six types:

TypePurposeDetails
"text"A text segment of the responseMirrors content; used when the response interleaves text with other part types
"context"Documents/filters/tags the agent found or that the client attachedSee Passing Context
"tool"Tool execution lifecycle eventsDescribed below
"table"Structured tabular dataDescribed below
"image"An image attached to a user messageDescribed below
"user_message"A message the user sent while the turn ran, where the agent read itDescribed below

During streaming, content_parts grows with each SSE event as the agent progresses through tool calls and text generation.

Tool Content Parts​

When an agent executes tools (search, sub-agents, custom functions), it streams tool execution state to the client via content_parts with type: "tool". This lets you build UIs that show what the agent is doing in real time — "Searching for documents...", "Analyzing results...", etc.

tip

Which tool calls are streamed to the client, and which fields (params, response, display_text) are included, is controlled by the agent's streaming tool progress configuration.

Tool Lifecycle​

Each tool call progresses through a lifecycle reflected in the status field:

running → completed
running → error

A call that needs the user's approval starts with an approval card instead, see Waiting for approval.

The agent streams a ContentPartTool for each state transition:

1. Running​

When the agent decides to call a tool, it emits a tool content part with status: "running":

{
"type": "tool",
"tool": {
"tool_call_id": "call_abc123",
"name": "document_search",
"params": {"query": "What is BERT?"},
"display_text": "Searching for documents about BERT...",
"status": "running"
}
}

2. Completed​

When the tool finishes successfully:

{
"type": "tool",
"tool": {
"tool_call_id": "call_abc123",
"name": "document_search",
"params": {"query": "What is BERT?"},
"response": {"document_ids": ["abc_0", "def_0"], "count": 2},
"display_text": "Found 2 documents about BERT",
"status": "completed"
}
}

3. Error​

If the tool execution fails:

{
"type": "tool",
"tool": {
"tool_call_id": "call_abc123",
"name": "document_search",
"params": {"query": "What is BERT?"},
"display_text": null,
"status": "error"
}
}

Waiting for approval​

A tool that needs the user's approval streams an approval card before it runs: a part with status: "completed", display_text: "Waiting for your confirmation", and a response whose status is "confirmation_required", carrying the app name and icon, the title, the JSON schema of the call's inputs (parameters_spec, for clients that let the user edit them) and allow_session:

{
"type": "tool",
"tool": {
"tool_call_id": "call_send_1",
"name": "ms365__create-draft-email",
"params": {"to": "b@x.com", "subject": "Q3 report"},
"response": {
"status": "confirmation_required",
"app": "Microsoft 365",
"icon_url": "https://example.com/icon.png",
"title": "Create a draft email",
"parameters_spec": {"type": "object", "properties": {}},
"allow_session": true
},
"display_text": "Waiting for your confirmation",
"status": "completed"
}
}

Render the card with the call's inputs and the choices Allow once, Always allow in this chat (only when allow_session is true) and Deny. The turn waits; answer with tool_decisions, see Approving Tool Calls for the card fields and the decisions.

Once decided, the card is replaced in the same answer, under the same tool_call_id:

DecisionReplaced by
ApprovedThe call's running part and its result
Declined, no decision within 10 minutes, or the user sent a message insteadA part with response.status: "confirmation_denied" and display_text: "Declined"
Nobody could be asked (a non-streaming or stateless request)A part with response.status: "confirmation_unavailable", declined at once

ContentPartTool Fields​

FieldTypeDescription
tool_call_idstringUnique identifier for this specific tool invocation
namestringThe tool that was called
paramsobject or nullVisible parameters (may be a subset of actual params, filtered by the tool's configuration)
responseobject or nullTool result (present only when status is "completed", may be filtered). On a call that needs approval, its status field carries confirmation_required, confirmation_denied or confirmation_unavailable
display_textstring or nullHuman-readable text describing what the tool is doing or did
status"running" / "completed" / "error"Current lifecycle state

Rendering in a UI​

A typical pattern for rendering tool content parts during streaming:

import json
import sseclient

# ... set up streaming request ...

client = sseclient.SSEClient(response)
tool_states = {} # track by tool_call_id

for event in client.events():
message = json.loads(event.data)

for part in message.get("content_parts") or []:
if part["type"] != "tool":
continue

tool = part["tool"]
tool_id = tool["tool_call_id"]
status = tool.get("status")

if status == "running":
# Show a spinner or "in progress" indicator
print(f"⏳ {tool.get('display_text', tool['name'])}")
tool_states[tool_id] = "running"

elif status == "completed":
# Replace spinner with completion state
print(f"✓ {tool.get('display_text', tool['name'])}")
tool_states[tool_id] = "completed"

elif status == "error":
print(f"✗ {tool['name']} failed")
tool_states[tool_id] = "error"

UI Recommendations​

  • Show display_text rather than raw name + params — it is formatted for human consumption.
  • Use tool_call_id to correlate running → completed/error transitions for the same call.
  • Multiple tool calls can run concurrently — track each by tool_call_id.
  • The params and response fields are optional and may contain filtered subsets. Use them for advanced UIs (e.g., showing search parameters or result previews) but don't depend on their presence.

Multiple Tool Calls​

Agents may call multiple tools in parallel. Each tool has its own tool_call_id. In a single SSE event, you may see multiple tool content parts at different lifecycle stages:

{
"content_parts": [
{"type": "tool", "tool": {"tool_call_id": "call_1", "name": "search", "status": "completed", ...}},
{"type": "tool", "tool": {"tool_call_id": "call_2", "name": "summarize", "status": "running", ...}},
{"type": "text", "text": "Based on the search results..."}
]
}

Image Content Parts​

A user message carries images as parts of type "image", each a data URI. A message can carry several:

{
"sender": "user",
"content": "What does this chart show?",
"content_parts": [
{"type": "image", "image": {"image_uri": "data:image/png;base64,<data>"}},
{"type": "image", "image": {"image_uri": "data:image/jpeg;base64,<data>"}}
]
}

User Message Content Parts​

In an agent's message, a part of type "user_message" marks a message the user sent while the turn ran, at the point where the agent read it. It carries the message's text and the message_id it was sent with, so a client can tell which of its messages were read. Render it as the user's own bubble inside the answer. Sending while a turn runs is described in Sending to a Conversation.

{"type": "user_message", "text": "Only papers from 2025, please.", "message_id": "client-chosen-id"}

Table Content Parts​

Agents can also return structured tabular data via content_parts with type: "table". This is used when the response contains data best presented as a table rather than prose.

{
"type": "table",
"table": {
"format": "row",
"headers": ["Document", "Score", "Source"],
"rows": [
{"Document": "BERT: Pre-training of Deep Bidirectional Transformers", "Score": "0.95", "Source": "arXiv"},
{"Document": "Attention Is All You Need", "Score": "0.87", "Source": "arXiv"}
]
}
}

Table Formats​

FormatShapeUse when
"row"rows: array of {header: value} objectsMost common — each row is a record
"columnar"columns: object mapping header → [values]Efficient for large uniform datasets

Exactly one of rows or columns is present. headers provides column ordering for rendering.