Omni.UI.Turn (Omni UI v0.1.0)

Copy Markdown View Source

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, or nil if no assistant response exists yet (e.g. turn just submitted).
  • status:complete, :streaming, or :error.
  • user_text — list of Omni.Content.Text blocks from the user message.
  • user_attachments — list of Omni.Content.Attachment blocks from the user message.
  • user_timestampDateTime from the user's message.
  • content — all assistant content blocks (Text, Thinking, ToolUse) accumulated across all assistant messages in the turn.
  • timestampDateTime from the last assistant message, nil while streaming.
  • tool_results%{tool_use_id => ToolResult} extracted from intermediate user messages.
  • error — error reason string when status == :error.
  • usage — cumulative Omni.Usage for 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

Types

t()

A UI-oriented view of a single conversation exchange. See module docs for fields.

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

t()

@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

all(tree)

@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.

get(tree, node_id)

@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.

get_text(turn, atom)

@spec get_text(t(), :user | :assistant) :: String.t()

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.

new(node_id, list, usage)

@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.

push_content(turn, content_block)

@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).

push_delta(turn, delta)

@spec push_delta(t(), String.t()) :: t()

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).

put_tool_result(turn, tool_result)

@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.

replace_content(turn, content_block)

@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.