recursive/
tool_set_provider.rs1use crate::tools::{policy_sandbox::PolicyConfig, ToolRegistry};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub enum SandboxMode {
17 #[default]
19 None,
20 Policy,
23 Container,
25 MicroVm,
28}
29
30pub trait ToolSetProvider: Send + Sync + 'static {
36 fn build_registry(&self) -> ToolRegistry;
38 fn sandbox_mode(&self) -> SandboxMode;
40}
41
42pub struct LocalToolSetProvider {
48 workspace: std::path::PathBuf,
49 shell_timeout_secs: u64,
50 skills: Vec<crate::skills::Skill>,
51}
52
53impl LocalToolSetProvider {
54 pub fn new(
56 workspace: std::path::PathBuf,
57 shell_timeout_secs: u64,
58 skills: Vec<crate::skills::Skill>,
59 ) -> Self {
60 Self {
61 workspace,
62 shell_timeout_secs,
63 skills,
64 }
65 }
66}
67
68impl ToolSetProvider for LocalToolSetProvider {
69 fn build_registry(&self) -> ToolRegistry {
70 crate::tools::build_standard_tools(&self.workspace, &self.skills, self.shell_timeout_secs)
71 }
72
73 fn sandbox_mode(&self) -> SandboxMode {
74 SandboxMode::None
75 }
76}
77
78pub struct PolicyToolSetProvider {
85 workspace: std::path::PathBuf,
86 shell_timeout_secs: u64,
87 skills: Vec<crate::skills::Skill>,
88 pub policy: PolicyConfig,
90}
91
92impl PolicyToolSetProvider {
93 pub fn new(
94 workspace: std::path::PathBuf,
95 shell_timeout_secs: u64,
96 skills: Vec<crate::skills::Skill>,
97 policy: PolicyConfig,
98 ) -> Self {
99 Self {
100 workspace,
101 shell_timeout_secs,
102 skills,
103 policy,
104 }
105 }
106
107 pub fn restrictive(
109 workspace: std::path::PathBuf,
110 shell_timeout_secs: u64,
111 skills: Vec<crate::skills::Skill>,
112 ) -> Self {
113 Self::new(
114 workspace,
115 shell_timeout_secs,
116 skills,
117 PolicyConfig::default_restrictive(),
118 )
119 }
120}
121
122impl ToolSetProvider for PolicyToolSetProvider {
123 fn build_registry(&self) -> ToolRegistry {
124 crate::tools::build_standard_tools(&self.workspace, &self.skills, self.shell_timeout_secs)
125 .with_policy(self.policy.clone())
126 }
127
128 fn sandbox_mode(&self) -> SandboxMode {
129 SandboxMode::Policy
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use tempfile::TempDir;
137
138 fn provider() -> (LocalToolSetProvider, TempDir) {
139 let dir = TempDir::new().unwrap();
140 let p = LocalToolSetProvider::new(dir.path().to_path_buf(), 30, vec![]);
141 (p, dir)
142 }
143
144 #[test]
145 fn local_provider_sandbox_mode_is_none() {
146 let (p, _dir) = provider();
147 assert_eq!(p.sandbox_mode(), SandboxMode::None);
148 }
149
150 #[test]
151 fn local_provider_registry_non_empty() {
152 let (p, _dir) = provider();
153 let reg = p.build_registry();
154 let names = reg.names();
155 assert!(
156 names.iter().any(|n| n == "Bash"),
157 "expected Bash in registry, got: {names:?}"
158 );
159 }
160
161 #[test]
162 fn policy_provider_sandbox_mode_is_policy() {
163 let dir = TempDir::new().unwrap();
164 let p = PolicyToolSetProvider::restrictive(dir.path().to_path_buf(), 30, vec![]);
165 assert_eq!(p.sandbox_mode(), SandboxMode::Policy);
166 }
167
168 #[test]
169 fn policy_provider_attaches_policy_to_registry() {
170 let dir = TempDir::new().unwrap();
171 let p = PolicyToolSetProvider::restrictive(dir.path().to_path_buf(), 30, vec![]);
172 let reg = p.build_registry();
173 let policy = reg.policy().expect("policy should be attached");
175 assert!(policy.check_shell("ls").is_ok());
176 assert!(policy.check_shell("rm -rf /").is_err());
177 }
178
179 #[test]
180 fn policy_provider_new_uses_given_policy() {
181 use crate::tools::policy_sandbox::PolicyConfig;
183 let dir = TempDir::new().unwrap();
184 let policy = PolicyConfig::default();
185 let p = PolicyToolSetProvider::new(dir.path().to_path_buf(), 30, vec![], policy);
186 assert_eq!(p.sandbox_mode(), SandboxMode::Policy);
187 let reg = p.build_registry();
188 let attached_policy = reg.policy().expect("policy should be attached");
190 assert!(
191 attached_policy.check_shell("rm -rf /").is_ok(),
192 "permissive policy should allow all shell commands"
193 );
194 }
195
196 #[test]
197 fn sandbox_mode_default_is_none() {
198 assert_eq!(
200 SandboxMode::default(),
201 SandboxMode::None,
202 "SandboxMode default must be None"
203 );
204 }
205
206 #[test]
207 fn local_provider_build_registry_has_read_tool() {
208 let (p, _dir) = provider();
210 let reg = p.build_registry();
211 let names = reg.names();
212 assert!(
213 names.iter().any(|n| n == "Read"),
214 "local registry must contain 'Read' tool; found: {names:?}"
215 );
216 }
217}