vtcode_core/utils/
safety.rs

1//! Safety checks for VTCode operations
2//!
3//! This module provides safety validations for potentially expensive
4//! or resource-intensive operations to ensure user control and efficiency.
5
6use crate::config::models::ModelId;
7use crate::ui::user_confirmation::{AgentMode, UserConfirmation};
8use anyhow::Result;
9use console::style;
10
11/// Safety validation utilities for VTCode operations
12pub struct SafetyValidator;
13
14impl SafetyValidator {
15    /// Validate and potentially request confirmation for model usage
16    /// Returns the approved model to use, which may be different from the requested model
17    pub fn validate_model_usage(
18        requested_model: &str,
19        task_description: Option<&str>,
20        skip_confirmations: bool,
21    ) -> Result<String> {
22        use crate::config::constants::models;
23        // Parse the requested model
24        let model_id = match requested_model {
25            s if s == models::GEMINI_2_5_PRO => Some(ModelId::Gemini25Pro),
26            s if s == models::GEMINI_2_5_FLASH => Some(ModelId::Gemini25Flash),
27            s if s == models::GEMINI_2_5_FLASH_LITE => Some(ModelId::Gemini25FlashLite),
28            _ => None,
29        };
30
31        // Check if this is the most capable (and expensive) model
32        if let Some(ModelId::Gemini25Pro) = model_id {
33            let current_default = ModelId::default();
34
35            if skip_confirmations {
36                println!(
37                    "{}",
38                    style("Using Gemini 2.5 Pro model (confirmations skipped)").yellow()
39                );
40                return Ok(requested_model.to_string());
41            }
42
43            if let Some(task) = task_description {
44                println!("{}", style("Model Selection Review").cyan().bold());
45                println!("Task: {}", style(task).cyan());
46                println!();
47            }
48
49            // Ask for explicit confirmation before using the most capable model
50            let confirmed = UserConfirmation::confirm_pro_model_usage(current_default.as_str())?;
51            if !confirmed {
52                println!(
53                    "Falling back to default model: {}",
54                    current_default.display_name()
55                );
56                return Ok(current_default.as_str().to_string());
57            }
58        }
59
60        Ok(requested_model.to_string())
61    }
62
63    /// Validate agent mode selection based on task complexity and user preferences
64    /// Returns the recommended agent mode with user confirmation if needed
65    pub fn validate_agent_mode(
66        _task_description: &str,
67        _skip_confirmations: bool,
68    ) -> Result<AgentMode> {
69        // Always use single-agent mode
70        println!(
71            "{}",
72            style("Using single-agent mode with Decision Ledger").green()
73        );
74        Ok(AgentMode::SingleCoder)
75    }
76
77    /// Check if a model switch is safe and cost-effective
78    pub fn is_model_switch_safe(from_model: &str, to_model: &str) -> bool {
79        let from_id = ModelId::from_str(from_model).ok();
80        let to_id = ModelId::from_str(to_model).ok();
81
82        match (from_id, to_id) {
83            (Some(from), Some(to)) => {
84                // Switching to Pro model requires confirmation
85                !matches!(to, ModelId::Gemini25Pro) || matches!(from, ModelId::Gemini25Pro)
86            }
87            _ => true, // Unknown models are allowed
88        }
89    }
90
91    /// Display safety recommendations for the current configuration
92    pub fn display_safety_recommendations(
93        model: &str,
94        agent_mode: &AgentMode,
95        task_description: Option<&str>,
96    ) {
97        println!("{}", style(" Safety Configuration Summary").cyan().bold());
98        println!("Model: {}", style(model).green());
99        println!("Agent Mode: {}", style(format!("{:?}", agent_mode)).green());
100
101        if let Some(task) = task_description {
102            println!("Task: {}", style(task).cyan());
103        }
104
105        println!();
106
107        // Model-specific recommendations
108        use crate::config::constants::models;
109        match model {
110            s if s == models::GEMINI_2_5_PRO => {
111                println!("{}", style("Using most capable model:").yellow());
112                println!("• Highest quality responses");
113                println!("• Higher cost per token");
114                println!("• Slower response times");
115            }
116            s if s == models::GEMINI_2_5_FLASH => {
117                println!("{}", style("[FAST] Using balanced model:").green());
118                println!("• Good quality responses");
119                println!("• Reasonable cost");
120                println!("• Fast response times");
121            }
122            s if s == models::GEMINI_2_5_FLASH_LITE => {
123                println!("{}", style("Using fast model:").blue());
124                println!("• Quick responses");
125                println!("• Most cost-effective");
126                println!("• Good for simple tasks");
127            }
128            _ => {}
129        }
130
131        // Agent mode recommendations
132        match agent_mode {
133            AgentMode::SingleCoder => {
134                println!("{}", style("Single-Agent System:").blue());
135                println!("• Streamlined execution");
136                println!("• Decision Ledger tracking");
137                println!("• Lower API costs");
138                println!("• Faster task completion");
139                println!("• Best for most development tasks");
140            }
141        }
142
143        println!();
144    }
145
146    /// Validate resource usage and warn about potential costs
147    pub fn validate_resource_usage(
148        model: &str,
149        _agent_mode: &AgentMode,
150        estimated_tokens: Option<usize>,
151    ) -> Result<bool> {
152        use crate::config::constants::models;
153        let mut warnings = Vec::new();
154
155        // Check for expensive model usage
156        if model == models::GEMINI_2_5_PRO {
157            warnings.push("Using most expensive model (Gemini 2.5 Pro)");
158        }
159
160        // Single-agent mode uses standard resource usage
161
162        // Check for high token usage
163        if let Some(tokens) = estimated_tokens {
164            if tokens > 10000 {
165                warnings.push("High token usage estimated (>10k tokens)");
166            }
167        }
168
169        if !warnings.is_empty() {
170            println!("{}", style(" Resource Usage Warning").yellow().bold());
171            for warning in &warnings {
172                println!("• {}", warning);
173            }
174            println!();
175
176            let confirmed = UserConfirmation::confirm_action(
177                "Do you want to proceed with these resource usage implications?",
178                false,
179            )?;
180
181            return Ok(confirmed);
182        }
183
184        Ok(true)
185    }
186}
187
188// Re-export ModelId::from_str for internal use
189impl ModelId {
190    /// Parse a model string into a ModelId
191    pub fn from_str(s: &str) -> Result<Self, &'static str> {
192        use crate::config::constants::models;
193        match s {
194            s if s == models::GEMINI_2_5_FLASH_LITE => Ok(ModelId::Gemini25FlashLite),
195            s if s == models::GEMINI_2_5_FLASH => Ok(ModelId::Gemini25Flash),
196            s if s == models::GEMINI_2_5_PRO => Ok(ModelId::Gemini25Pro),
197            _ => Err("Unknown model"),
198        }
199    }
200}