Skip to content

Data types

The data objects that the other classes take and return.

Message dataclass

Message(role: Literal['user', 'assistant'], content: tuple[Block, ...], tokens: int | None = None)

One message in the context (state.messages).

The system prompt is not a message; it goes in Request.system.

role instance-attribute

role: Literal['user', 'assistant']

"user" or "assistant". Notices and tool results are user messages.

content instance-attribute

content: tuple[Block, ...]

The blocks in order: text, tool calls (assistant only), tool results (user only) and provider-specific blocks such as thinking.

tokens class-attribute instance-attribute

tokens: int | None = None

The token count of the whole context up to and including this message, from API usage. Set on assistant replies only; None if unknown.

is_notice property

is_notice: bool

True for a user message whose first text starts with NOTICE_PREFIX (see Message.notice).

text property

text: str

The text of all TextBlocks joined together.

tool_calls property

tool_calls: tuple[ToolCall, ...]

The tool calls in this message, in order.

user classmethod

user(text: str, *images: Image) -> Message

A user message: the text, then the images if any.

With images the text may be empty (Message.user("", image)); the empty text block is left out, because providers reject it.

Raises:

Type Description
TypeError

text is not a string, or an image is not an Image.

notice classmethod

notice(text: str) -> Message

A notice: a user message from your loop or a tool, not from the person. Its text starts with NOTICE_PREFIX ("[notice] "), which is added if missing, so the model can tell it apart.

assistant classmethod

assistant(text: str) -> Message

An assistant message with a single text, for importing a conversation (State(messages=[...])).

ToolCall dataclass

ToolCall(name: str, args: Mapping[str, Any], id: str)

One tool call the model requested. Also a block of an assistant message.

Calls are told apart by id, and two calls with the same id hash the same.

name instance-attribute

name: str

The tool name the model called.

args instance-attribute

args: Mapping[str, Any]

The arguments the model gave. A read-only dict (a FrozenDict, frozen at every level): reading, **args, dict(args) and json.dumps work, and changing it raises TypeError. A call is a record of what the model asked, so copy the arguments (dict(call.args)) before changing them.

id instance-attribute

id: str

The call id. Tool results refer to the call by this id.

ToolSpec dataclass

ToolSpec(name: str, description: str, input_schema: dict[str, Any])

One tool shown to the model.

name instance-attribute

name: str

The tool name the model calls.

description instance-attribute

description: str

What the tool does, as the model reads it.

input_schema instance-attribute

input_schema: dict[str, Any]

The arguments as a JSON Schema object.

Request dataclass

Request(system: str | None, messages: tuple[Message, ...], tools: tuple[ToolSpec, ...] = (), tool_choice: Literal['auto', 'none'] = 'auto')

A request the Agent sends to the Model. Model settings (max_tokens etc.) belong to the Model object.

system instance-attribute

system: str | None

The system prompt, or None.

messages instance-attribute

messages: tuple[Message, ...]

The context to send.

tools class-attribute instance-attribute

tools: tuple[ToolSpec, ...] = ()

The tools shown to the model.

tool_choice class-attribute instance-attribute

tool_choice: Literal['auto', 'none'] = 'auto'

"auto" lets the model call tools. "none" still sends the tool definitions but forbids calling them.

Reply dataclass

Reply(message: Message, usage: Usage, context_tokens: int | None = None, stop_reason: str | None = None, model: str | None = None)

One reply from the Model.

message instance-attribute

message: Message

The assistant message. Blocks stay in the order the provider gave.

usage instance-attribute

usage: Usage

The usage of this one request (requests=1).

context_tokens class-attribute instance-attribute

context_tokens: int | None = None

The token count of the whole context up to and including this reply (input + cache read + cache write + output). None if unknown.

stop_reason class-attribute instance-attribute

stop_reason: str | None = None

Exactly what the provider gave, e.g. "end_turn", "tool_use" or "max_tokens".

model class-attribute instance-attribute

model: str | None = None

The model that answered, as the provider reported it. The requested name if the provider did not say. None only for a custom Model that does not set it. It can differ from the requested name (an alias, a router, a fallback); usage.cost is still computed from the requested Model's price.

text property

text: str

The text of the reply message.

