Skip to main content

car_browser/perception/
formatting.rs

1//! UiMap formatting for LLM prompt consumption.
2//!
3//! Provides configurable formatters that serialize UiMap data into compact
4//! text that fits within token budgets.
5
6use super::ui_map::UiMap;
7
8/// Configurable UiMap formatter.
9pub struct UiMapFormatter {
10    /// Maximum elements to include.
11    pub max_elements: usize,
12    /// Whether to include bounds coordinates.
13    pub include_bounds: bool,
14    /// Whether to include state flags.
15    pub include_states: bool,
16    /// Whether to only show interactive elements.
17    pub interactive_only: bool,
18    /// Maximum name length before truncation.
19    pub max_name_length: usize,
20}
21
22impl UiMapFormatter {
23    /// Default formatter.
24    pub fn new() -> Self {
25        Self {
26            max_elements: 100,
27            include_bounds: true,
28            include_states: true,
29            interactive_only: false,
30            max_name_length: 50,
31        }
32    }
33
34    /// Compact formatter for token-constrained contexts.
35    pub fn compact() -> Self {
36        Self {
37            max_elements: 40,
38            include_bounds: true,
39            include_states: true,
40            interactive_only: true,
41            max_name_length: 30,
42        }
43    }
44
45    /// Format a UiMap to text.
46    pub fn format(&self, ui_map: &UiMap) -> String {
47        // Delegate to UiMap's format_compact for now — more modes can be added
48        // based on formatter configuration
49        if self.interactive_only && ui_map.elements.len() > self.max_elements {
50            let mut output = String::new();
51            let elements = ui_map.interactive_elements();
52            for element in elements.iter().take(self.max_elements) {
53                use std::fmt::Write;
54                let role_str = element.role.to_hash_string();
55                let name_str = element
56                    .name
57                    .as_deref()
58                    .map(|n| {
59                        if n.len() > self.max_name_length {
60                            let truncated: String =
61                                n.chars().take(self.max_name_length - 3).collect();
62                            format!(" \"{}...\"", truncated)
63                        } else {
64                            format!(" \"{}\"", n)
65                        }
66                    })
67                    .unwrap_or_default();
68                let _ = writeln!(output, "[{}] {}{}", element.id, role_str, name_str);
69            }
70            output
71        } else {
72            ui_map.format_compact()
73        }
74    }
75}
76
77impl Default for UiMapFormatter {
78    fn default() -> Self {
79        Self::new()
80    }
81}