1use crate::core::PromptCachingConfig;
4use serde::{Deserialize, Deserializer, Serialize};
5use std::collections::BTreeMap;
6use std::env;
7use std::fmt;
8use std::path::PathBuf;
9
10pub use vtcode_commons::reasoning::ReasoningEffortLevel;
12
13#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
17#[serde(rename_all = "lowercase")]
18#[derive(Default)]
19pub enum SystemPromptMode {
20 #[default]
23 Minimal,
24 Lightweight,
27 Default,
30 Specialized,
33}
34
35impl SystemPromptMode {
36 fn as_str(self) -> &'static str {
38 match self {
39 Self::Minimal => "minimal",
40 Self::Lightweight => "lightweight",
41 Self::Default => "default",
42 Self::Specialized => "specialized",
43 }
44 }
45
46 pub fn parse(value: &str) -> Option<Self> {
48 let normalized = value.trim();
49 if normalized.eq_ignore_ascii_case("minimal") {
50 Some(Self::Minimal)
51 } else if normalized.eq_ignore_ascii_case("lightweight") {
52 Some(Self::Lightweight)
53 } else if normalized.eq_ignore_ascii_case("default") {
54 Some(Self::Default)
55 } else if normalized.eq_ignore_ascii_case("specialized") {
56 Some(Self::Specialized)
57 } else {
58 None
59 }
60 }
61
62 pub fn allowed_values() -> &'static [&'static str] {
64 &["minimal", "lightweight", "default", "specialized"]
65 }
66}
67
68impl fmt::Display for SystemPromptMode {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 f.write_str(self.as_str())
71 }
72}
73
74impl<'de> Deserialize<'de> for SystemPromptMode {
75 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
76 where
77 D: Deserializer<'de>,
78 {
79 let raw = String::deserialize(deserializer)?;
80 if let Some(parsed) = Self::parse(&raw) {
81 Ok(parsed)
82 } else {
83 Ok(Self::default())
84 }
85 }
86}
87
88#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
92#[serde(rename_all = "lowercase")]
93#[derive(Default)]
94pub enum ToolDocumentationMode {
95 Minimal,
99 #[default]
104 Progressive,
105 Full,
108}
109
110impl ToolDocumentationMode {
111 fn as_str(self) -> &'static str {
113 match self {
114 Self::Minimal => "minimal",
115 Self::Progressive => "progressive",
116 Self::Full => "full",
117 }
118 }
119
120 fn parse(value: &str) -> Option<Self> {
122 let normalized = value.trim();
123 if normalized.eq_ignore_ascii_case("minimal") {
124 Some(Self::Minimal)
125 } else if normalized.eq_ignore_ascii_case("progressive") {
126 Some(Self::Progressive)
127 } else if normalized.eq_ignore_ascii_case("full") {
128 Some(Self::Full)
129 } else {
130 None
131 }
132 }
133
134 pub fn allowed_values() -> &'static [&'static str] {
136 &["minimal", "progressive", "full"]
137 }
138}
139
140impl fmt::Display for ToolDocumentationMode {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 f.write_str(self.as_str())
143 }
144}
145
146impl<'de> Deserialize<'de> for ToolDocumentationMode {
147 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
148 where
149 D: Deserializer<'de>,
150 {
151 let raw = String::deserialize(deserializer)?;
152 if let Some(parsed) = Self::parse(&raw) {
153 Ok(parsed)
154 } else {
155 Ok(Self::default())
156 }
157 }
158}
159
160#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
166#[serde(rename_all = "snake_case")]
167#[derive(Default)]
168pub enum ShellPromptProfile {
169 #[default]
172 Auto,
173 UnixLike,
175 PowerShell,
177}
178
179impl ShellPromptProfile {
180 fn as_str(self) -> &'static str {
182 match self {
183 Self::Auto => "auto",
184 Self::UnixLike => "unix_like",
185 Self::PowerShell => "powershell",
186 }
187 }
188
189 fn parse(value: &str) -> Option<Self> {
191 let normalized = value.trim();
192 if normalized.eq_ignore_ascii_case("auto") {
193 Some(Self::Auto)
194 } else if normalized.eq_ignore_ascii_case("unix_like")
195 || normalized.eq_ignore_ascii_case("unix-like")
196 || normalized.eq_ignore_ascii_case("unix")
197 || normalized.eq_ignore_ascii_case("posix")
198 {
199 Some(Self::UnixLike)
200 } else if normalized.eq_ignore_ascii_case("powershell")
201 || normalized.eq_ignore_ascii_case("power_shell")
202 || normalized.eq_ignore_ascii_case("windows")
203 {
204 Some(Self::PowerShell)
205 } else {
206 None
207 }
208 }
209
210 pub fn allowed_values() -> &'static [&'static str] {
212 &["auto", "unix_like", "powershell"]
213 }
214
215 pub fn resolve_for_current_platform(self) -> ResolvedShellPromptProfile {
217 self.resolve_for_platform(ShellProfilePlatform::current())
218 }
219
220 fn resolve_for_platform(self, platform: ShellProfilePlatform) -> ResolvedShellPromptProfile {
222 match self {
223 Self::Auto => match platform {
224 ShellProfilePlatform::NativeWindows => ResolvedShellPromptProfile::PowerShell,
225 ShellProfilePlatform::UnixLike | ShellProfilePlatform::Wsl => ResolvedShellPromptProfile::UnixLike,
226 },
227 Self::UnixLike => ResolvedShellPromptProfile::UnixLike,
228 Self::PowerShell => ResolvedShellPromptProfile::PowerShell,
229 }
230 }
231}
232
233impl fmt::Display for ShellPromptProfile {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 f.write_str(self.as_str())
236 }
237}
238
239impl<'de> Deserialize<'de> for ShellPromptProfile {
240 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241 where
242 D: Deserializer<'de>,
243 {
244 let raw = String::deserialize(deserializer)?;
245 if let Some(parsed) = Self::parse(&raw) {
246 Ok(parsed)
247 } else {
248 Ok(Self::default())
249 }
250 }
251}
252
253#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
256#[serde(rename_all = "snake_case")]
257pub enum ShellProfilePlatform {
258 UnixLike,
259 Wsl,
260 NativeWindows,
261}
262
263impl ShellProfilePlatform {
264 fn current() -> Self {
265 if cfg!(windows) {
266 Self::NativeWindows
267 } else if is_wsl_environment() {
268 Self::Wsl
269 } else {
270 Self::UnixLike
271 }
272 }
273}
274
275#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
278#[serde(rename_all = "snake_case")]
279pub enum ResolvedShellPromptProfile {
280 UnixLike,
281 PowerShell,
282}
283
284impl ResolvedShellPromptProfile {
285 fn as_str(self) -> &'static str {
286 match self {
287 Self::UnixLike => "unix_like",
288 Self::PowerShell => "powershell",
289 }
290 }
291}
292
293impl fmt::Display for ResolvedShellPromptProfile {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 f.write_str(self.as_str())
296 }
297}
298
299fn is_wsl_environment() -> bool {
300 if env::var_os("WSL_DISTRO_NAME").is_some() || env::var_os("WSL_INTEROP").is_some() {
301 return true;
302 }
303
304 for path in ["/proc/sys/kernel/osrelease", "/proc/version"] {
305 if let Ok(contents) = std::fs::read_to_string(path)
306 && contents.to_ascii_lowercase().contains("microsoft")
307 {
308 return true;
309 }
310 }
311
312 false
313}
314
315#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
317#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
318#[serde(rename_all = "lowercase")]
319#[derive(Default)]
320pub enum VerbosityLevel {
321 Low,
322 #[default]
323 Medium,
324 High,
325}
326
327impl VerbosityLevel {
328 pub fn as_str(self) -> &'static str {
330 match self {
331 Self::Low => "low",
332 Self::Medium => "medium",
333 Self::High => "high",
334 }
335 }
336
337 pub fn parse(value: &str) -> Option<Self> {
339 let normalized = value.trim();
340 if normalized.eq_ignore_ascii_case("low") {
341 Some(Self::Low)
342 } else if normalized.eq_ignore_ascii_case("medium") {
343 Some(Self::Medium)
344 } else if normalized.eq_ignore_ascii_case("high") {
345 Some(Self::High)
346 } else {
347 None
348 }
349 }
350
351 pub fn allowed_values() -> &'static [&'static str] {
353 &["low", "medium", "high"]
354 }
355}
356
357impl fmt::Display for VerbosityLevel {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359 f.write_str(self.as_str())
360 }
361}
362
363impl<'de> Deserialize<'de> for VerbosityLevel {
364 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
365 where
366 D: Deserializer<'de>,
367 {
368 let raw = String::deserialize(deserializer)?;
369 if let Some(parsed) = Self::parse(&raw) {
370 Ok(parsed)
371 } else {
372 Ok(Self::default())
373 }
374 }
375}
376
377#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
379#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
380#[serde(rename_all = "lowercase")]
381#[derive(Default)]
382pub enum UiSurfacePreference {
383 Auto,
384 Alternate,
385 #[default]
386 Inline,
387}
388
389impl UiSurfacePreference {
390 fn as_str(self) -> &'static str {
392 match self {
393 Self::Auto => "auto",
394 Self::Alternate => "alternate",
395 Self::Inline => "inline",
396 }
397 }
398
399 fn parse(value: &str) -> Option<Self> {
401 let normalized = value.trim();
402 if normalized.eq_ignore_ascii_case("auto") {
403 Some(Self::Auto)
404 } else if normalized.eq_ignore_ascii_case("alternate") || normalized.eq_ignore_ascii_case("alt") {
405 Some(Self::Alternate)
406 } else if normalized.eq_ignore_ascii_case("inline") {
407 Some(Self::Inline)
408 } else {
409 None
410 }
411 }
412
413 pub fn allowed_values() -> &'static [&'static str] {
415 &["auto", "alternate", "inline"]
416 }
417}
418
419impl fmt::Display for UiSurfacePreference {
420 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421 f.write_str(self.as_str())
422 }
423}
424
425impl<'de> Deserialize<'de> for UiSurfacePreference {
426 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
427 where
428 D: Deserializer<'de>,
429 {
430 let raw = String::deserialize(deserializer)?;
431 if let Some(parsed) = Self::parse(&raw) {
432 Ok(parsed)
433 } else {
434 Ok(Self::default())
435 }
436 }
437}
438
439#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
441pub enum ModelSelectionSource {
442 #[default]
444 WorkspaceConfig,
445 CliOverride,
447}
448
449#[derive(Debug, Clone)]
451pub struct AgentConfig {
452 pub model: String,
453 pub api_key: String,
454 pub provider: String,
455 pub openai_chatgpt_auth: Option<crate::auth::OpenAIChatGptAuthHandle>,
456 pub api_key_env: String,
457 pub workspace: PathBuf,
458 pub verbose: bool,
459 pub quiet: bool,
460 pub theme: String,
461 pub reasoning_effort: ReasoningEffortLevel,
462 pub ui_surface: UiSurfacePreference,
463 pub prompt_cache: PromptCachingConfig,
464 pub model_source: ModelSelectionSource,
465 pub custom_api_keys: BTreeMap<String, String>,
466 pub checkpointing_enabled: bool,
467 pub checkpointing_storage_dir: Option<PathBuf>,
468 pub checkpointing_max_snapshots: usize,
469 pub checkpointing_max_age_days: Option<u64>,
470 pub max_conversation_turns: usize,
471 pub model_behavior: Option<crate::core::ModelConfig>,
472}
473
474#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
476pub enum CapabilityLevel {
477 Basic,
479 FileReading,
481 FileListing,
483 Bash,
485 Editing,
487 CodeSearch,
489}
490
491#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct SessionInfo {
494 pub session_id: String,
495 pub start_time: u64,
496 pub total_turns: usize,
497 pub total_decisions: usize,
498 pub error_count: usize,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct ErrorInfo {
504 error_type: String,
505 message: String,
506 turn_number: usize,
507 recoverable: bool,
508 timestamp: u64,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct PerformanceMetrics {
514 pub session_duration_seconds: u64,
515 pub total_api_calls: usize,
516 pub total_tokens_used: Option<usize>,
517 pub average_response_time_ms: f64,
518 pub tool_execution_count: usize,
519 pub error_count: usize,
520 pub recovery_success_rate: f64,
521}
522
523#[derive(Debug, Clone, Serialize, Deserialize)]
525pub enum AnalysisDepth {
526 Basic,
527 Standard,
528 Deep,
529}
530
531#[derive(Debug, Clone, Serialize, Deserialize)]
533pub enum OutputFormat {
534 Text,
535 Json,
536 Html,
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542
543 #[test]
544 fn test_reasoning_effort_parse_and_allowed_values_include_max() {
545 assert_eq!(ReasoningEffortLevel::parse("max"), Some(ReasoningEffortLevel::Max));
546 assert_eq!(ReasoningEffortLevel::Max.as_str(), "max");
547 assert!(ReasoningEffortLevel::allowed_values().contains(&"max"));
548 }
549
550 #[test]
551 fn shell_prompt_profile_parses_aliases() {
552 assert_eq!(ShellPromptProfile::parse("auto"), Some(ShellPromptProfile::Auto));
553 assert_eq!(ShellPromptProfile::parse("unix-like"), Some(ShellPromptProfile::UnixLike));
554 assert_eq!(ShellPromptProfile::parse("posix"), Some(ShellPromptProfile::UnixLike));
555 assert_eq!(ShellPromptProfile::parse("PowerShell"), Some(ShellPromptProfile::PowerShell));
556 assert_eq!(ShellPromptProfile::parse("unknown"), None);
557 }
558
559 #[test]
560 fn ui_surface_defaults_to_inline() {
561 assert_eq!(UiSurfacePreference::default(), UiSurfacePreference::Inline);
562 }
563
564 #[test]
565 fn auto_shell_prompt_profile_selects_platform_defaults() {
566 let profile = ShellPromptProfile::Auto;
567
568 assert_eq!(profile.resolve_for_platform(ShellProfilePlatform::UnixLike), ResolvedShellPromptProfile::UnixLike);
569 assert_eq!(profile.resolve_for_platform(ShellProfilePlatform::Wsl), ResolvedShellPromptProfile::UnixLike);
570 assert_eq!(
571 profile.resolve_for_platform(ShellProfilePlatform::NativeWindows),
572 ResolvedShellPromptProfile::PowerShell
573 );
574 }
575
576 #[test]
577 fn explicit_shell_prompt_profiles_override_platform_defaults() {
578 assert_eq!(
579 ShellPromptProfile::UnixLike.resolve_for_platform(ShellProfilePlatform::NativeWindows),
580 ResolvedShellPromptProfile::UnixLike
581 );
582 assert_eq!(
583 ShellPromptProfile::PowerShell.resolve_for_platform(ShellProfilePlatform::Wsl),
584 ResolvedShellPromptProfile::PowerShell
585 );
586 }
587}