tool_calls property

tool_calls: tuple[ToolCall, ...]

The tool calls in the reply message, in order.

Usage dataclass

Usage(input_tokens: int = 0, output_tokens: int = 0, cache_read_tokens: int = 0, cache_write_tokens: int = 0, requests: int = 0, cost: float | None = None)

Token usage. Every provider's counts are mapped to the same meanings, so total input is input_tokens + cache_read_tokens + cache_write_tokens.

Adding two Usages (+) returns a new one. Its cost is None if either side has requests but an unknown cost.

input_tokens class-attribute instance-attribute

input_tokens: int = 0

Input tokens that did not go through the cache.

output_tokens class-attribute instance-attribute

output_tokens: int = 0

Output tokens.

cache_read_tokens class-attribute instance-attribute

cache_read_tokens: int = 0

Input tokens read from the cache.

cache_write_tokens class-attribute instance-attribute

cache_write_tokens: int = 0

Input tokens newly written to the cache.

requests class-attribute instance-attribute

requests: int = 0

The number of model requests.

cost class-attribute instance-attribute

cost: float | None = None

Estimated dollars. None when the price is unknown, which is different from 0.

cache_hit_rate property

cache_hit_rate: float | None

The fraction of total input read from the cache. None when there is no input.

Price dataclass

Price(input: float, output: float, cache_read: float | None = None, cache_write: float | None = None)

Dollars per million tokens. Cache prices default to the input price when not given.

Example

Anthropic("claude-sonnet-5", price=Price(input=3, output=15, cache_read=0.3))

input instance-attribute

input: float

Dollars per million input tokens that did not go through the cache.

output instance-attribute

output: float

Dollars per million output tokens.

cache_read class-attribute instance-attribute

cache_read: float | None = None

Dollars per million tokens read from the cache. None uses input.

cache_write class-attribute instance-attribute

cache_write: float | None = None

Dollars per million tokens written to the cache. None uses input.

cost

cost(usage: Usage) -> float

The cost of usage in dollars.

History entries

The items of state.history. alpineagents.HistoryEntry is their union: check kind (or use isinstance or match) to know which one an entry is, and so what its content holds. There are 11 classes. Model... entries are about the model, ToolResultEntry about a tool call.

Class kind content
MessageEntry user, notice str, or a tuple of TextBlock and Image when the message has images
ModelRequestEntry model_request str, the model name that was asked (provider/name)
ModelReplyEntry model_reply Reply
ModelEventEntry model_event ModelEvent
ToolResultEntry tool_result The result sent to the model: str, or a tuple of TextBlock and Image. outcome says how the call ended, including denied and cancelled
ExchangeEntry ask, human An object with question and answer. An ask entry also has usage
ContextChangeEntry context_change ContextChange: compact, clear_tool_results, import or restore
RunStartEntry run_start AgentInfo
StopEntry stop Stopped, or None for a stop that was cleared
ExtraDataEntry extra_data The keys that changed with their new values. removed lists the deleted keys
ErrorEntry error str such as "TimeoutError: ..."

Every entry has kind, content, turn and at. The other fields are only on the classes where they mean something: call on ToolResultEntry and ErrorEntry, for example. The subagent fields are child_ids and usage on ToolResultEntry, and parent_id and parent_call_id on RunStartEntry.

Values in history are read-only. A ToolCall's args, a ModelEvent's data, an ExtraDataEntry's content and an Exchange's answer are frozen dicts and lists: they work with indexing, json.dumps and ==, and a change raises TypeError. dict(value) makes an editable copy.

MessageEntry dataclass

MessageEntry(*, kind: Literal['user', 'notice'], content: str | tuple[TextBlock | Image, ...], turn: int, at: datetime = _stamp())

A user message or a notice added to the State (State(messages=...) records its messages as one context_change entry instead).

kind instance-attribute

kind: Literal['user', 'notice']

"user": from the person (Message.user). "notice": from your loop or a tool (Message.notice).

content instance-attribute

content: str | tuple[TextBlock | Image, ...]

The text, as a str when the message was only text. A notice starts with "[notice] ". A tuple of TextBlock and Image when the message has images.

turn instance-attribute

turn: int

state.turn when the entry was recorded.

