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        use std::str::FromStr;
80        let from_id = ModelId::from_str(from_model).ok();
81        let to_id = ModelId::from_str(to_model).ok();
82
83        match (from_id, to_id) {
84            (Some(from), Some(to)) => {
85                // Switching to Pro model requires confirmation
86                !matches!(to, ModelId::Gemini25Pro) || matches!(from, ModelId::Gemini25Pro)
87            }
88            _ => true, // Unknown models are allowed
89        }
90    }
91
92    /// Display safety recommendations for the current configuration
93    pub fn display_safety_recommendations(
94        model: &str,
95        agent_mode: &AgentMode,
96        task_description: Option<&str>,
97    ) {
98        println!("{}", style(" Safety Configuration Summary").cyan().bold());
99        println!("Model: {}", style(model).green());
100        println!("Agent Mode: {}", style(format!("{:?}", agent_mode)).green());
101
102        if let Some(task) = task_description {
103            println!("Task: {}", style(task).cyan());
104        }
105
106        println!();
107
108        // Model-specific recommendations
109        use crate::config::constants::models;
110        match model {
111            s if s == models::GEMINI_2_5_FLASH_PREVIEW => {
112                println!("{}", style("[FAST] Using balanced model:").green());
113                println!("• Good quality responses");
114                println!("• Reasonable cost");
115                println!("• Fast response times");
116            }
117            s if s == models::GEMINI_2_5_PRO => {
118                println!("{}", style("Using most capable model:").yellow());
119                println!("• Highest quality responses");
120                println!("• Higher cost per token");
121                println!("• Slower response times");
122            }
123            _ => {}
124        }
125
126        // Agent mode recommendations
127        match agent_mode {
128            AgentMode::SingleCoder => {
129                println!("{}", style("Single-Agent System:").blue());
130                println!("• Streamlined execution");
131                println!("• Decision Ledger tracking");
132                println!("• Lower API costs");
133                println!("• Faster task completion");
134                println!("• Best for most development tasks");
135            }
136        }
137
138        println!();
139    }
140
141    /// Validate resource usage and warn about potential costs
142    pub fn validate_resource_usage(
143        model: &str,
144        _agent_mode: &AgentMode,
145        estimated_tokens: Option<usize>,
146    ) -> Result<bool> {
147        use crate::config::constants::models;
148        let mut warnings = Vec::new();
149
150        // Check for expensive model usage
151        if model == models::GEMINI_2_5_PRO {
152            warnings.push("Using most expensive model (Gemini 2.5 Pro)");
153        }
154
155        // Single-agent mode uses standard resource usage
156
157        // Check for high token usage
158        if let Some(tokens) = estimated_tokens
159            && tokens > 10000
160        {
161            warnings.push("High token usage estimated (>10k tokens)");
162        }
163
164        if !warnings.is_empty() {
165            println!("{}", style(" Resource Usage Warning").yellow().bold());
166            for warning in &warnings {
167                println!("• {}", warning);
168            }
169            println!();
170
171            let confirmed = UserConfirmation::confirm_action(
172                "Do you want to proceed with these resource usage implications?",
173                false,
174            )?;
175
176            return Ok(confirmed);
177        }
178
179        Ok(true)
180    }
181}
182
183// Re-export ModelId::from_str for internal use
184impl ModelId {
185    /// Parse a model string into a ModelId
186    pub fn parse_from_str(s: &str) -> Result<Self, &'static str> {
187        use std::str::FromStr;
188
189        <Self as FromStr>::from_str(s).map_err(|_| "Unknown model")
190    }
191}