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