UiMessage Types

UiMessage is the enum used to communicate from the controller to the TUI layer. These messages are designed for UI consumption, with fields optimized for display rather than internal processing. This page documents all message variants and the channel infrastructure.

Overview

UiMessage is the TUI-facing counterpart to ControllerEvent. While ControllerEvent represents internal controller state changes, UiMessage is structured for UI rendering:

// Controller emits events
let event = ControllerEvent::TextChunk { session_id, text, turn_id };

// Conversion to UI message
let msg = convert_controller_event_to_ui_message(event);

// TUI receives and handles
match msg {
    UiMessage::TextChunk { text, .. } => chat_view.append_text(&text),
    // ...
}

Message Categories

CategoryMessages
ContentTextChunk, Display
Tool StatusToolExecuting, ToolCompleted
LifecycleComplete
MonitoringTokenUpdate
ErrorsError
SystemSystem
ControlCommandComplete
User InteractionUserInteractionRequired, PermissionRequired

Content Messages

TextChunk

Streamed text content from the LLM:

UiMessage::TextChunk {
    session_id: i64,
    turn_id: Option<TurnId>,
    text: String,
    input_tokens: i64,
    output_tokens: i64,
}
FieldDescription
session_idSession receiving the text
turn_idAssistant turn identifier
textText content to display
input_tokensInput token count (may be 0 during streaming)
output_tokensOutput token count (may be 0 during streaming)

The TUI appends each chunk to the chat view. Token counts may be zero during streaming and are updated via TokenUpdate.

Display

General display messages for status and information:

UiMessage::Display {
    session_id: i64,
    turn_id: Option<TurnId>,
    message: String,
}
FieldDescription
session_idSession for this message
turn_idTurn identifier if applicable
messageText to display

Used for informational messages like “Executing tool: web_search”.

Tool Status Messages

ToolExecuting

Indicates a tool has started execution:

UiMessage::ToolExecuting {
    session_id: i64,
    turn_id: Option<TurnId>,
    tool_use_id: String,
    display_name: String,
    display_title: String,
}
FieldDescription
session_idSession running the tool
turn_idTurn identifier
tool_use_idUnique tool use ID for tracking
display_nameUI-friendly tool name (e.g., “Web Search”)
display_titleDynamic title from input (e.g., “Seattle weather”)

The TUI can show a spinner or progress indicator using this message.

ToolCompleted

Indicates a tool has finished execution:

UiMessage::ToolCompleted {
    session_id: i64,
    turn_id: Option<TurnId>,
    tool_use_id: String,
    status: ToolResultStatus,
    error: Option<String>,
}
FieldDescription
session_idSession that ran the tool
turn_idTurn identifier
tool_use_idID matching the ToolExecuting message
statusSuccess, Error, or Timeout
errorError message if status is not Success

ToolResultStatus:

pub enum ToolResultStatus {
    Success,
    Error,
    Timeout,
}

The TUI updates the tool status display to show completion or failure.

Lifecycle Messages

Complete

Response from the LLM is complete:

UiMessage::Complete {
    session_id: i64,
    turn_id: Option<TurnId>,
    input_tokens: i64,
    output_tokens: i64,
    stop_reason: Option<String>,
}
FieldDescription
session_idSession that completed
turn_idTurn identifier
input_tokensTotal input tokens
output_tokensTotal output tokens
stop_reasonWhy the response ended

Stop Reasons:

ValueMeaningNext Action
"end_turn"Natural completionReady for user input
"tool_use"Waiting for toolsTools will execute, then continue
"max_tokens"Token limit reachedMay be truncated

When stop_reason is "tool_use", the TUI knows more activity will follow.

Monitoring Messages

TokenUpdate

Real-time token usage information:

UiMessage::TokenUpdate {
    session_id: i64,
    turn_id: Option<TurnId>,
    input_tokens: i64,
    output_tokens: i64,
    context_limit: i32,
}
FieldDescription
session_idSession being tracked
turn_idTurn identifier (often None)
input_tokensCurrent input token count
output_tokensCurrent output token count
context_limitMaximum context window size

Used to update status bar displays with context usage percentage.

Error Messages

Error

Error from the controller:

UiMessage::Error {
    session_id: i64,
    turn_id: Option<TurnId>,
    error: String,
}
FieldDescription
session_idSession where error occurred
turn_idTurn identifier if applicable
errorHuman-readable error message

