Skip to main content

vtcode_commons/ui_protocol/
tool_summary.rs

1//! Data exchanged by compact per-call tool summaries.
2
3use crate::tool_types::CompactStr;
4
5/// Session-local key for a complete tool-output capture.
6///
7/// This is deliberately a UI protocol identifier. It is not persisted in
8/// `ThreadEvent` data and must not be treated as a tool-call identity outside
9/// the live terminal session.
10pub type ToolOutputId = u64;
11
12/// Presentation metadata for a compact command activity row.
13///
14/// The complete command output is sent through `RecordToolOutput` separately.
15/// Keeping this small metadata object independent means compact rendering can
16/// replace a row without truncating, reordering, or otherwise changing the
17/// captured stdout, stderr, PTY, or spool-backed transcript.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct CompactActivityMetadata {
20    /// Identifier shared by all rows in one contiguous successful-command group.
21    pub group_id: u64,
22    /// Number of successful commands represented by the row.
23    pub command_count: usize,
24    /// The command text for a single-command row. Grouped rows leave this empty.
25    pub command: Option<CompactStr>,
26    /// Number of complete output lines hidden behind the review affordance.
27    pub hidden_line_count: usize,
28    /// Optional status or artifact text that remains visible in the row.
29    pub suffix: Option<CompactStr>,
30    /// First complete capture represented by the row, used as the review focus.
31    pub review_anchor: Option<ToolOutputId>,
32    /// All complete captures represented by the row, in render order.
33    ///
34    /// `review_anchor` remains the first capture for compatibility with the
35    /// click protocol; this list lets the UI re-anchor every member of a
36    /// grouped row without guessing from transcript text.
37    pub review_anchors: Vec<ToolOutputId>,
38}
39
40impl CompactActivityMetadata {
41    /// Return the compact row text without the UI-only review affordance.
42    pub fn display_text(&self) -> String {
43        let mut text = if self.command_count > 1 {
44            format!("• Ran {} commands", self.command_count)
45        } else {
46            format!("• Ran {}", self.command.as_deref().unwrap_or("command"))
47        };
48
49        if self.command_count == 1 && self.hidden_line_count > 0 {
50            text.push_str(&format!(" · … +{} lines", self.hidden_line_count));
51        }
52        if let Some(suffix) = self.suffix.as_deref().filter(|suffix| !suffix.is_empty()) {
53            text.push_str(" · ");
54            text.push_str(suffix);
55        }
56        text
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::CompactActivityMetadata;
63
64    #[test]
65    fn compact_activity_display_includes_single_command_output_count() {
66        let activity = CompactActivityMetadata {
67            group_id: 1,
68            command_count: 1,
69            command: Some("cargo check".into()),
70            hidden_line_count: 3,
71            suffix: None,
72            review_anchor: Some(7),
73            review_anchors: vec![7],
74        };
75
76        assert_eq!(activity.display_text(), "• Ran cargo check · … +3 lines");
77    }
78
79    #[test]
80    fn compact_activity_display_collapses_grouped_commands() {
81        let activity = CompactActivityMetadata {
82            group_id: 2,
83            command_count: 4,
84            command: None,
85            hidden_line_count: 12,
86            suffix: Some("output retained".into()),
87            review_anchor: Some(9),
88            review_anchors: vec![9],
89        };
90
91        assert_eq!(activity.display_text(), "• Ran 4 commands · output retained");
92    }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct CompactToolSummaryLine {
97    pub kind: CompactToolSummaryLineKind,
98    pub text: String,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum CompactToolSummaryLineKind {
103    Info,
104    Detail,
105}