1use std::collections::HashMap;
2use std::future::Future;
3use std::pin::Pin;
4
5use tokio_util::sync::CancellationToken;
6
7use crate::error::RuntimeError;
8use crate::value::Value;
9
10pub type BoxFut<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
15
16pub type ToolResult = Result<Value, RuntimeError>;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Tier {
20 Zero,
21 One,
22 Two,
23 Three,
24 Four,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
28pub enum ApprovalLevel {
29 Auto,
30 Approve,
31 Dangerous,
32}
33
34impl ApprovalLevel {
35 pub fn from_tier(tier: Tier) -> Self {
36 match tier {
37 Tier::Zero => ApprovalLevel::Auto,
38 Tier::One | Tier::Two => ApprovalLevel::Approve,
39 Tier::Three | Tier::Four => ApprovalLevel::Dangerous,
40 }
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum CancelBehavior {
46 AbortSafe,
47 Revertible,
48 Atomic,
49 Irreversible,
50}
51
52#[derive(Debug, Default, Clone)]
53pub struct ToolArgs {
54 pub positional: Vec<Value>,
55 pub named: Vec<(String, Value)>,
56}
57
58impl ToolArgs {
59 pub fn positional(&self, index: usize) -> Result<&Value, RuntimeError> {
60 self.positional
61 .get(index)
62 .ok_or_else(|| RuntimeError::MissingArg(format!("positional[{index}]")))
63 }
64
65 pub fn named(&self, name: &str) -> Option<&Value> {
66 self.named.iter().find(|(k, _)| k == name).map(|(_, v)| v)
67 }
68}
69
70#[derive(Clone, Default)]
71pub struct ToolCtx {
72 pub cancel: CancellationToken,
73 pub turn_id: Option<crate::event::TurnId>,
74 pub flow_run_id: Option<crate::event::FlowRunId>,
75 pub event_seq: Option<u64>,
76 pub prompt_resolver: Option<std::sync::Arc<dyn crate::rendezvous::PromptResolver>>,
77 pub registry: Option<std::sync::Arc<ToolRegistry>>,
78 pub sandbox: Option<std::sync::Arc<dyn crate::sandbox::Sandbox>>,
79 pub events: Option<crate::event::EventSink>,
80 pub stdout_broadcast: Option<tokio::sync::broadcast::Sender<String>>,
81 pub session_messages: Option<std::sync::Arc<Vec<crate::message::Message>>>,
82 pub session_messages_handle:
83 Option<std::sync::Arc<std::sync::Mutex<Vec<crate::message::Message>>>>,
84 pub session_runtime: Option<std::sync::Arc<crate::session::Session>>,
85 pub compact_lock_handle: Option<std::sync::Arc<tokio::sync::Mutex<()>>>,
86 pub current_node_id: Option<String>,
87 pub stream_tx: Option<tokio::sync::broadcast::Sender<crate::stream::StreamFrame>>,
88 pub read_files:
89 Option<std::sync::Arc<std::sync::Mutex<std::collections::HashSet<std::path::PathBuf>>>>,
90 pub approval: Option<std::sync::Arc<crate::session::ApprovalRegistry>>,
91 pub forms: Option<std::sync::Arc<crate::session::FormRegistry>>,
92 pub providers: Option<std::sync::Arc<crate::provider::ProviderRegistry>>,
93 pub session_dir: Option<std::path::PathBuf>,
94 pub data_root: Option<std::path::PathBuf>,
95 pub project_index: Option<std::sync::Arc<crate::index::AnchorIndex>>,
96 pub fs_access: crate::fs_access::FsAccessPolicy,
97 pub lifecycle_fire_tx:
98 Option<tokio::sync::mpsc::UnboundedSender<atman_dsl::ast::LifecycleEvent>>,
99 pub bg_registry: Option<std::sync::Arc<crate::tools::bash_bg::BgRegistry>>,
100 pub term_registry: Option<std::sync::Arc<crate::tools::term::TermRegistry>>,
101 pub session_id: Option<String>,
102 pub trust: Option<crate::trust::TrustConfig>,
103 pub current_model: Option<String>,
104 pub on_memory_recent: Option<std::sync::Arc<dyn Fn(u16) + Send + Sync>>,
106}
107
108impl ToolCtx {
109 pub fn new() -> Self {
110 Self::default()
111 }
112
113 pub fn with_anchors(
114 mut self,
115 turn_id: Option<crate::event::TurnId>,
116 flow_run_id: Option<crate::event::FlowRunId>,
117 event_seq: Option<u64>,
118 ) -> Self {
119 self.turn_id = turn_id;
120 self.flow_run_id = flow_run_id;
121 self.event_seq = event_seq;
122 self
123 }
124
125 pub fn with_registry(mut self, registry: std::sync::Arc<ToolRegistry>) -> Self {
126 self.registry = Some(registry);
127 self
128 }
129
130 pub fn with_sandbox(mut self, sandbox: std::sync::Arc<dyn crate::sandbox::Sandbox>) -> Self {
131 self.sandbox = Some(sandbox);
132 self
133 }
134
135 pub fn with_events(mut self, events: crate::event::EventSink) -> Self {
136 self.events = Some(events);
137 self
138 }
139
140 pub fn with_stdout_broadcast(mut self, tx: tokio::sync::broadcast::Sender<String>) -> Self {
141 self.stdout_broadcast = Some(tx);
142 self
143 }
144
145 pub fn with_session_messages(
146 mut self,
147 msgs: std::sync::Arc<Vec<crate::message::Message>>,
148 ) -> Self {
149 self.session_messages = Some(msgs);
150 self
151 }
152
153 pub fn with_session_messages_handle(
154 mut self,
155 handle: std::sync::Arc<std::sync::Mutex<Vec<crate::message::Message>>>,
156 ) -> Self {
157 self.session_messages_handle = Some(handle);
158 self
159 }
160
161 pub fn with_session_runtime(
162 mut self,
163 session: std::sync::Arc<crate::session::Session>,
164 ) -> Self {
165 self.session_runtime = Some(session);
166 self
167 }
168
169 pub fn with_compact_lock_handle(
170 mut self,
171 handle: std::sync::Arc<tokio::sync::Mutex<()>>,
172 ) -> Self {
173 self.compact_lock_handle = Some(handle);
174 self
175 }
176
177 pub fn with_current_node(mut self, node_id: Option<String>) -> Self {
178 self.current_node_id = node_id;
179 self
180 }
181
182 pub fn with_read_files(
183 mut self,
184 set: std::sync::Arc<std::sync::Mutex<std::collections::HashSet<std::path::PathBuf>>>,
185 ) -> Self {
186 self.read_files = Some(set);
187 self
188 }
189
190 pub fn with_providers(
191 mut self,
192 providers: std::sync::Arc<crate::provider::ProviderRegistry>,
193 ) -> Self {
194 self.providers = Some(providers);
195 self
196 }
197
198 pub fn with_session_dir(mut self, dir: std::path::PathBuf) -> Self {
199 self.session_dir = Some(dir);
200 self
201 }
202
203 pub fn with_data_root(mut self, dir: std::path::PathBuf) -> Self {
204 self.data_root = Some(dir);
205 self
206 }
207
208 pub fn with_approval(
209 mut self,
210 approval: std::sync::Arc<crate::session::ApprovalRegistry>,
211 ) -> Self {
212 self.approval = Some(approval);
213 self
214 }
215
216 pub fn with_fs_access(mut self, policy: crate::fs_access::FsAccessPolicy) -> Self {
217 self.fs_access = policy;
218 self
219 }
220
221 pub fn with_lifecycle_fire_tx(
222 mut self,
223 tx: tokio::sync::mpsc::UnboundedSender<atman_dsl::ast::LifecycleEvent>,
224 ) -> Self {
225 self.lifecycle_fire_tx = Some(tx);
226 self
227 }
228
229 pub fn with_forms(mut self, forms: std::sync::Arc<crate::session::FormRegistry>) -> Self {
230 self.forms = Some(forms);
231 self
232 }
233
234 pub fn with_bg_registry(
235 mut self,
236 registry: std::sync::Arc<crate::tools::bash_bg::BgRegistry>,
237 ) -> Self {
238 self.bg_registry = Some(registry);
239 self
240 }
241
242 pub fn with_session_id(mut self, id: impl Into<String>) -> Self {
243 self.session_id = Some(id.into());
244 self
245 }
246
247 pub fn with_trust(mut self, trust: crate::trust::TrustConfig) -> Self {
248 self.trust = Some(trust);
249 self
250 }
251
252 pub fn with_current_model(mut self, model: impl Into<String>) -> Self {
253 self.current_model = Some(model.into());
254 self
255 }
256
257 pub fn with_term_registry(
258 mut self,
259 registry: std::sync::Arc<crate::tools::term::TermRegistry>,
260 ) -> Self {
261 self.term_registry = Some(registry);
262 self
263 }
264
265 pub fn note_read(&self, path: &std::path::Path) {
266 if let Some(set) = &self.read_files
267 && let Ok(mut lock) = set.lock()
268 {
269 lock.insert(path.to_path_buf());
270 }
271 }
272
273 pub fn has_read(&self, path: &std::path::Path) -> bool {
274 self.read_files
275 .as_ref()
276 .and_then(|set| set.lock().ok().map(|lock| lock.contains(path)))
277 .unwrap_or(false)
278 }
279
280 pub fn with_project_index(mut self, idx: std::sync::Arc<crate::index::AnchorIndex>) -> Self {
281 self.project_index = Some(idx);
282 self
283 }
284
285 pub fn with_stream_tx(
286 mut self,
287 tx: tokio::sync::broadcast::Sender<crate::stream::StreamFrame>,
288 ) -> Self {
289 self.stream_tx = Some(tx);
290 self
291 }
292}
293
294pub trait Tool: Send + Sync {
295 fn name(&self) -> &str;
296 fn tier(&self) -> Tier;
297 fn approval_level(&self, _args: &ToolArgs, _ctx: &ToolCtx) -> ApprovalLevel {
298 ApprovalLevel::from_tier(self.tier())
299 }
300 fn cancel_behavior(&self) -> CancelBehavior {
301 CancelBehavior::AbortSafe
302 }
303 fn description(&self) -> Option<&str> {
304 None
305 }
306 fn input_schema(&self) -> serde_json::Value {
307 serde_json::json!({"type": "object"})
308 }
309 fn call<'a>(&'a self, args: ToolArgs, ctx: &'a ToolCtx) -> BoxFut<'a, ToolResult>;
310 fn preview_call<'a>(
311 &'a self,
312 _args: &'a ToolArgs,
313 _ctx: &'a ToolCtx,
314 ) -> BoxFut<'a, Option<String>> {
315 Box::pin(async { None })
316 }
317}
318
319pub fn tool_spec(tool: &dyn Tool) -> ToolSpec {
320 ToolSpec {
321 name: tool.name().to_string(),
322 description: tool.description().map(str::to_string),
323 input_schema: tool.input_schema(),
324 }
325}
326
327#[derive(Debug, Clone, serde::Serialize)]
328pub struct ToolSpec {
329 pub name: String,
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub description: Option<String>,
332 pub input_schema: serde_json::Value,
333}
334
335#[derive(Default, Clone)]
336pub struct ToolRegistry {
337 tools: std::sync::Arc<std::sync::RwLock<HashMap<String, std::sync::Arc<dyn Tool>>>>,
338}
339
340impl ToolRegistry {
341 pub fn new() -> Self {
342 Self::default()
343 }
344
345 pub fn register(&self, tool: std::sync::Arc<dyn Tool>) {
346 self.tools
347 .write()
348 .unwrap()
349 .insert(tool.name().to_string(), tool);
350 }
351
352 pub fn get(&self, name: &str) -> Option<std::sync::Arc<dyn Tool>> {
353 self.tools.read().unwrap().get(name).cloned()
354 }
355
356 pub fn has(&self, name: &str) -> bool {
357 self.tools.read().unwrap().contains_key(name)
358 }
359
360 pub fn names(&self) -> Vec<String> {
361 self.tools.read().unwrap().keys().cloned().collect()
362 }
363
364 pub fn iter(&self) -> Vec<(String, std::sync::Arc<dyn Tool>)> {
365 self.tools
366 .read()
367 .unwrap()
368 .iter()
369 .map(|(k, v)| (k.clone(), v.clone()))
370 .collect()
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377
378 #[test]
379 fn approval_level_default_maps_from_tier() {
380 assert_eq!(ApprovalLevel::from_tier(Tier::Zero), ApprovalLevel::Auto);
381 assert_eq!(ApprovalLevel::from_tier(Tier::One), ApprovalLevel::Approve);
382 assert_eq!(ApprovalLevel::from_tier(Tier::Two), ApprovalLevel::Approve);
383 assert_eq!(
384 ApprovalLevel::from_tier(Tier::Three),
385 ApprovalLevel::Dangerous
386 );
387 assert_eq!(
388 ApprovalLevel::from_tier(Tier::Four),
389 ApprovalLevel::Dangerous
390 );
391 }
392
393 #[test]
394 fn approval_level_ordered_auto_lt_approve_lt_dangerous() {
395 assert!(ApprovalLevel::Auto < ApprovalLevel::Approve);
396 assert!(ApprovalLevel::Approve < ApprovalLevel::Dangerous);
397 }
398}