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_PREVIEW => Some(ModelId::Gemini25FlashPreview),
27            s if s == models::GEMINI_2_5_PRO => Some(ModelId::Gemini25Pro),
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_FLASH_PREVIEW => {
111                println!("{}", style("[FAST] Using balanced model:").green());
112                println!("• Good quality responses");
113                println!("• Reasonable cost");
114                println!("• Fast response times");
115            }
116            s if s == models::GEMINI_2_5_PRO => {
117                println!("{}", style("Using most capable model:").yellow());
118                println!("• Highest quality responses");
119                println!("• Higher cost per token");
120                println!("• Slower response times");
121            }
122            _ => {}
123        }
124
125        // Agent mode recommendations
126        match agent_mode {
127            AgentMode::SingleCoder => {
128                println!("{}", style("Single-Agent System:").blue());
129                println!("• Streamlined execution");
130                println!("• Decision Ledger tracking");
131                println!("• Lower API costs");
132                println!("• Faster task completion");
133                println!("• Best for most development tasks");
134            }
135        }
136
137        println!();
138    }
139
140    /// Validate resource usage and warn about potential costs
141    pub fn validate_resource_usage(
142        model: &str,
143        _agent_mode: &AgentMode,
144        estimated_tokens: Option<usize>,
145    ) -> Result<bool> {
146        use crate::config::constants::models;
147        let mut warnings = Vec::new();
148
149        // Check for expensive model usage
150        if model == models::GEMINI_2_5_PRO {
151            warnings.push("Using most expensive model (Gemini 2.5 Pro)");
152        }
153
154        // Single-agent mode uses standard resource usage
155
156        // Check for high token usage
157        if let Some(tokens) = estimated_tokens {
158            if tokens > 10000 {
159                warnings.push("High token usage estimated (>10k tokens)");
160            }
161        }
162
163        if !warnings.is_empty() {
164            println!("{}", style(" Resource Usage Warning").yellow().bold());
165            for warning in &warnings {
166                println!("• {}", warning);
167            }
168            println!();
169
170            let confirmed = UserConfirmation::confirm_action(
171                "Do you want to proceed with these resource usage implications?",
172                false,
173            )?;
174
175            return Ok(confirmed);
176        }
177
178        Ok(true)
179    }
180}
181
182// Re-export ModelId::from_str for internal use
183impl ModelId {
184    /// Parse a model string into a ModelId
185    pub fn from_str(s: &str) -> Result<Self, &'static str> {
186        use std::str::FromStr;
187
188        <Self as FromStr>::from_str(s).map_err(|_| "Unknown model")
189    }
190}