steer_tui/tui/widgets/formatters/
external.rs

1use 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 textwrap;
10
11/// Fallback formatter for MCP/external tools (names starting with "mcp__")
12/// Displays parameters and payload/error in a generic way.
13pub struct ExternalFormatter;
14
15impl ToolFormatter for ExternalFormatter {
16    fn compact(
17        &self,
18        params: &Value,
19        result: &Option<ToolResult>,
20        _wrap_width: usize,
21        theme: &Theme,
22    ) -> Vec<Line<'static>> {
23        // Show a one-liner with param preview and short payload/error summary.
24        let mut spans = Vec::new();
25
26        // Param preview
27        let preview = json_preview(params, 30);
28        spans.push(Span::styled(preview.to_string(), theme.dim_text()));
29
30        if let Some(result) = result {
31            match result {
32                ToolResult::External(ext) => {
33                    let payload_preview = truncate_middle(&ext.payload, 40);
34                    spans.push(Span::raw(" → "));
35                    spans.push(Span::styled(payload_preview, Style::default()));
36                }
37                ToolResult::Error(err) => {
38                    spans.push(Span::raw(" ✗ "));
39                    spans.push(Span::styled(err.to_string(), theme.error_text()));
40                }
41                _ => {}
42            }
43        }
44
45        vec![Line::from(spans)]
46    }
47
48    fn detailed(
49        &self,
50        params: &Value,
51        result: &Option<ToolResult>,
52        wrap_width: usize,
53        theme: &Theme,
54    ) -> Vec<Line<'static>> {
55        let mut lines = Vec::new();
56
57        // Parameters block
58        lines.push(Line::from(Span::styled("Parameters:", theme.text())));
59
60        let pretty_params = serde_json::to_string_pretty(params).unwrap_or_default();
61        for line in wrap_lines(pretty_params.lines(), wrap_width) {
62            lines.push(Line::from(Span::styled(line, theme.dim_text())));
63        }
64
65        // Result block
66        if let Some(result) = result {
67            lines.push(separator_line(wrap_width, theme.dim_text()));
68            match result {
69                ToolResult::External(ext) => {
70                    // Attempt to pretty-print JSON payload with 2-space indent
71                    if let Ok(json_val) = serde_json::from_str::<serde_json::Value>(&ext.payload) {
72                        if let Ok(pretty) = serde_json::to_string_pretty(&json_val) {
73                            for ln in wrap_lines(pretty.lines(), wrap_width) {
74                                lines.push(Line::from(Span::raw(ln)));
75                            }
76                        } else {
77                            // Fallback to raw text if serialization fails
78                            for wrapped in textwrap::wrap(&ext.payload, wrap_width) {
79                                lines.push(Line::from(Span::raw(wrapped.to_string())));
80                            }
81                        }
82                    } else {
83                        // Non-JSON payload – render raw text
84                        for wrapped in textwrap::wrap(&ext.payload, wrap_width) {
85                            lines.push(Line::from(Span::raw(wrapped.to_string())));
86                        }
87                    }
88                }
89                ToolResult::Error(err) => {
90                    for wrapped in textwrap::wrap(&err.to_string(), wrap_width) {
91                        lines.push(Line::from(Span::styled(
92                            wrapped.to_string(),
93                            theme.error_text(),
94                        )));
95                    }
96                }
97                _ => {}
98            }
99        }
100
101        lines
102    }
103}