at class-attribute instance-attribute

at: datetime = _stamp()

When the entry was recorded, as a timezone-aware UTC datetime.

message property

message: Message

The user message this entry added: Message.user(...) with the text and images.

from_message classmethod

from_message(message: Message, *, turn: int) -> MessageEntry

The entry for a user message: "notice" if it is a notice, else "user".

Raises:

Type Description
ValueError

the message is not a user message, or has blocks other than text and images.

ModelRequestEntry dataclass

ModelRequestEntry(*, kind: Literal['model_request'] = 'model_request', content: str, turn: int, at: datetime = _stamp())

think is sending a request to the model. A request with no model_reply after it (an error entry follows instead) did not change the context. Only think writes one: ask and compact requests do not, so history shows which model wrote each reply, even when the model changed inside one run.

kind class-attribute instance-attribute

kind: Literal['model_request'] = 'model_request'

Always "model_request".

content instance-attribute

content: str

The model name that was asked, as the Agent's model gives it (provider/name).

turn instance-attribute

turn: int

The turn this request starts (state.turn before it, plus one).

at class-attribute instance-attribute

at: datetime = _stamp()

When the entry was recorded, as a timezone-aware UTC datetime.

ModelReplyEntry dataclass

ModelReplyEntry(*, kind: Literal['model_reply'] = 'model_reply', content: Reply, turn: int, at: datetime = _stamp())

A model reply from think.

kind class-attribute instance-attribute

kind: Literal['model_reply'] = 'model_reply'

Always "model_reply".

content instance-attribute

content: Reply

The Reply, including reply.model, the model that answered.

turn instance-attribute

turn: int

state.turn when the entry was recorded.

at class-attribute instance-attribute

at: datetime = _stamp()

When the entry was recorded, as a timezone-aware UTC datetime.

ModelEventEntry dataclass

ModelEventEntry(*, kind: Literal['model_event'] = 'model_event', content: ModelEvent, turn: int, at: datetime = _stamp())

Something the Model went through, such as a fallback or a retry.

kind class-attribute instance-attribute

kind: Literal['model_event'] = 'model_event'

Always "model_event".

content instance-attribute

content: ModelEvent

What the Model reported.

turn instance-attribute

turn: int

state.turn when the entry was recorded.

at class-attribute instance-attribute

at: datetime = _stamp()

When the entry was recorded, as a timezone-aware UTC datetime.

ToolResultEntry dataclass

ToolResultEntry(*, kind: Literal['tool_result'] = 'tool_result', content: ToolResultContent, call: ToolCall, outcome: ToolOutcomeKind, late: bool = False, error: BaseException | None = None, child_ids: tuple[str, ...] = (), usage: Usage | None = None, turn: int, at: datetime = _stamp())

The result of a tool call, as the model got it, whatever way the call ended (outcome): it ran, it was an input error, it was closed by an exception ("(aborted: TimeoutError)", "(interrupted by user)"), or a permission kept it from running.

kind class-attribute instance-attribute

kind: Literal['tool_result'] = 'tool_result'

Always "tool_result".

content instance-attribute

content: ToolResultContent

The result sent to the model: a str, or a tuple of TextBlock and Image (result_text turns either into one string).

call instance-attribute

call: ToolCall

The call this is the result of.

outcome instance-attribute

outcome: ToolOutcomeKind

How the call ended: DONE, ERROR, INPUT_ERROR, ABORTED, INTERRUPTED, DENIED or CANCELLED. A plain string such as "denied" becomes the matching ToolOutcomeKind.

late class-attribute instance-attribute

late: bool = False

True if it arrived after its call was already closed (the model gets it as a notice at the next think).

error class-attribute instance-attribute

error: BaseException | None = field(default=None, compare=False)

The ToolError behind an error result (its __cause__ is the original exception when exception_handler made it), or the exception that closed the call. Not saved by a store, and ignored by ==.

child_ids class-attribute instance-attribute

child_ids: tuple[str, ...] = ()

The ids of the subagent States this call started (runs started inside the call), in the order they started. Open one with state.child(child_id). Empty for a call that started none. A late result repeats the ids of its call.

usage class-attribute instance-attribute

usage: Usage | None = None

