1pub mod constants {
3 pub mod defaults;
4 pub mod ui;
5}
6
7pub mod loader;
9pub mod types;
11
12use anyhow::Result;
13use serde::{Deserialize, Serialize};
14use vtcode_commons::terminal_detection::is_ghostty_terminal;
15
16pub use types::{ReasoningEffortLevel, SystemPromptMode, ToolDocumentationMode, UiSurfacePreference, VerbosityLevel};
17
18#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
20#[serde(rename_all = "snake_case")]
21pub enum ToolOutputMode {
22 #[default]
24 Compact,
25 Full,
27}
28
29#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
31#[serde(rename_all = "snake_case")]
32pub enum UiDisplayMode {
33 Full,
35 #[default]
37 Minimal,
38 Focused,
40}
41
42#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
44#[serde(rename_all = "snake_case")]
45pub enum NotificationDeliveryMode {
46 Terminal,
48 #[default]
50 Hybrid,
51 Desktop,
53}
54
55#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
57#[serde(rename_all = "snake_case")]
58pub enum AgentClientProtocolZedWorkspaceTrustMode {
59 FullAuto,
61 #[default]
63 ToolsPolicy,
64}
65
66pub use vtcode_config::core::tools::ToolPolicy;
67
68#[derive(Debug, Clone, Deserialize, Serialize)]
70pub struct KeyboardProtocolConfig {
71 pub(crate) enabled: bool,
72 pub(crate) mode: String,
73 pub(crate) disambiguate_escape_codes: bool,
74 pub(crate) report_event_types: bool,
75 pub(crate) report_alternate_keys: bool,
76 pub(crate) report_all_keys: bool,
77}
78
79impl Default for KeyboardProtocolConfig {
80 fn default() -> Self {
81 Self {
82 enabled: true,
83 mode: "default".to_string(),
84 disambiguate_escape_codes: true,
85 report_event_types: true,
86 report_alternate_keys: true,
87 report_all_keys: false,
88 }
89 }
90}
91
92impl KeyboardProtocolConfig {
93 pub fn validate(&self) -> Result<()> {
95 match self.mode.as_str() {
96 "default" | "full" | "minimal" | "custom" => Ok(()),
97 _ => anyhow::bail!(
98 "Invalid keyboard protocol mode '{}'. Must be: default, full, minimal, or custom",
99 self.mode
100 ),
101 }
102 }
103}
104
105#[derive(Debug, Clone, Deserialize, Serialize)]
107pub struct UiNotificationsConfig {
108 pub enabled: bool,
109 pub delivery_mode: NotificationDeliveryMode,
110 pub suppress_when_focused: bool,
111 pub tool_failure: bool,
112 pub error: bool,
113 pub completion: bool,
114 pub hitl: bool,
115 pub tool_success: bool,
116}
117
118impl Default for UiNotificationsConfig {
119 fn default() -> Self {
120 Self {
121 enabled: true,
122 delivery_mode: NotificationDeliveryMode::Hybrid,
123 suppress_when_focused: true,
124 tool_failure: true,
125 error: true,
126 completion: true,
127 hitl: true,
128 tool_success: false,
129 }
130 }
131}
132
133#[derive(Debug, Clone, Deserialize, Serialize)]
135pub struct UiConfig {
136 pub tool_output_mode: ToolOutputMode,
137 pub allow_tool_ansi: bool,
138 pub inline_viewport_rows: u16,
139 pub keyboard_protocol: KeyboardProtocolConfig,
140 pub display_mode: UiDisplayMode,
141 pub show_sidebar: bool,
142 pub dim_completed_todos: bool,
143 pub message_block_spacing: bool,
144 pub show_turn_timer: bool,
145 pub notifications: UiNotificationsConfig,
146}
147
148impl Default for UiConfig {
149 fn default() -> Self {
150 Self {
151 tool_output_mode: ToolOutputMode::Compact,
152 allow_tool_ansi: false,
153 inline_viewport_rows: constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS,
154 keyboard_protocol: KeyboardProtocolConfig::default(),
155 display_mode: UiDisplayMode::Minimal,
156 show_sidebar: true,
157 dim_completed_todos: true,
158 message_block_spacing: false,
159 show_turn_timer: false,
160 notifications: UiNotificationsConfig::default(),
161 }
162 }
163}
164
165#[derive(Debug, Clone, Deserialize, Serialize, Default)]
167pub struct AgentCheckpointingConfig {
168 pub enabled: bool,
169}
170
171#[derive(Debug, Clone, Deserialize, Serialize, Default)]
173pub struct AgentSmallModelConfig {
174 pub enabled: bool,
175}
176
177#[derive(Debug, Clone, Deserialize, Serialize, Default)]
178pub struct AgentVibeCodingConfig {
179 pub enabled: bool,
180}
181
182#[derive(Debug, Clone, Deserialize, Serialize)]
184pub struct AgentConfig {
185 pub default_model: String,
186 pub theme: String,
187 pub reasoning_effort: ReasoningEffortLevel,
188 pub system_prompt_mode: SystemPromptMode,
189 pub tool_documentation_mode: ToolDocumentationMode,
190 pub verbosity: VerbosityLevel,
191 pub todo_planning_mode: bool,
192 pub checkpointing: AgentCheckpointingConfig,
193 pub small_model: AgentSmallModelConfig,
194 pub vibe_coding: AgentVibeCodingConfig,
195 pub max_conversation_turns: usize,
196}
197
198impl Default for AgentConfig {
199 fn default() -> Self {
200 Self {
201 default_model: "gpt-5-mini".to_string(),
202 theme: "default".to_string(),
203 reasoning_effort: ReasoningEffortLevel::Medium,
204 system_prompt_mode: SystemPromptMode::Default,
205 tool_documentation_mode: ToolDocumentationMode::Progressive,
206 verbosity: VerbosityLevel::Medium,
207 todo_planning_mode: false,
208 checkpointing: AgentCheckpointingConfig::default(),
209 small_model: AgentSmallModelConfig::default(),
210 vibe_coding: AgentVibeCodingConfig::default(),
211 max_conversation_turns: 100,
212 }
213 }
214}
215
216#[derive(Debug, Clone, Deserialize, Serialize)]
218pub struct PromptCacheConfig {
219 pub enabled: bool,
220}
221
222impl Default for PromptCacheConfig {
223 fn default() -> Self {
224 Self { enabled: true }
225 }
226}
227
228#[derive(Debug, Clone, Deserialize, Serialize)]
230pub struct McpConfig {
231 pub enabled: bool,
232}
233
234impl Default for McpConfig {
235 fn default() -> Self {
236 Self { enabled: true }
237 }
238}
239
240#[derive(Debug, Clone, Deserialize, Serialize)]
242pub struct AcpZedConfig {
243 pub workspace_trust: AgentClientProtocolZedWorkspaceTrustMode,
244}
245
246impl Default for AcpZedConfig {
247 fn default() -> Self {
248 Self {
249 workspace_trust: AgentClientProtocolZedWorkspaceTrustMode::ToolsPolicy,
250 }
251 }
252}
253
254#[derive(Debug, Clone, Deserialize, Serialize, Default)]
256pub struct AcpConfig {
257 pub zed: AcpZedConfig,
258}
259
260#[derive(Debug, Clone, Deserialize, Serialize, Default)]
262pub struct FullAutoConfig {
263 pub enabled: bool,
264}
265
266#[derive(Debug, Clone, Deserialize, Serialize, Default)]
268pub struct AutomationConfig {
269 pub full_auto: FullAutoConfig,
270}
271
272#[derive(Debug, Clone, Deserialize, Serialize)]
274pub struct ToolsConfig {
275 pub default_policy: ToolPolicy,
276}
277
278impl Default for ToolsConfig {
279 fn default() -> Self {
280 Self { default_policy: ToolPolicy::Prompt }
281 }
282}
283
284#[derive(Debug, Clone, Deserialize, Serialize)]
286pub struct SecurityConfig {
287 pub human_in_the_loop: bool,
288}
289
290impl Default for SecurityConfig {
291 fn default() -> Self {
292 Self { human_in_the_loop: true }
293 }
294}
295
296#[derive(Debug, Clone, Deserialize, Serialize)]
298pub struct ContextConfig {
299 pub max_context_tokens: usize,
300 pub trim_to_percent: u8,
301}
302
303impl Default for ContextConfig {
304 fn default() -> Self {
305 Self { max_context_tokens: 128_000, trim_to_percent: 80 }
306 }
307}
308
309#[derive(Debug, Clone, Deserialize, Serialize)]
311pub struct SyntaxHighlightingConfig {
312 pub enabled: bool,
313 pub theme: String,
314 pub cache_themes: bool,
315 pub max_file_size_mb: usize,
316 pub enabled_languages: Vec<String>,
317 pub highlight_timeout_ms: u64,
318}
319
320impl Default for SyntaxHighlightingConfig {
321 fn default() -> Self {
322 Self {
323 enabled: true,
324 theme: "base16-ocean.dark".to_string(),
325 cache_themes: true,
326 max_file_size_mb: 5,
327 enabled_languages: vec![
328 "rust".to_string(),
329 "python".to_string(),
330 "javascript".to_string(),
331 "typescript".to_string(),
332 "go".to_string(),
333 "bash".to_string(),
334 "json".to_string(),
335 "yaml".to_string(),
336 "toml".to_string(),
337 "markdown".to_string(),
338 ],
339 highlight_timeout_ms: 300,
340 }
341 }
342}
343
344#[derive(Debug, Clone, Deserialize, Serialize)]
346pub struct PtyConfig {
347 pub enabled: bool,
348 pub default_rows: u16,
349 pub default_cols: u16,
350 pub command_timeout_seconds: u64,
351}
352
353impl Default for PtyConfig {
354 fn default() -> Self {
355 Self {
356 enabled: true,
357 default_rows: 24,
358 default_cols: 80,
359 command_timeout_seconds: 300,
360 }
361 }
362}
363
364pub fn keyboard_protocol_to_flags(config: &KeyboardProtocolConfig) -> crossterm::event::KeyboardEnhancementFlags {
366 keyboard_protocol_to_flags_for_terminal(
367 config,
368 cfg!(target_os = "macos"),
369 std::env::var("TERM_PROGRAM").ok().as_deref(),
370 std::env::var("TERM").ok().as_deref(),
371 )
372}
373
374fn keyboard_protocol_to_flags_for_terminal(
375 config: &KeyboardProtocolConfig,
376 is_macos: bool,
377 term_program: Option<&str>,
378 term: Option<&str>,
379) -> crossterm::event::KeyboardEnhancementFlags {
380 use ratatui::crossterm::event::KeyboardEnhancementFlags;
381
382 if !config.enabled {
383 return KeyboardEnhancementFlags::empty();
384 }
385
386 let mut flags = match config.mode.as_str() {
387 "default" => {
388 KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
389 | KeyboardEnhancementFlags::REPORT_EVENT_TYPES
390 | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
391 }
392 "full" => {
393 KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
394 | KeyboardEnhancementFlags::REPORT_EVENT_TYPES
395 | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
396 | KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES
397 }
398 "minimal" => KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES,
399 "custom" => {
400 let mut flags = KeyboardEnhancementFlags::empty();
401 if config.disambiguate_escape_codes {
402 flags |= KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES;
403 }
404 if config.report_event_types {
405 flags |= KeyboardEnhancementFlags::REPORT_EVENT_TYPES;
406 }
407 if config.report_alternate_keys {
408 flags |= KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS;
409 }
410 if config.report_all_keys {
411 flags |= KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES;
412 }
413 flags
414 }
415 _ => {
416 KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
417 | KeyboardEnhancementFlags::REPORT_EVENT_TYPES
418 | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
419 }
420 };
421
422 if should_force_report_all_keys(config.mode.as_str(), is_macos, term_program, term) {
423 flags |= KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES;
424 }
425
426 flags
427}
428
429fn should_force_report_all_keys(mode: &str, is_macos: bool, term_program: Option<&str>, term: Option<&str>) -> bool {
430 if !is_macos || !matches!(mode, "default") {
431 return false;
432 }
433
434 is_ghostty_terminal(term_program, term)
437}
438
439#[cfg(test)]
440mod keyboard_protocol_tests {
441 use super::*;
442 use ratatui::crossterm::event::KeyboardEnhancementFlags;
443
444 fn default_keyboard_protocol_config() -> KeyboardProtocolConfig {
445 KeyboardProtocolConfig {
446 enabled: true,
447 mode: "default".to_string(),
448 disambiguate_escape_codes: true,
449 report_event_types: true,
450 report_alternate_keys: true,
451 report_all_keys: false,
452 }
453 }
454
455 #[test]
456 fn keyboard_protocol_defaults_keep_standard_flags() {
457 let flags = keyboard_protocol_to_flags_for_terminal(
458 &default_keyboard_protocol_config(),
459 false,
460 Some("Ghostty"),
461 Some("xterm-ghostty"),
462 );
463
464 assert!(flags.contains(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES));
465 assert!(flags.contains(KeyboardEnhancementFlags::REPORT_EVENT_TYPES));
466 assert!(flags.contains(KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS));
467 assert!(!flags.contains(KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES));
468 }
469
470 #[test]
471 fn keyboard_protocol_defaults_enable_all_keys_for_ghostty_on_macos() {
472 let flags = keyboard_protocol_to_flags_for_terminal(
473 &default_keyboard_protocol_config(),
474 true,
475 Some("Ghostty"),
476 Some("xterm-ghostty"),
477 );
478
479 assert!(flags.contains(KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES));
480 }
481
482 #[test]
483 fn keyboard_protocol_custom_mode_respects_explicit_report_all_keys_setting() {
484 let flags = keyboard_protocol_to_flags_for_terminal(
485 &KeyboardProtocolConfig {
486 enabled: true,
487 mode: "custom".to_string(),
488 disambiguate_escape_codes: true,
489 report_event_types: true,
490 report_alternate_keys: true,
491 report_all_keys: false,
492 },
493 true,
494 Some("Ghostty"),
495 Some("xterm-ghostty"),
496 );
497
498 assert!(!flags.contains(KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES));
499 }
500}