vtcode_config/
root.rs

1use serde::{Deserialize, Serialize};
2
3use crate::status_line::StatusLineConfig;
4
5#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
6#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
7#[serde(rename_all = "snake_case")]
8#[derive(Default)]
9pub enum ToolOutputMode {
10    #[default]
11    Compact,
12    Full,
13}
14
15#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
16#[derive(Debug, Clone, Deserialize, Serialize)]
17pub struct UiConfig {
18    #[serde(default = "default_tool_output_mode")]
19    pub tool_output_mode: ToolOutputMode,
20    #[serde(default = "default_tool_output_max_lines")]
21    pub tool_output_max_lines: usize,
22    #[serde(default = "default_tool_output_spool_bytes")]
23    pub tool_output_spool_bytes: usize,
24    #[serde(default)]
25    pub tool_output_spool_dir: Option<String>,
26    #[serde(default = "default_allow_tool_ansi")]
27    pub allow_tool_ansi: bool,
28    #[serde(default = "default_inline_viewport_rows")]
29    pub inline_viewport_rows: u16,
30    #[serde(default)]
31    pub status_line: StatusLineConfig,
32    #[serde(default)]
33    pub keyboard_protocol: KeyboardProtocolConfig,
34}
35
36impl Default for UiConfig {
37    fn default() -> Self {
38        Self {
39            tool_output_mode: default_tool_output_mode(),
40            tool_output_max_lines: default_tool_output_max_lines(),
41            tool_output_spool_bytes: default_tool_output_spool_bytes(),
42            tool_output_spool_dir: None,
43            allow_tool_ansi: default_allow_tool_ansi(),
44            inline_viewport_rows: default_inline_viewport_rows(),
45            status_line: StatusLineConfig::default(),
46            keyboard_protocol: KeyboardProtocolConfig::default(),
47        }
48    }
49}
50
51/// PTY configuration
52#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
53#[derive(Debug, Clone, Deserialize, Serialize)]
54pub struct PtyConfig {
55    /// Enable PTY functionality
56    #[serde(default = "default_pty_enabled")]
57    pub enabled: bool,
58
59    /// Default terminal rows
60    #[serde(default = "default_pty_rows")]
61    pub default_rows: u16,
62
63    /// Default terminal columns
64    #[serde(default = "default_pty_cols")]
65    pub default_cols: u16,
66
67    /// Maximum number of concurrent PTY sessions
68    #[serde(default = "default_max_pty_sessions")]
69    pub max_sessions: usize,
70
71    /// Command timeout in seconds
72    #[serde(default = "default_pty_timeout")]
73    pub command_timeout_seconds: u64,
74
75    /// Number of PTY stdout lines to display in chat output
76    #[serde(default = "default_stdout_tail_lines")]
77    pub stdout_tail_lines: usize,
78
79    /// Maximum number of scrollback lines retained per PTY session
80    #[serde(default = "default_scrollback_lines")]
81    pub scrollback_lines: usize,
82
83    /// Maximum bytes of output to retain per PTY session (prevents memory explosion)
84    #[serde(default = "default_max_scrollback_bytes")]
85    pub max_scrollback_bytes: usize,
86
87    /// Threshold (KB) at which to auto-spool large outputs to disk
88    #[serde(default = "default_large_output_threshold_kb")]
89    pub large_output_threshold_kb: usize,
90
91    /// Preferred shell program for PTY sessions (falls back to environment when unset)
92    #[serde(default)]
93    pub preferred_shell: Option<String>,
94}
95
96impl Default for PtyConfig {
97    fn default() -> Self {
98        Self {
99            enabled: default_pty_enabled(),
100            default_rows: default_pty_rows(),
101            default_cols: default_pty_cols(),
102            max_sessions: default_max_pty_sessions(),
103            command_timeout_seconds: default_pty_timeout(),
104            stdout_tail_lines: default_stdout_tail_lines(),
105            scrollback_lines: default_scrollback_lines(),
106            max_scrollback_bytes: default_max_scrollback_bytes(),
107            large_output_threshold_kb: default_large_output_threshold_kb(),
108            preferred_shell: None,
109        }
110    }
111}
112
113fn default_pty_enabled() -> bool {
114    true
115}
116
117fn default_pty_rows() -> u16 {
118    24
119}
120
121fn default_pty_cols() -> u16 {
122    80
123}
124
125fn default_max_pty_sessions() -> usize {
126    10
127}
128
129fn default_pty_timeout() -> u64 {
130    300
131}
132
133fn default_stdout_tail_lines() -> usize {
134    crate::constants::defaults::DEFAULT_PTY_STDOUT_TAIL_LINES
135}
136
137fn default_scrollback_lines() -> usize {
138    crate::constants::defaults::DEFAULT_PTY_SCROLLBACK_LINES
139}
140
141fn default_max_scrollback_bytes() -> usize {
142    // Reduced from 50MB to 25MB for memory-constrained development environments
143    // Can be overridden in vtcode.toml with: pty.max_scrollback_bytes = 52428800
144    25_000_000 // 25MB max to prevent memory explosion
145}
146
147fn default_large_output_threshold_kb() -> usize {
148    5_000 // 5MB threshold for auto-spooling
149}
150
151fn default_tool_output_mode() -> ToolOutputMode {
152    ToolOutputMode::Compact
153}
154
155fn default_tool_output_max_lines() -> usize {
156    600
157}
158
159fn default_tool_output_spool_bytes() -> usize {
160    200_000
161}
162
163fn default_allow_tool_ansi() -> bool {
164    false
165}
166
167fn default_inline_viewport_rows() -> u16 {
168    crate::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS
169}
170
171/// Kitty keyboard protocol configuration
172/// Reference: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
173#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
174#[derive(Debug, Clone, Deserialize, Serialize)]
175pub struct KeyboardProtocolConfig {
176    /// Enable keyboard protocol enhancements (master toggle)
177    #[serde(default = "default_keyboard_protocol_enabled")]
178    pub enabled: bool,
179
180    /// Preset mode: "default", "full", "minimal", "custom"
181    #[serde(default = "default_keyboard_protocol_mode")]
182    pub mode: String,
183
184    /// Individual flag controls (used when mode = "custom")
185    /// Resolve Esc key ambiguity (recommended)
186    #[serde(default = "default_disambiguate_escape_codes")]
187    pub disambiguate_escape_codes: bool,
188
189    /// Report press/release/repeat events
190    #[serde(default = "default_report_event_types")]
191    pub report_event_types: bool,
192
193    /// Report alternate key layouts
194    #[serde(default = "default_report_alternate_keys")]
195    pub report_alternate_keys: bool,
196
197    /// Report modifier-only keys (Shift, Ctrl, Alt alone)
198    #[serde(default = "default_report_all_keys")]
199    pub report_all_keys: bool,
200}
201
202impl Default for KeyboardProtocolConfig {
203    fn default() -> Self {
204        Self {
205            enabled: default_keyboard_protocol_enabled(),
206            mode: default_keyboard_protocol_mode(),
207            disambiguate_escape_codes: default_disambiguate_escape_codes(),
208            report_event_types: default_report_event_types(),
209            report_alternate_keys: default_report_alternate_keys(),
210            report_all_keys: default_report_all_keys(),
211        }
212    }
213}
214
215impl KeyboardProtocolConfig {
216    pub fn validate(&self) -> anyhow::Result<()> {
217        match self.mode.as_str() {
218            "default" | "full" | "minimal" | "custom" => Ok(()),
219            _ => anyhow::bail!(
220                "Invalid keyboard protocol mode '{}'. Must be: default, full, minimal, or custom",
221                self.mode
222            ),
223        }
224    }
225}
226
227fn default_keyboard_protocol_enabled() -> bool {
228    std::env::var("VTCODE_KEYBOARD_PROTOCOL_ENABLED")
229        .ok()
230        .and_then(|v| v.parse().ok())
231        .unwrap_or(true)
232}
233
234fn default_keyboard_protocol_mode() -> String {
235    std::env::var("VTCODE_KEYBOARD_PROTOCOL_MODE").unwrap_or_else(|_| "default".to_string())
236}
237
238fn default_disambiguate_escape_codes() -> bool {
239    true
240}
241
242fn default_report_event_types() -> bool {
243    true
244}
245
246fn default_report_alternate_keys() -> bool {
247    true
248}
249
250fn default_report_all_keys() -> bool {
251    false
252}