The usage of those subagents, and of their own subagents, that this State has not counted yet: what each one used since it joined the call. It is added to state.usage when the entry is recorded, so a late result holds only what the subagents used after the call was closed. An empty Usage() when they used nothing; None only when the call started no subagent.

turn instance-attribute

turn: int

state.turn when the entry was recorded.

at class-attribute instance-attribute

at: datetime = _stamp()

When the entry was recorded, as a timezone-aware UTC datetime.

is_error property

is_error: bool

True if the model got it as an error result: the outcome is anything but DONE.

ExchangeEntry dataclass

ExchangeEntry(*, kind: Literal['ask', 'human'], content: Exchange, usage: Usage | None = None, turn: int, at: datetime = _stamp())

A question and its answer. The context does not change.

kind instance-attribute

kind: Literal['ask', 'human']

"ask": agent.ask asked the model. "human": the Human was asked (ask_human, DecideByHuman).

content instance-attribute

content: Exchange

The Exchange: the question and its answer.

usage class-attribute instance-attribute

usage: Usage | None = None

The usage of the model request behind an "ask". None for "human".

turn instance-attribute

turn: int

state.turn when the entry was recorded.

at class-attribute instance-attribute

at: datetime = _stamp()

When the entry was recorded (when the answer came), as a timezone-aware UTC datetime.

ContextChangeEntry dataclass

ContextChangeEntry(*, kind: Literal['context_change'] = 'context_change', content: ContextChange, turn: int, at: datetime = _stamp())

The context was replaced or shrunk (compact, clear_tool_results), imported at the start, or the State went back to an earlier point (restore).

kind class-attribute instance-attribute

kind: Literal['context_change'] = 'context_change'

Always "context_change".

content instance-attribute

content: ContextChange

What changed. content.kind says which.

turn instance-attribute

turn: int

state.turn when the entry was recorded.

at class-attribute instance-attribute

at: datetime = _stamp()

When the entry was recorded, as a timezone-aware UTC datetime.

RunStartEntry dataclass

RunStartEntry(*, kind: Literal['run_start'] = 'run_start', content: AgentInfo, parent_id: str | None = None, parent_call_id: str | None = None, turn: int, at: datetime = _stamp())

A run started (agent.run). content is the Agent that runs it, which is what a later resume of this State is compared with.

kind class-attribute instance-attribute

kind: Literal['run_start'] = 'run_start'

Always "run_start".

content instance-attribute

content: AgentInfo

The AgentInfo of the Agent that started the run.

parent_id class-attribute instance-attribute

parent_id: str | None = None

The id of the State whose tool call started this run (a subagent run), or None for a run started anywhere else.

parent_call_id class-attribute instance-attribute

parent_call_id: str | None = None

The ToolCall.id of that tool call, or None together with parent_id.

turn instance-attribute

turn: int

state.turn when the entry was recorded.

at class-attribute instance-attribute

at: datetime = _stamp()

When the entry was recorded, as a timezone-aware UTC datetime.

StopEntry dataclass

StopEntry(*, kind: Literal['stop'] = 'stop', content: Stopped | None, turn: int, at: datetime = _stamp())

The current run is set to stop, and why (state.stopped): an until function returned true, the loop reached its limit, finish was called, or a permission stopped the turn.

content is None for a record that clears an earlier stop. A @loop that goes on after an inner loop stopped records it, so state.stopped is not left at the inner loop's reason.

kind class-attribute instance-attribute

kind: Literal['stop'] = 'stop'

Always "stop".

content instance-attribute

content: Stopped | None

Why: StoppedByUntil, StoppedByLimit, StoppedByFinish or StoppedByPermission, or None for a stop that was cleared.

turn instance-attribute

turn: int

state.turn when the entry was recorded.

at class-attribute instance-attribute

at: datetime = _stamp()

When the entry was recorded, as a timezone-aware UTC datetime.

ExtraDataEntry dataclass

ExtraDataEntry(*, kind: Literal['extra_data'] = 'extra_data', content: Mapping[str, Any], removed: tuple[str, ...] = (), turn: int, at: datetime = _stamp())

state.extra_data changed (State(extra_data=...) or edit_extra_data()).

kind class-attribute instance-attribute