The TUI should display the error prominently, typically in red text.

System Messages

System

Local TUI system messages:

UiMessage::System {
    session_id: i64,
    message: String,
}
FieldDescription
session_idSession for context
messageSystem message text

Used for TUI-local messages that do not originate from the controller (e.g., silent StreamStart events are converted to empty System messages).

Control Messages

CommandComplete

Control command has completed:

UiMessage::CommandComplete {
    session_id: i64,
    command: ControlCmd,
    success: bool,
    message: Option<String>,
}
FieldDescription
session_idSession that processed command
commandWhich command completed
successWhether it succeeded
messageOptional status message

ControlCmd:

pub enum ControlCmd {
    Interrupt,
    Shutdown,
    Clear,
    Compact,
}

User Interaction Messages

UserInteractionRequired

Tool needs user input:

UiMessage::UserInteractionRequired {
    session_id: i64,
    tool_use_id: String,
    request: AskUserQuestionsRequest,
    turn_id: Option<TurnId>,
}
FieldDescription
session_idSession requesting input
tool_use_idID of the blocked tool
requestQuestions to ask the user
turn_idTurn identifier

The TUI should:

  1. Display the QuestionPanel
  2. Collect user responses
  3. Submit via UserInteractionRegistry.submit_response()

PermissionRequired

Tool needs user permission:

UiMessage::PermissionRequired {
    session_id: i64,
    tool_use_id: String,
    request: PermissionRequest,
    turn_id: Option<TurnId>,
}
FieldDescription
session_idSession requesting permission
tool_use_idID of the blocked tool
requestPermission details
turn_idTurn identifier

The TUI should:

  1. Display the PermissionPanel
  2. Collect user decision (allow/deny)
  3. Submit via PermissionRegistry.submit_response()

Channel Infrastructure

Type Aliases

pub mod channels {
    use tokio::sync::mpsc;

    pub type ToControllerTx = mpsc::Sender<ControllerInputPayload>;
    pub type ToControllerRx = mpsc::Receiver<ControllerInputPayload>;
    pub type FromControllerTx = mpsc::Sender<UiMessage>;
    pub type FromControllerRx = mpsc::Receiver<UiMessage>;
}

Default Buffer Size

pub const DEFAULT_CHANNEL_SIZE: usize = 100;

Channel Creation

pub fn create_channels() -> (ToControllerTx, ToControllerRx, FromControllerTx, FromControllerRx) {
    let (to_ctrl_tx, to_ctrl_rx) = mpsc::channel(DEFAULT_CHANNEL_SIZE);
    let (from_ctrl_tx, from_ctrl_rx) = mpsc::channel(DEFAULT_CHANNEL_SIZE);
    (to_ctrl_tx, to_ctrl_rx, from_ctrl_tx, from_ctrl_rx)
}

Handling Messages

The App receives messages in its event loop:

impl App {
    fn handle_controller_message(&mut self, msg: UiMessage) {
        match msg {
            UiMessage::TextChunk { session_id, text, .. } => {
                if session_id == self.current_session {
                    self.chat_view.append_text(&text);
                }
            }
            UiMessage::ToolExecuting { tool_use_id, display_name, display_title, .. } => {
                self.chat_view.add_tool_executing(&tool_use_id, &display_name, &display_title);
            }
            UiMessage::ToolCompleted { tool_use_id, status, error, .. } => {
                self.chat_view.update_tool_status(&tool_use_id, status, error);
            }
            UiMessage::Complete { stop_reason, .. } => {
                if stop_reason.as_deref() != Some("tool_use") {
                    self.set_input_ready();
                }
            }
            UiMessage::Error { error, .. } => {
                self.chat_view.add_error(&error);
            }
            UiMessage::TokenUpdate { input_tokens, output_tokens, context_limit, .. } => {
                self.status_bar.update_tokens(input_tokens, output_tokens, context_limit);
            }
            UiMessage::UserInteractionRequired { request, .. } => {
                self.show_question_panel(request);
            }
            UiMessage::PermissionRequired { request, .. } => {
                self.show_permission_panel(request);
            }
            // ... other variants
        }
    }
}

Session Filtering

Messages include session_id for filtering in multi-session scenarios:

if msg.session_id() == self.current_session_id {
    self.process_message(msg);
}

This allows the TUI to ignore messages from background sessions.

Next Steps