chat: route_entry returns Vec for multi-tool-call entries

An assistant entry can have text + multiple tool calls. route_entry
now returns Vec<(PaneTarget, String, Marker)> — tool calls go to
tools pane, text goes to conversation, all from the same entry.

Pop phase iterates the vec in reverse to pop correct number of
pane items per entry.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2026-04-05 19:43:48 -04:00
parent 222b2cbeb2
commit 65d23692fb

View file

@ -66,39 +66,50 @@ impl InteractScreen {
}
}
/// Route an agent entry to the appropriate pane.
/// Returns None for entries that shouldn't be displayed (memory, system).
fn route_entry(&mut self, entry: &crate::agent::context::ConversationEntry) -> Option<&mut PaneState> {
/// Route an agent entry to pane items.
/// Returns empty vec for entries that shouldn't be displayed.
fn route_entry(entry: &crate::agent::context::ConversationEntry) -> Vec<(PaneTarget, String, Marker)> {
use crate::agent::api::types::Role;
use crate::agent::context::ConversationEntry;
if let ConversationEntry::Memory { .. } = entry {
return None;
return vec![];
}
let msg = entry.message();
let text = msg.content_text().to_string();
match msg.role {
if text.is_empty() { return None; }
if text.starts_with("<system-reminder>") { return None; }
if text.starts_with("<system-reminder>") {
return vec![];
}
Role::User => Some(&mut self.conversation),
match msg.role {
Role::User => {
if text.is_empty() { return vec![]; }
vec![(PaneTarget::Conversation, text, Marker::User)]
}
Role::Assistant => {
let mut items = Vec::new();
// Tool calls → tools pane
if let Some(ref calls) = msg.tool_calls {
for call in calls {
let line = format!("[{}] {}",
call.function.name,
call.function.arguments.chars().take(80).collect::<String>());
// TODO: return multiple targets — for now just return first tool call
return Some((PaneTarget::Tools, line, Marker::None));
items.push((PaneTarget::Tools, line, Marker::None));
}
}
Some((PaneTarget::ConversationAssistant, text, Marker::Assistant))
// Text content → conversation
if !text.is_empty() {
items.push((PaneTarget::ConversationAssistant, text, Marker::Assistant));
}
items
}
Role::Tool => Some(&mut self.tools),
Role::System => None,
Role::Tool => {
if text.is_empty() { return vec![]; }
vec![(PaneTarget::ToolResult, text, Marker::None)]
}
Role::System => vec![],
}
}
@ -122,7 +133,7 @@ impl InteractScreen {
break;
}
let popped = self.last_entries.pop().unwrap();
if let Some((target, _, _)) = Self::route_entry(&popped) {
for (target, _, _) in Self::route_entry(&popped) {
match target {
PaneTarget::Conversation | PaneTarget::ConversationAssistant
=> self.conversation.pop_line(),
@ -136,17 +147,14 @@ impl InteractScreen {
// Phase 2: push new entries
let start = self.last_entries.len();
for entry in entries.iter().skip(start) {
if let Some((target, text, marker)) = Self::route_entry(entry) {
for (target, text, marker) in Self::route_entry(entry) {
match target {
PaneTarget::Conversation => {
self.conversation.push_line_with_marker(text, Color::Cyan, marker);
}
PaneTarget::ConversationAssistant => {
self.conversation.push_line_with_marker(text, Color::Reset, marker);
}
PaneTarget::Tools => {
self.tools.push_line(text, Color::Yellow);
}
PaneTarget::Conversation =>
self.conversation.push_line_with_marker(text, Color::Cyan, marker),
PaneTarget::ConversationAssistant =>
self.conversation.push_line_with_marker(text, Color::Reset, marker),
PaneTarget::Tools =>
self.tools.push_line(text, Color::Yellow),
PaneTarget::ToolResult => {
for line in text.lines().take(20) {
self.tools.push_line(format!(" {}", line), Color::DarkGray);