kind: Literal['extra_data'] = 'extra_data'

Always "extra_data".

content instance-attribute

content: Mapping[str, Any]

The keys that were set, with their new values (JSON). A read-only dict (a FrozenDict).

removed class-attribute instance-attribute

removed: tuple[str, ...] = ()

The keys that were deleted.

turn instance-attribute

turn: int

state.turn when the entry was recorded.

at class-attribute instance-attribute

at: datetime = _stamp()

When the entry was recorded, as a timezone-aware UTC datetime.

ErrorEntry dataclass

ErrorEntry(*, kind: Literal['error'] = 'error', content: str, error: BaseException | None = None, call: ToolCall | None = None, turn: int, at: datetime = _stamp())

An exception raised in think, use_tools or run. An exception is recorded once.

An error right after a model_request (no model_reply) means the request failed and the context did not change: the turn is taken back.

kind class-attribute instance-attribute

kind: Literal['error'] = 'error'

Always "error".

content instance-attribute

content: str

Its type and message, e.g. "TimeoutError: timed out" (only the type if the message is empty).

error class-attribute instance-attribute

error: BaseException | None = field(default=None, compare=False)

The exception. None in a State loaded from a store (content keeps the text). Ignored by ==.

call class-attribute instance-attribute

call: ToolCall | None = None

The call whose tool or permission raised it, or None.

turn instance-attribute

turn: int

state.turn when the entry was recorded.

at class-attribute instance-attribute

at: datetime = _stamp()

When the entry was recorded, as a timezone-aware UTC datetime.

Other records

ContextChange dataclass

ContextChange(kind: ContextChangeKind, before_tokens: int, after_tokens: int, summary: str | None = None, kept: int = 0, cleared: tuple[str, ...] = (), messages: tuple[Message, ...] = (), restored_to: int | None = None, usage: Usage | None = None)

A record that the context changed. Shown by Reporter.on_context_change and kept in history.

before_tokens and after_tokens are facts recorded for display: they are never recomputed when a State is replayed from its history.

kind instance-attribute

kind: ContextChangeKind

"compact", "clear_tool_results", "import" (State(messages=...)) or "restore" (state.restore(snapshot)).

before_tokens instance-attribute

before_tokens: int

The estimated context size before the change (without the system prompt and tool overhead).

after_tokens instance-attribute

after_tokens: int

The estimated context size after the change.

summary class-attribute instance-attribute

summary: str | None = None

The summary text compact put in the context. None for other kinds.

kept class-attribute instance-attribute

kept: int = 0

compact: how many of the latest messages stay after the summary, namely the ones added while a model was writing the summary.

cleared class-attribute instance-attribute

cleared: tuple[str, ...] = ()

clear_tool_results: the ids of the tool calls whose results were blanked.

messages class-attribute instance-attribute

messages: tuple[Message, ...] = ()

import: the messages the State started with.

restored_to class-attribute instance-attribute

restored_to: int | None = None

restore: the history length the State went back to (len(snapshot.history)).

usage class-attribute instance-attribute

usage: Usage | None = None

compact done by a model: the usage of that request. None otherwise.

Exchange dataclass

Exchange(question: str, answer: Any)

The question and answer of ask/ask_human: the content of an ExchangeEntry. answer is None if no answer came (OutputError).

answer is JSON (a read-only FrozenDict/FrozenList when it is a dict or list), as the history records it: a dataclass or Pydantic model comes back as a dict. agent.ask still returns the real object to its caller.

question instance-attribute

question: str

The question that was asked.

answer instance-attribute

answer: Any

The answer, in JSON form.

AgentInfo dataclass

AgentInfo(*, name: str | None = None, model: str, system_sha256: str | None = None, tools: tuple[str, ...] = (), mcp_servers: tuple[str, ...] = ())

What an Agent was when it started a run, kept in history (RunStartEntry). Used to warn when a loaded State is resumed by a different Agent (ResumeWarning). JSON values only; no MCP connection is needed to build it.

name class-attribute instance-attribute

name: str | None = None

The Agent's name.

model instance-attribute

model: str

provider/name of the Agent's model.

system_sha256 class-attribute instance-attribute

system_sha256: str | None = None

The hash of the system prompt, or None without one. The text itself is not kept.

