// tools/mod.rs — Tool registry and dispatch // // Tools are the agent's hands. Each tool is a function that takes // JSON arguments and returns a string result. The registry maps // tool names to implementations and generates the JSON schema // definitions that the model needs to know how to call them. // // Design note: dispatch is async to support tools that need it // (bash timeout, future HTTP tools). Sync tools just return // immediately from an async fn. mod bash; mod control; mod edit; mod glob_tool; mod grep; pub mod journal; pub mod memory; mod read; mod vision; mod write; pub mod working_stack; pub use bash::ProcessTracker; use crate::agent::types::ToolDef; /// Result of dispatching a tool call. pub struct ToolOutput { pub text: String, pub is_yield: bool, /// Base64 data URIs for images to attach to the next message. pub images: Vec, /// Model name to switch to (deferred to session level). pub model_switch: Option, /// Agent requested DMN pause (deferred to session level). pub dmn_pause: bool, } impl ToolOutput { fn error(e: impl std::fmt::Display) -> Self { Self { text: format!("Error: {}", e), is_yield: false, images: Vec::new(), model_switch: None, dmn_pause: false, } } fn text(s: String) -> Self { Self { text: s, is_yield: false, images: Vec::new(), model_switch: None, dmn_pause: false, } } } /// Truncate output if it exceeds max length, appending a truncation notice. /// Used by tools that can produce large amounts of output (bash, grep, glob, etc). pub fn truncate_output(mut s: String, max: usize) -> String { if s.len() > max { s.truncate(max); s.push_str("\n... (output truncated)"); } s } /// Dispatch a tool call by name. /// /// Control tools (pause, switch_model, yield_to_user) and view_image /// return Result. Regular tools return Result and /// get wrapped in a text-only ToolOutput. /// /// Note: working_stack is handled in agent.rs before reaching this /// function (it needs mutable context access). pub async fn dispatch( name: &str, args: &serde_json::Value, tracker: &ProcessTracker, ) -> ToolOutput { // Tools that return Result directly let rich_result = match name { "pause" => Some(control::pause(args)), "switch_model" => Some(control::switch_model(args)), "yield_to_user" => Some(control::yield_to_user(args)), "view_image" => Some(vision::view_image(args)), _ => None, }; if let Some(result) = rich_result { return result.unwrap_or_else(ToolOutput::error); } // Regular tools — return Result let result = match name { "read_file" => read::read_file(args), "write_file" => write::write_file(args), "edit_file" => edit::edit_file(args), "bash" => bash::run_bash(args, tracker).await, "grep" => grep::grep(args), "glob" => glob_tool::glob_search(args), "journal" => journal::write_entry(args), // memory_* tools are dispatched in runner.rs for context tracking _ => Err(anyhow::anyhow!("Unknown tool: {}", name)), }; match result { Ok(s) => ToolOutput::text(s), Err(e) => ToolOutput::error(e), } } /// Return tool definitions for the model. pub fn definitions() -> Vec { vec![ read::definition(), write::definition(), edit::definition(), bash::definition(), grep::definition(), glob_tool::definition(), vision::definition(), journal::definition(), working_stack::definition(), ].into_iter() .chain(control::definitions()) .chain(memory::definitions()) .collect() }