steer_tui/tui/widgets/formatters/
dispatch_agent.rs1use super::{ToolFormatter, helpers::*};
2use crate::tui::theme::Theme;
3use ratatui::{
4 style::Style,
5 text::{Line, Span},
6};
7use serde_json::Value;
8use steer_core::app::conversation::ToolResult;
9use steer_core::tools::dispatch_agent::DispatchAgentParams;
10
11pub struct DispatchAgentFormatter;
12
13impl ToolFormatter for DispatchAgentFormatter {
14 fn compact(
15 &self,
16 params: &Value,
17 result: &Option<ToolResult>,
18 _wrap_width: usize,
19 theme: &Theme,
20 ) -> Vec<Line<'static>> {
21 let mut lines = Vec::new();
22
23 let Ok(params) = serde_json::from_value::<DispatchAgentParams>(params.clone()) else {
24 return vec![Line::from(Span::styled(
25 "Invalid agent params",
26 theme.error_text(),
27 ))];
28 };
29
30 let preview = if params.prompt.len() > 60 {
31 format!("{}...", ¶ms.prompt[..57])
32 } else {
33 params.prompt.clone()
34 };
35
36 let info = match result {
37 Some(ToolResult::Agent(agent_result)) => {
38 let line_count = agent_result.content.lines().count();
39 format!("{line_count} lines")
40 }
41 Some(ToolResult::Error(_)) => "failed".to_string(),
42 Some(_) => "unexpected result type".to_string(),
43 None => "running...".to_string(),
44 };
45
46 lines.push(Line::from(vec![
47 Span::styled(format!("task='{preview}' "), Style::default()),
48 Span::styled(format!("({info})"), theme.subtle_text()),
49 ]));
50
51 lines
52 }
53
54 fn detailed(
55 &self,
56 params: &Value,
57 result: &Option<ToolResult>,
58 wrap_width: usize,
59 theme: &Theme,
60 ) -> Vec<Line<'static>> {
61 let mut lines = Vec::new();
62
63 let Ok(params) = serde_json::from_value::<DispatchAgentParams>(params.clone()) else {
64 return vec![Line::from(Span::styled(
65 "Invalid agent params",
66 theme.error_text(),
67 ))];
68 };
69
70 lines.push(Line::from(Span::styled("Agent Task:", theme.text())));
71 for line in params.prompt.lines() {
72 for wrapped_line in textwrap::wrap(line, wrap_width) {
73 lines.push(Line::from(Span::styled(
74 wrapped_line.to_string(),
75 Style::default(),
76 )));
77 }
78 }
79
80 if let Some(result) = result {
82 match result {
83 ToolResult::Agent(agent_result) => {
84 if !agent_result.content.trim().is_empty() {
85 lines.push(separator_line(wrap_width, theme.dim_text()));
86
87 const MAX_OUTPUT_LINES: usize = 30;
88 let (output_lines, truncated) =
89 truncate_lines(&agent_result.content, MAX_OUTPUT_LINES);
90
91 for line in output_lines {
92 for wrapped in textwrap::wrap(line, wrap_width) {
93 lines.push(Line::from(Span::raw(wrapped.to_string())));
94 }
95 }
96
97 if truncated {
98 lines.push(Line::from(Span::styled(
99 format!(
100 "... ({} more lines)",
101 agent_result.content.lines().count() - MAX_OUTPUT_LINES
102 ),
103 theme.subtle_text(),
104 )));
105 }
106 }
107 }
108 ToolResult::Error(error) => {
109 lines.push(separator_line(wrap_width, theme.dim_text()));
110 lines.push(Line::from(Span::styled(
111 error.to_string(),
112 theme.error_text(),
113 )));
114 }
115 _ => {
116 lines.push(Line::from(Span::styled(
117 "Unexpected result type",
118 theme.error_text(),
119 )));
120 }
121 }
122 }
123
124 lines
125 }
126}