1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! Configuration, limits, and shared state for built-in tools.
use parking_lot::Mutex;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use super::policy::{BuiltinToolPolicy, ShellAvailability};
#[derive(Debug, Clone)]
/// Runtime configuration shared by all built-in tool instances.
///
/// Clones share read-tracking state, which enforces the read-before-edit
/// invariant across tools created from the same configuration.
pub struct BuiltinToolConfig {
/// Workspace roots within which filesystem operations are permitted.
pub allowed_roots: Vec<PathBuf>,
/// Security and approval policy applied by built-in tools.
pub policy: BuiltinToolPolicy,
/// Shell backend that may be registered and instantiated.
pub shell_availability: ShellAvailability,
/// Maximum bytes retained independently from shell stdout and stderr.
pub max_output_bytes: usize,
/// Default maximum number of bytes returned by the read tool.
pub max_read_bytes: usize,
/// Default timeout available to built-in operations.
pub default_timeout: Duration,
/// Default maximum duration of a shell command.
pub shell_timeout: Duration,
/// Maximum number of paths collected by a glob search.
pub max_glob_results: usize,
/// Maximum number of content matches collected by a grep search.
pub max_grep_results: usize,
/// Maximum response-body bytes returned by web fetch.
pub max_fetch_bytes: usize,
/// Tool names excluded from registration and instantiation.
pub disabled_tools: Vec<String>,
/// Canonical paths successfully read and therefore eligible for editing.
pub read_tracking: Arc<Mutex<HashSet<PathBuf>>>,
}
impl Default for BuiltinToolConfig {
fn default() -> Self {
Self {
allowed_roots: vec![std::env::current_dir().unwrap_or_default()],
policy: BuiltinToolPolicy::default(),
shell_availability: ShellAvailability::detect(),
max_output_bytes: 256 * 1024,
max_read_bytes: 256 * 1024,
default_timeout: Duration::from_secs(120),
shell_timeout: Duration::from_secs(120),
max_glob_results: 1000,
max_grep_results: 500,
max_fetch_bytes: 512 * 1024,
disabled_tools: Vec::new(),
read_tracking: Arc::new(Mutex::new(HashSet::new())),
}
}
}
impl BuiltinToolConfig {
/// Create a configuration with the given allowed roots and default limits.
pub fn new(allowed_roots: Vec<PathBuf>) -> Self {
Self {
allowed_roots,
..Self::default()
}
}
/// Select the shell backend exposed by this configuration.
pub fn with_shell_availability(mut self, avail: ShellAvailability) -> Self {
self.shell_availability = avail;
self
}
/// Disable the tools whose exact names occur in `tools`.
pub fn with_disabled_tools(mut self, tools: Vec<String>) -> Self {
self.disabled_tools = tools;
self
}
/// Replace the security and approval policy.
pub fn with_policy(mut self, policy: BuiltinToolPolicy) -> Self {
self.policy = policy;
self
}
/// Set the maximum retained bytes for each shell output stream.
pub fn with_max_output_bytes(mut self, bytes: usize) -> Self {
self.max_output_bytes = bytes;
self
}
/// Set the read tool's default byte limit.
pub fn with_max_read_bytes(mut self, bytes: usize) -> Self {
self.max_read_bytes = bytes;
self
}
/// Set the default shell-command timeout.
pub fn with_shell_timeout(mut self, timeout: Duration) -> Self {
self.shell_timeout = timeout;
self
}
/// Set the general default operation timeout.
pub fn with_default_timeout(mut self, timeout: Duration) -> Self {
self.default_timeout = timeout;
self
}
/// Return whether a tool name is absent from [`Self::disabled_tools`].
pub fn is_tool_enabled(&self, tool_name: &str) -> bool {
!self.disabled_tools.iter().any(|d| d == tool_name)
}
/// Validate invariants required before using this configuration.
///
/// # Errors
///
/// Returns a configuration error when no roots are configured or any root
/// is relative.
pub fn validate(&self) -> Result<(), super::error::BuiltinToolError> {
if self.allowed_roots.is_empty() {
return Err(super::error::BuiltinToolError::config(
"at least one allowed root is required",
));
}
for root in &self.allowed_roots {
if !root.is_absolute() {
return Err(super::error::BuiltinToolError::config(format!(
"allowed root must be an absolute path: {}",
root.display()
)));
}
}
Ok(())
}
/// Record that `path` has been read for subsequent edit authorization.
///
/// Callers must provide the canonical path used by the corresponding edit
/// operation because tracking stores paths verbatim.
pub fn record_read(&self, path: &Path) {
self.read_tracking.lock().insert(path.to_path_buf());
}
/// Return whether `path` has previously been recorded as read.
///
/// Callers must provide the same canonical path that was passed to
/// [`Self::record_read`].
pub fn has_read(&self, path: &Path) -> bool {
self.read_tracking.lock().contains(path)
}
}