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
| Category | Messages |
|---|---|
| Content | TextChunk, Display |
| Tool Status | ToolExecuting, ToolCompleted |
| Lifecycle | Complete |
| Monitoring | TokenUpdate |
| Errors | Error |
| System | System |
| Control | CommandComplete |
| User Interaction | UserInteractionRequired, 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,
}
| Field | Description |
|---|---|
session_id | Session receiving the text |
turn_id | Assistant turn identifier |
text | Text content to display |
input_tokens | Input token count (may be 0 during streaming) |
output_tokens | Output 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,
}
| Field | Description |
|---|---|
session_id | Session for this message |
turn_id | Turn identifier if applicable |
message | Text 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,
}
| Field | Description |
|---|---|
session_id | Session running the tool |
turn_id | Turn identifier |
tool_use_id | Unique tool use ID for tracking |
display_name | UI-friendly tool name (e.g., “Web Search”) |
display_title | Dynamic 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>,
}
| Field | Description |
|---|---|
session_id | Session that ran the tool |
turn_id | Turn identifier |
tool_use_id | ID matching the ToolExecuting message |
status | Success, Error, or Timeout |
error | Error 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>,
}
| Field | Description |
|---|---|
session_id | Session that completed |
turn_id | Turn identifier |
input_tokens | Total input tokens |
output_tokens | Total output tokens |
stop_reason | Why the response ended |
Stop Reasons:
| Value | Meaning | Next Action |
|---|---|---|
"end_turn" | Natural completion | Ready for user input |
"tool_use" | Waiting for tools | Tools will execute, then continue |
"max_tokens" | Token limit reached | May 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,
}
| Field | Description |
|---|---|
session_id | Session being tracked |
turn_id | Turn identifier (often None) |
input_tokens | Current input token count |
output_tokens | Current output token count |
context_limit | Maximum 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,
}
| Field | Description |
|---|---|
session_id | Session where error occurred |
turn_id | Turn identifier if applicable |
error | Human-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,
}
| Field | Description |
|---|---|
session_id | Session for context |
message | System 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>,
}
| Field | Description |
|---|---|
session_id | Session that processed command |
command | Which command completed |
success | Whether it succeeded |
message | Optional 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>,
}
| Field | Description |
|---|---|
session_id | Session requesting input |
tool_use_id | ID of the blocked tool |
request | Questions to ask the user |
turn_id | Turn identifier |
The TUI should:
- Display the QuestionPanel
- Collect user responses
- 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>,
}
| Field | Description |
|---|---|
session_id | Session requesting permission |
tool_use_id | ID of the blocked tool |
request | Permission details |
turn_id | Turn identifier |
The TUI should:
- Display the PermissionPanel
- Collect user decision (allow/deny)
- 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
- Controller Events - The source events and conversion logic
- Message Handling - The controller’s message processing