tools class-attribute instance-attribute

tools: tuple[str, ...] = ()

The names of the Agent's own tools.

mcp_servers class-attribute instance-attribute

mcp_servers: tuple[str, ...] = ()

The names of its MCP servers.

ModelEvent dataclass

ModelEvent(kind: str, message: str, data: Mapping[str, Any] = dict())

Something the Model went through while handling a request, such as falling back to another model or a retry. It is not part of the reply.

A Model reports it by calling on_event in respond or compact. The Agent records it in history (model_event) and shows it with Reporter.on_model_event.

kind instance-attribute

kind: str

A short type name, e.g. "fallback".

message instance-attribute

message: str

One line to show a person.

data class-attribute instance-attribute

data: Mapping[str, Any] = field(default_factory=dict)

Extra values the Model adds. A read-only dict (a FrozenDict).

ToolOutcome dataclass

ToolOutcome(kind: ToolOutcomeKind, error: BaseException | None = None, decided_by: str | None = None)

How a tool call ended, passed to Reporter.on_tool_end next to the result.

The result is written for the model and may change. Branch on kind instead of reading it.

kind instance-attribute

One of:

  • DONE: the tool returned, and the result is its output
  • ERROR: the tool returned an error result, which the model sees as an error (an MCP server's isError)
  • INPUT_ERROR: the tool was not called because the tool is unknown or the arguments failed validation
  • ABORTED: the tool raised
  • INTERRUPTED: closed by KeyboardInterrupt or CancelledError
  • DENIED: not run because a permission denied it, and the result is the reason
  • CANCELLED: not run because a permission stopped the turn (another call of the same turn was denied with stop=True)

A plain string such as "denied" given here becomes the matching ToolOutcomeKind.

error class-attribute instance-attribute

error: BaseException | None = None

The exception for ABORTED and INTERRUPTED, and the ToolError a permission raised for a DENIED call. Otherwise None.

decided_by class-attribute instance-attribute

decided_by: str | None = None

The repr() of the permission that decided a DENIED or CANCELLED outcome. None for other kinds, and when no permission decided.

ToolOutcomeKind

Bases: StrEnum

How a tool call ended (ToolOutcome.kind). A StrEnum, so outcome.kind == "denied" works too.

DONE class-attribute instance-attribute

DONE = 'done'

The tool returned, and the result is its output.

ERROR class-attribute instance-attribute

ERROR = 'error'

The tool returned an error result, which the model sees as an error (an MCP server's isError).

INPUT_ERROR class-attribute instance-attribute

INPUT_ERROR = 'input_error'

The tool was not called because the tool is unknown or the arguments failed validation.

ABORTED class-attribute instance-attribute

ABORTED = 'aborted'

The tool raised.

INTERRUPTED class-attribute instance-attribute

INTERRUPTED = 'interrupted'

Closed by KeyboardInterrupt or CancelledError.

DENIED class-attribute instance-attribute

DENIED = 'denied'

Not run because a permission denied it, and the result is the reason.

CANCELLED class-attribute instance-attribute

CANCELLED = 'cancelled'

Not run because a permission stopped the turn (another call of the same turn was denied with stop=True).

Why a loop stopped

The values of state.stopped. alpineagents.types.Stopped is their union.

StoppedByUntil dataclass

StoppedByUntil(name: str)

state.stopped when an until function of @loop returned true. str() gives stopped by waiting_for_user.

name instance-attribute

name: str

The function's __name__, e.g. "waiting_for_user" ("<lambda>" for a lambda).

StoppedByLimit dataclass

StoppedByLimit(turns: int)

state.stopped when the loop ran limit turns in this call. str() gives stopped at limit 30.

turns instance-attribute

turns: int

The loop's limit.

StoppedByFinish dataclass

StoppedByFinish(answer: Any = None)

state.stopped when state.finish() was called. str() gives stopped by finish.

answer class-attribute instance-attribute

answer: Any = None

The answer given to finish as JSON (a read-only FrozenDict/FrozenList when it is a dict or list), or None when finish() had no answer.

StoppedByPermission dataclass

StoppedByPermission(call: ToolCall, permission: str)

state.stopped when a permission denied a tool call with stop=True. The run ended normally and the State is not finished: add a user message and run again to continue. str() gives stopped by permission DecideByHuman().

call instance-attribute

call: ToolCall

The tool call that was denied.

permission instance-attribute

permission: str

The repr() of the permission that denied it.

Content blocks

The parts of a Message. ToolCall above is also one.

TextBlock dataclass

TextBlock(text: str)

A piece of text.

text instance-attribute

text: str

The text.

ToolResultBlock dataclass

ToolResultBlock(call_id: str, content: ToolResultContent, name: str = '', is_error: bool = False)

The result of one tool call. Goes in user messages only.

content is exactly what is sent to the model: a string (e.g. "(done)", "(input error: ...)", "(aborted: TimeoutError)", "(cleared: kept in history)", a denial reason), or a tuple of TextBlock and Image when the tool returned an image.

call_id instance-attribute

call_id: str

The id of the ToolCall this answers.

content instance-attribute

content: ToolResultContent

What is sent to the model: a string, or a tuple of TextBlock and Image.

name class-attribute instance-attribute

name: str = ''

The tool name.

is_error class-attribute instance-attribute

is_error: bool = False

True if the model is told this is an error result.

Image dataclass

Image(data: bytes, media_type: str | None = None)

An image the model sees: a tool result, or part of a user message.

Return it from a tool, alone or in a list with text: return Image.from_path("chart.png") or return ["Page loaded in 1.2s", Image(png_bytes)]. Send it to the model with Message.user("What is wrong in this chart?", Image.from_path("chart.png")).

data is the file's bytes. The adapters encode it (base64) when they send it, and a Store saves it the same way, so a run with many screenshots makes large files. PNG, JPEG, GIF and WebP are supported; the type is read from the bytes (or pass media_type=).

Both Anthropic and OpenAICompatible send images in user messages. In a tool result, Anthropic takes images as they are, while Chat Completions APIs take only text there, so OpenAICompatible sends them in a user message right after, marked as the call's result. Nothing checks that the model can read images: a server that cannot answers with its own error. agent.context_tokens(state) counts each image as 1,600 tokens, and state.clear_tool_results() clears images with the rest of old results. A compaction summary keeps only what the model wrote about them, while history and the store keep them.

Raises:

Type Description
TypeError

data is not bytes.

ValueError

The data is not a PNG, JPEG, GIF or WebP image, or media_type is not one of those.

data instance-attribute

data: bytes

The image file's bytes, not encoded.

media_type class-attribute instance-attribute

media_type: str | None = None

"image/png", "image/jpeg", "image/gif" or "image/webp". Read from data when not given.

base64 cached property

base64: str

data as base64 text, the form providers take.

from_path classmethod

from_path(path: str | PathLike[str]) -> Image

Reads an image file. The type is read from the file's bytes, not its name.

from_base64 classmethod

from_base64(data: str, media_type: str | None = None) -> Image

An image from base64 text, such as an MCP server's image content.

result_text

result_text(content: ToolResultContent) -> str

A tool result as one string for display: the string itself, or the text blocks joined by newlines with each image as (image/png, 34.2KB).

format_call

format_call(call: ToolCall) -> str

A tool call as one line of code, such as read_file(path="main.py"): the name, then each argument as key=value with the value as JSON (repr for a value JSON cannot show).

Terminal uses it for its tool lines and DecideByHuman for its question (Run write_file(path="a.md", content="x")?). Use it to show a call to the person, for example in a permission or a Reporter.

Parameters:

Name Type Description Default
call ToolCall

The tool call.

required

Returns:

Type Description
str

The one-line form.

Example
format_call(ToolCall("read_file", {"path": "main.py"}, "call_1"))  # 'read_file(path="main.py")'

RawBlock dataclass

RawBlock(provider: str, data: Mapping[str, Any])

A provider-specific block (thinking blocks, redacted_thinking, server tools, etc.).

data is kept exactly as received and sent back as is to the same provider. Never modified: it is a read-only dict (a FrozenDict, frozen at every level).

provider instance-attribute

provider: str

The provider id (Model.provider) the block came from and is sent back to.

data instance-attribute

data: Mapping[str, Any]

The block as the provider gave it. A read-only dict.