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