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, Copy, Debug, Default, PartialEq, Eq)]
71pub enum HistorySegment {
72 #[default]
73 Root,
74 Spawned,
75}
76
77#[derive(Clone, Default)]
78pub struct ToolCtx {
79 pub cancel: CancellationToken,
80 pub turn_id: Option<crate::event::TurnId>,
81 pub flow_run_id: Option<crate::event::FlowRunId>,
82 pub history_segment: HistorySegment,
83 pub event_seq: Option<u64>,
84 pub prompt_resolver: Option<std::sync::Arc<dyn crate::rendezvous::PromptResolver>>,
85 pub registry: Option<std::sync::Arc<ToolRegistry>>,
86 pub sandbox: Option<std::sync::Arc<dyn crate::sandbox::Sandbox>>,
87 pub events: Option<crate::event::EventSink>,
88 pub stdout_broadcast: Option<tokio::sync::broadcast::Sender<String>>,
89 pub session_messages: Option<std::sync::Arc<Vec<crate::message::Message>>>,
90 pub session_messages_handle:
91 Option<std::sync::Arc<std::sync::Mutex<Vec<crate::message::Message>>>>,
92 pub session_runtime: Option<std::sync::Arc<crate::session::Session>>,
93 pub compact_lock_handle: Option<std::sync::Arc<tokio::sync::Mutex<()>>>,
94 pub current_node_id: Option<String>,
95 pub stream_tx: Option<tokio::sync::broadcast::Sender<crate::stream::StreamFrame>>,
96 pub read_files:
97 Option<std::sync::Arc<std::sync::Mutex<std::collections::HashSet<std::path::PathBuf>>>>,
98 pub approval: Option<std::sync::Arc<crate::session::ApprovalRegistry>>,
99 pub forms: Option<std::sync::Arc<crate::session::FormRegistry>>,
100 pub providers: Option<std::sync::Arc<crate::provider::ProviderRegistry>>,
101 pub session_dir: Option<std::path::PathBuf>,
102 pub output_store: Option<std::sync::Arc<crate::tools::tool_output::OutputStore>>,
103 pub data_root: Option<std::path::PathBuf>,
104 pub project_index: Option<std::sync::Arc<crate::index::AnchorIndex>>,
105 pub fs_access: crate::fs_access::FsAccessPolicy,
106 pub lifecycle_fire_tx:
107 Option<tokio::sync::mpsc::UnboundedSender<atman_dsl::ast::LifecycleEvent>>,
108 pub bg_registry: Option<std::sync::Arc<crate::tools::bash_bg::BgRegistry>>,
109 pub term_registry: Option<std::sync::Arc<crate::tools::term::TermRegistry>>,
110 pub watch_hub: Option<std::sync::Arc<crate::watch::WatchHub>>,
111 pub flow_registry: Option<std::sync::Arc<crate::tools::agent_ctrl::FlowRegistry>>,
112 pub task_registry: Option<crate::task_registry::TaskRegistry>,
113 pub session_id: Option<String>,
114 pub trust: Option<crate::trust::TrustConfig>,
115 pub safety: Option<crate::safety::SafetyConfig>,
116 pub current_model: Option<String>,
117 pub watch_rules: Option<crate::streaming::WatchRules>,
118 pub on_memory_recent: Option<std::sync::Arc<dyn Fn(u16) + Send + Sync>>,
120 pub history_store: Option<std::sync::Arc<dyn crate::history_store::HistoryStore>>,
121 pub agent_entry: Option<std::sync::Arc<crate::tools::agent_ctrl::FlowEntry>>,
122 pub tool_output_budget: crate::tools::tool_output::ToolOutputBudget,
123}
124
125impl ToolCtx {
126 pub fn new() -> Self {
127 Self::default()
128 }
129
130 pub fn with_anchors(
131 mut self,
132 turn_id: Option<crate::event::TurnId>,
133 flow_run_id: Option<crate::event::FlowRunId>,
134 event_seq: Option<u64>,
135 ) -> Self {
136 self.turn_id = turn_id;
137 self.flow_run_id = flow_run_id;
138 self.event_seq = event_seq;
139 self
140 }
141
142 pub fn with_history_segment(mut self, segment: HistorySegment) -> Self {
143 self.history_segment = segment;
144 self
145 }
146
147 pub fn message_flow_run_id(&self) -> Option<crate::event::FlowRunId> {
148 match self.history_segment {
149 HistorySegment::Root => None,
150 HistorySegment::Spawned => self.flow_run_id.clone(),
151 }
152 }
153
154 pub fn with_registry(mut self, registry: std::sync::Arc<ToolRegistry>) -> Self {
155 self.registry = Some(registry);
156 self
157 }
158
159 pub fn with_sandbox(mut self, sandbox: std::sync::Arc<dyn crate::sandbox::Sandbox>) -> Self {
160 self.sandbox = Some(sandbox);
161 self
162 }
163
164 pub fn with_events(mut self, events: crate::event::EventSink) -> Self {
165 self.events = Some(events);
166 self
167 }
168
169 pub fn with_stdout_broadcast(mut self, tx: tokio::sync::broadcast::Sender<String>) -> Self {
170 self.stdout_broadcast = Some(tx);
171 self
172 }
173
174 pub fn with_session_messages(
175 mut self,
176 msgs: std::sync::Arc<Vec<crate::message::Message>>,
177 ) -> Self {
178 self.session_messages = Some(msgs);
179 self
180 }
181
182 pub fn with_session_messages_handle(
183 mut self,
184 handle: std::sync::Arc<std::sync::Mutex<Vec<crate::message::Message>>>,
185 ) -> Self {
186 self.session_messages_handle = Some(handle);
187 self
188 }
189
190 pub fn with_session_runtime(
191 mut self,
192 session: std::sync::Arc<crate::session::Session>,
193 ) -> Self {
194 self.session_runtime = Some(session);
195 self
196 }
197
198 pub fn with_compact_lock_handle(
199 mut self,
200 handle: std::sync::Arc<tokio::sync::Mutex<()>>,
201 ) -> Self {
202 self.compact_lock_handle = Some(handle);
203 self
204 }
205
206 pub fn with_current_node(mut self, node_id: Option<String>) -> Self {
207 self.current_node_id = node_id;
208 self
209 }
210
211 pub fn with_read_files(
212 mut self,
213 set: std::sync::Arc<std::sync::Mutex<std::collections::HashSet<std::path::PathBuf>>>,
214 ) -> Self {
215 self.read_files = Some(set);
216 self
217 }
218
219 pub fn with_providers(
220 mut self,
221 providers: std::sync::Arc<crate::provider::ProviderRegistry>,
222 ) -> Self {
223 self.providers = Some(providers);
224 self
225 }
226
227 pub fn with_session_dir(mut self, dir: std::path::PathBuf) -> Self {
228 self.output_store = Some(std::sync::Arc::new(
229 crate::tools::tool_output::OutputStore::at(dir.clone()),
230 ));
231 self.session_dir = Some(dir);
232 self
233 }
234
235 pub fn with_output_store(
236 mut self,
237 store: std::sync::Arc<crate::tools::tool_output::OutputStore>,
238 ) -> Self {
239 self.output_store = Some(store);
240 self
241 }
242
243 pub fn with_data_root(mut self, dir: std::path::PathBuf) -> Self {
244 self.data_root = Some(dir);
245 self
246 }
247
248 pub fn with_approval(
249 mut self,
250 approval: std::sync::Arc<crate::session::ApprovalRegistry>,
251 ) -> Self {
252 self.approval = Some(approval);
253 self
254 }
255
256 pub fn with_fs_access(mut self, policy: crate::fs_access::FsAccessPolicy) -> Self {
257 self.fs_access = policy;
258 self
259 }
260
261 pub fn with_lifecycle_fire_tx(
262 mut self,
263 tx: tokio::sync::mpsc::UnboundedSender<atman_dsl::ast::LifecycleEvent>,
264 ) -> Self {
265 self.lifecycle_fire_tx = Some(tx);
266 self
267 }
268
269 pub fn with_forms(mut self, forms: std::sync::Arc<crate::session::FormRegistry>) -> Self {
270 self.forms = Some(forms);
271 self
272 }
273
274 pub fn with_bg_registry(
275 mut self,
276 registry: std::sync::Arc<crate::tools::bash_bg::BgRegistry>,
277 ) -> Self {
278 self.bg_registry = Some(registry);
279 self
280 }
281
282 pub fn with_session_id(mut self, id: impl Into<String>) -> Self {
283 self.session_id = Some(id.into());
284 self
285 }
286
287 pub fn with_trust(mut self, trust: crate::trust::TrustConfig) -> Self {
288 self.trust = Some(trust);
289 self
290 }
291
292 pub fn with_safety(mut self, safety: crate::safety::SafetyConfig) -> Self {
293 self.safety = Some(safety);
294 self
295 }
296
297 pub fn with_current_model(mut self, model: impl Into<String>) -> Self {
298 self.current_model = Some(model.into());
299 self
300 }
301
302 pub fn with_watch_rules(mut self, rules: crate::streaming::WatchRules) -> Self {
303 self.watch_rules = Some(rules);
304 self
305 }
306
307 pub fn with_term_registry(
308 mut self,
309 registry: std::sync::Arc<crate::tools::term::TermRegistry>,
310 ) -> Self {
311 self.term_registry = Some(registry);
312 self
313 }
314
315 pub fn with_watch_hub(mut self, hub: std::sync::Arc<crate::watch::WatchHub>) -> Self {
316 self.watch_hub = Some(hub);
317 self
318 }
319
320 pub fn with_flow_registry(
321 mut self,
322 registry: std::sync::Arc<crate::tools::agent_ctrl::FlowRegistry>,
323 ) -> Self {
324 self.flow_registry = Some(registry);
325 self
326 }
327
328 pub fn with_task_registry(mut self, registry: crate::task_registry::TaskRegistry) -> Self {
329 self.task_registry = Some(registry);
330 self
331 }
332
333 pub fn note_read(&self, path: &std::path::Path) {
334 if let Some(set) = &self.read_files
335 && let Ok(mut lock) = set.lock()
336 {
337 lock.insert(path.to_path_buf());
338 }
339 }
340
341 pub fn has_read(&self, path: &std::path::Path) -> bool {
342 self.read_files
343 .as_ref()
344 .and_then(|set| set.lock().ok().map(|lock| lock.contains(path)))
345 .unwrap_or(false)
346 }
347
348 pub fn with_project_index(mut self, idx: std::sync::Arc<crate::index::AnchorIndex>) -> Self {
349 self.project_index = Some(idx);
350 self
351 }
352
353 pub fn with_history_store(
354 mut self,
355 store: std::sync::Arc<dyn crate::history_store::HistoryStore>,
356 ) -> Self {
357 self.history_store = Some(store);
358 self
359 }
360
361 pub fn with_agent_entry(
362 mut self,
363 entry: std::sync::Arc<crate::tools::agent_ctrl::FlowEntry>,
364 ) -> Self {
365 self.agent_entry = Some(entry);
366 self
367 }
368
369 pub fn with_stream_tx(
370 mut self,
371 tx: tokio::sync::broadcast::Sender<crate::stream::StreamFrame>,
372 ) -> Self {
373 self.stream_tx = Some(tx);
374 self
375 }
376}
377
378pub trait Tool: Send + Sync {
379 fn name(&self) -> &str;
380 fn tier(&self) -> Tier;
381 fn approval_level(&self, _args: &ToolArgs, _ctx: &ToolCtx) -> ApprovalLevel {
382 ApprovalLevel::from_tier(self.tier())
383 }
384 fn cancel_behavior(&self) -> CancelBehavior {
385 CancelBehavior::AbortSafe
386 }
387 fn description(&self) -> Option<&str> {
388 None
389 }
390 fn input_schema(&self) -> serde_json::Value {
391 serde_json::json!({"type": "object"})
392 }
393 fn call<'a>(&'a self, args: ToolArgs, ctx: &'a ToolCtx) -> BoxFut<'a, ToolResult>;
394 fn preview_call<'a>(
395 &'a self,
396 _args: &'a ToolArgs,
397 _ctx: &'a ToolCtx,
398 ) -> BoxFut<'a, Option<String>> {
399 Box::pin(async { None })
400 }
401}
402
403pub fn tool_spec(tool: &dyn Tool) -> ToolSpec {
404 ToolSpec {
405 name: tool.name().to_string(),
406 description: tool.description().map(str::to_string),
407 input_schema: tool.input_schema(),
408 }
409}
410
411#[derive(Debug, Clone, serde::Serialize)]
412pub struct ToolSpec {
413 pub name: String,
414 #[serde(skip_serializing_if = "Option::is_none")]
415 pub description: Option<String>,
416 pub input_schema: serde_json::Value,
417}
418
419#[derive(Default, Clone)]
420pub struct ToolRegistry {
421 tools: std::sync::Arc<std::sync::RwLock<HashMap<String, std::sync::Arc<dyn Tool>>>>,
422}
423
424impl ToolRegistry {
425 pub fn new() -> Self {
426 Self::default()
427 }
428
429 pub fn register(&self, tool: std::sync::Arc<dyn Tool>) {
430 self.tools
431 .write()
432 .unwrap()
433 .insert(tool.name().to_string(), tool);
434 }
435
436 pub fn get(&self, name: &str) -> Option<std::sync::Arc<dyn Tool>> {
437 self.tools.read().unwrap().get(name).cloned()
438 }
439
440 pub fn has(&self, name: &str) -> bool {
441 self.tools.read().unwrap().contains_key(name)
442 }
443
444 pub fn names(&self) -> Vec<String> {
445 self.tools.read().unwrap().keys().cloned().collect()
446 }
447
448 pub fn iter(&self) -> Vec<(String, std::sync::Arc<dyn Tool>)> {
449 self.tools
450 .read()
451 .unwrap()
452 .iter()
453 .map(|(k, v)| (k.clone(), v.clone()))
454 .collect()
455 }
456
457 pub fn unregister_prefix(&self, prefix: &str) {
459 self.tools
460 .write()
461 .unwrap()
462 .retain(|k, _| !k.starts_with(prefix));
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469
470 #[test]
471 fn approval_level_default_maps_from_tier() {
472 assert_eq!(ApprovalLevel::from_tier(Tier::Zero), ApprovalLevel::Auto);
473 assert_eq!(ApprovalLevel::from_tier(Tier::One), ApprovalLevel::Approve);
474 assert_eq!(ApprovalLevel::from_tier(Tier::Two), ApprovalLevel::Approve);
475 assert_eq!(
476 ApprovalLevel::from_tier(Tier::Three),
477 ApprovalLevel::Dangerous
478 );
479 assert_eq!(
480 ApprovalLevel::from_tier(Tier::Four),
481 ApprovalLevel::Dangerous
482 );
483 }
484
485 #[test]
486 fn approval_level_ordered_auto_lt_approve_lt_dangerous() {
487 assert!(ApprovalLevel::Auto < ApprovalLevel::Approve);
488 assert!(ApprovalLevel::Approve < ApprovalLevel::Dangerous);
489 }
490}