rho-coding-agent 0.28.0

A lightweight agent harness inspired by Pi
Documentation
use std::collections::VecDeque;

use crate::{
    model::{ContentBlock, Message},
    tool::ToolDisplayStyle,
};

use super::{Entry, ToolEntry, ToolEntryState};

pub(super) fn transcript_entries_from_messages(messages: &[Message]) -> Vec<Entry> {
    let mut entries = Vec::new();
    let mut pending_tool_names = VecDeque::new();
    for message in messages {
        match message {
            Message::System(_) => {}
            Message::User(blocks) => {
                let text = super::render_message_blocks(blocks);
                if !text.is_empty() {
                    entries.push(Entry::User(text));
                }
            }
            Message::Assistant(blocks) => {
                let text = super::text_blocks(blocks);
                if !text.is_empty() {
                    entries.push(Entry::Assistant(text));
                }
                pending_tool_names.extend(blocks.iter().filter_map(|block| match block {
                    ContentBlock::ToolCall(call) => Some(call.name.clone()),
                    ContentBlock::Text(_) | ContentBlock::Image(_) => None,
                }));
            }
            Message::AbortedAssistant(message) => {
                let text = super::text_blocks(&message.content);
                if !text.is_empty() {
                    entries.push(Entry::Assistant(text));
                }
                if let Some(tool_call) = message.tool_calls.last() {
                    let mut display_lines =
                        vec![tool_call.name.clone().unwrap_or_else(|| "tool call".into())];
                    if !tool_call.arguments.is_empty() {
                        display_lines.push(tool_call.arguments.clone());
                    }
                    entries.push(Entry::Tool(ToolEntry {
                        state: ToolEntryState::Finished {
                            ok: false,
                            display_style: ToolDisplayStyle::default_tool(),
                        },
                        display_lines,
                        expanded: false,
                    }));
                }
                entries.push(Entry::Notice("model interrupted".into()));
            }
            Message::ToolResult(result) => {
                let name = pending_tool_names
                    .pop_front()
                    .unwrap_or_else(|| "tool".into());
                let display_style = ToolDisplayStyle::for_tool_name(&name);
                let mut display_lines = vec![name];
                if !result.content.trim().is_empty() {
                    display_lines.push(result.content.clone());
                }
                entries.push(Entry::Tool(ToolEntry {
                    state: ToolEntryState::Finished {
                        ok: result.ok,
                        display_style,
                    },
                    display_lines,
                    expanded: false,
                }));
            }
        }
    }
    entries
}

#[cfg(test)]
#[path = "message_history_tests.rs"]
mod tests;