A UI-oriented view of a conversation exchange.
Each turn collapses a sequence of tree nodes — a user prompt, any intermediate
tool-use rounds, and the final assistant response — into a single renderable
struct. all/1 walks the active path and chunks it into turns; get/2
returns a single turn by its starting node ID; new/3 builds a turn from
raw messages (used during streaming).
Fields
id— node ID of the user message that starts this turn.res_id— node ID of the first assistant message in this turn, ornilif no assistant response exists yet (e.g. turn just submitted).status—:complete,:streaming, or:error.user_text— list ofOmni.Content.Textblocks from the user message.user_attachments— list ofOmni.Content.Attachmentblocks from the user message.user_timestamp—DateTimefrom the user's message.content— all assistant content blocks (Text,Thinking,ToolUse) accumulated across all assistant messages in the turn.timestamp—DateTimefrom the last assistant message,nilwhile streaming.tool_results—%{tool_use_id => ToolResult}extracted from intermediate user messages.error— error reason string whenstatus == :error.usage— cumulativeOmni.Usagefor this turn.
Branching metadata
edits— sorted node IDs of all user messages that share the same parent as this turn's user message, including the active node (id). Length > 1 means the user edited their prompt. The active node is included so that UI components can compute position (e.g. "2/3") and prev/next navigation without needing to re-insert it.regens— sorted node IDs of all assistant messages that are children of this turn's user message, including the active node (res_id). Length > 1 means the user regenerated the response.
Summary
Functions
Converts a tree's active path into a list of turns.
Returns a single turn from the tree starting at the given node ID.
Returns the concatenated text content for the given role in a turn.
Builds a turn from a node ID, a list of messages, and cumulative usage.
Appends a content block to the turn's assistant content.
Appends a text delta to the last content block.
Stores a tool result, keyed by its tool_use_id.
Replaces a content block in the turn by matching its id.
Types
@type t() :: %Omni.UI.Turn{ content: [Omni.Message.content()], edits: [Omni.Session.Tree.node_id()], error: String.t() | nil, id: Omni.Session.Tree.node_id() | nil, regens: [Omni.Session.Tree.node_id()], res_id: Omni.Session.Tree.node_id() | nil, status: :complete | :streaming | :error, timestamp: DateTime.t() | nil, tool_results: %{required(String.t()) => Omni.Content.ToolResult.t()}, usage: Omni.Usage.t(), user_attachments: [Omni.Content.Attachment.t()], user_text: [Omni.Content.Text.t()], user_timestamp: DateTime.t() | nil }
A UI-oriented view of a single conversation exchange. See module docs for fields.
Functions
@spec all(Omni.Session.Tree.t()) :: [t()]
Converts a tree's active path into a list of turns.
Chunks the path at user-message boundaries (skipping tool-result user messages), then builds a turn from each chunk with edits and regens populated from the full tree structure.
@spec get(Omni.Session.Tree.t(), Omni.Session.Tree.node_id()) :: t() | nil
Returns a single turn from the tree starting at the given node ID.
Walks the active path forward from node_id, collecting nodes until the
next turn boundary (a non-tool-result user message), then builds a turn
with branching metadata from the full tree structure.
Returns nil if node_id is not on the active path.
Returns the concatenated text content for the given role in a turn.
Multiple text blocks are joined with double newlines. For :assistant,
non-text content blocks (e.g. Thinking, ToolUse) are filtered out.
@spec new(Omni.Session.Tree.node_id() | nil, [Omni.Message.t()], Omni.Usage.t()) :: t()
Builds a turn from a node ID, a list of messages, and cumulative usage.
The first message must be the user prompt. Subsequent messages are reduced
into the turn: assistant messages append to content, and user messages
containing tool results are collected into tool_results.
@spec push_content(t(), Omni.Message.content()) :: t()
Appends a content block to the turn's assistant content.
Called during streaming when the agent starts a new content block
(e.g. a new Text or Thinking block).
Appends a text delta to the last content block.
Called during streaming as text chunks arrive from the agent. Assumes the
last content block has a :text field (i.e. push_content/2 was called
first to start the block).
@spec put_tool_result(t(), Omni.Content.ToolResult.t()) :: t()
Stores a tool result, keyed by its tool_use_id.
Called during streaming when a tool execution completes. The result is
stored so the corresponding ToolUse content block can render it.
@spec replace_content(t(), Omni.Message.content()) :: t()
Replaces a content block in the turn by matching its id.
Called during streaming when a content block is finalised — e.g. a tool-use
block that started as a stub on :tool_use_start and is replaced with the
fully-formed struct on :tool_use_end. For blocks with an id field (like
ToolUse), the match is by id so parallel blocks don't clobber each other.
Falls back to replacing the last block for id-less content types.