Skip to main content

vtcode_core/tool_policy/
model.rs

1use anyhow::Result;
2use indexmap::{IndexMap, IndexSet};
3use serde::{Deserialize, Serialize};
4
5use super::{McpPolicyStore, ToolPolicy};
6
7/// Decision result for tool execution
8#[derive(Debug, Clone, PartialEq)]
9pub enum ToolExecutionDecision {
10    /// The tool is allowed to execute.
11    Allowed,
12    /// The tool execution is denied.
13    Denied,
14    /// The tool execution is denied with a feedback message.
15    DeniedWithFeedback(String),
16}
17
18impl ToolExecutionDecision {
19    pub fn is_allowed(&self) -> bool {
20        matches!(self, Self::Allowed)
21    }
22}
23
24/// Tool policy configuration stored in the canonical user config directory.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct ToolPolicyConfig {
27    /// Configuration version for future compatibility
28    pub version: u32,
29    /// Available tools at time of last update
30    pub available_tools: Vec<String>,
31    /// Policy for each tool
32    pub policies: IndexMap<String, ToolPolicy>,
33    /// Optional per-tool constraints to scope permissions and enforce safety
34    #[serde(default)]
35    pub constraints: IndexMap<String, ToolConstraints>,
36    /// MCP-specific policy configuration
37    #[serde(default)]
38    pub mcp: McpPolicyStore,
39    /// Explicit remembered approvals for future prompts in this workspace
40    #[serde(default)]
41    pub approval_cache: ApprovalCacheConfig,
42}
43
44impl Default for ToolPolicyConfig {
45    fn default() -> Self {
46        Self {
47            version: 1,
48            available_tools: Vec::new(),
49            policies: IndexMap::new(),
50            constraints: IndexMap::new(),
51            mcp: McpPolicyStore::default(),
52            approval_cache: ApprovalCacheConfig::default(),
53        }
54    }
55}
56
57/// Persisted approval cache stored alongside tool policies
58#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
59pub struct ApprovalCacheConfig {
60    /// Stable approval keys that should bypass future prompts
61    #[serde(default)]
62    pub allowed: IndexSet<String>,
63    /// Shell command prefixes that should bypass future prompts in the same scope
64    #[serde(default)]
65    pub prefixes: IndexSet<String>,
66    /// Regex patterns matched against approval keys for advanced manual policy tuning
67    #[serde(default)]
68    pub regexes: IndexSet<String>,
69}
70
71/// Maximum remembered approval entries per cache collection. Unbounded growth
72/// turned `tool-policy.json` into a multi-hundred-kilobyte file after a few
73/// long sessions (see session-vtcode-20260925T234343Z: 294 KB). Oldest
74/// insertions are dropped first so recent approvals survive.
75pub const APPROVAL_CACHE_MAX_ENTRIES: usize = 256;
76
77impl ApprovalCacheConfig {
78    /// Drop the oldest entries so each collection stays within
79    /// [`APPROVAL_CACHE_MAX_ENTRIES`]. Returns `true` when anything was trimmed.
80    pub fn trim_to_budget(&mut self) -> bool {
81        let mut trimmed = false;
82        for collection in [&mut self.allowed, &mut self.prefixes, &mut self.regexes] {
83            while collection.len() > APPROVAL_CACHE_MAX_ENTRIES {
84                let _ = collection.shift_remove_index(0);
85                trimmed = true;
86            }
87        }
88        trimmed
89    }
90}
91
92/// Handler for tool permission prompts
93///
94/// This trait allows different UI modes (CLI, TUI) to provide their own
95/// implementation for prompting users about tool execution.
96pub trait PermissionPromptHandler: Send + Sync {
97    /// Prompt the user for tool execution permission
98    fn prompt_tool_permission(&mut self, tool_name: &str) -> Result<ToolExecutionDecision>;
99}
100
101/// Scoped, optional constraints for a tool to align with safe defaults
102#[derive(Debug, Clone, Default, Serialize, Deserialize)]
103pub struct ToolConstraints {
104    /// Whitelisted modes for tools that support modes (e.g., 'terminal')
105    #[serde(default)]
106    pub allowed_modes: Option<Vec<String>>,
107    /// Cap on results for list/search-like tools
108    #[serde(default)]
109    pub max_results_per_call: Option<usize>,
110    /// Cap on items scanned for file listing
111    #[serde(default)]
112    pub max_items_per_call: Option<usize>,
113    /// Default response format if unspecified by caller
114    #[serde(default)]
115    pub default_response_format: Option<String>,
116    /// Cap maximum bytes when reading files
117    #[serde(default)]
118    pub max_bytes_per_read: Option<usize>,
119    /// Cap maximum bytes when fetching over the network
120    #[serde(default)]
121    pub max_response_bytes: Option<usize>,
122    /// Allowed URL schemes for network tools
123    #[serde(default)]
124    pub allowed_url_schemes: Option<Vec<String>>,
125    /// Denied URL hosts or suffixes for network tools
126    #[serde(default)]
127    pub denied_url_hosts: Option<Vec<String>>,
128}