bamboo_agent_core/tools/mod.rs
1//! Tool execution system for Bamboo agents.
2//!
3//! This module provides a comprehensive framework for defining, registering, and executing
4//! tools that can be used by AI agents to interact with external systems.
5//!
6//! # Architecture
7//!
8//! The tools system is built around several key components:
9//!
10//! - **accumulator**: Accumulates partial tool calls from streaming responses
11//! - **agentic**: Agentic tool execution with multi-step capabilities
12//! - **executor**: Core tool execution logic
13//! - **registry**: Tool registration and lookup
14//! - **result_handler**: Processes tool results and handles agentic support
15//! - **smart_code_review**: Specialized tool for intelligent code review
16//! - **types**: Core type definitions for tools
17//!
18//! # Key Concepts
19//!
20//! ## Tool Registry
21//!
22//! Tools are registered in a central [`ToolRegistry`] that maps tool names to their
23//! implementations. The registry supports:
24//!
25//! - Dynamic tool registration
26//! - Tool name normalization
27//! - Global singleton access via [`global_registry`]
28//!
29//! ## Tool Execution
30//!
31//! Tools implement the [`ToolExecutor`] trait and can be executed via [`execute_tool_call`].
32//! The execution flow:
33//!
34//! 1. Parse tool arguments from JSON
35//! 2. Execute the tool logic
36//! 3. Return a [`ToolResult`] with success/failure status
37//!
38//! ## Agentic Tools
39//!
40//! Some tools support "agentic" behavior, allowing multi-step execution:
41//!
42//! - [`AgenticTool`]: Marker trait for agentic tools
43//! - [`AgenticContext`]: Context for agentic execution
44//! - [`AgenticToolResult`]: Extended result type with sub-actions
45//!
46//! # Example
47//!
48//! ```rust,ignore
49//! use async_trait::async_trait;
50//! use bamboo_agent::agent::core::tools::{
51//! execute_tool_call, FunctionCall, ToolCall, ToolError, ToolExecutor, ToolResult, ToolSchema,
52//! };
53//!
54//! struct NoopExecutor;
55//!
56//! #[async_trait]
57//! impl ToolExecutor for NoopExecutor {
58//! async fn execute(&self, call: &ToolCall) -> Result<ToolResult, ToolError> {
59//! Err(ToolError::NotFound(call.function.name.clone()))
60//! }
61//!
62//! fn list_tools(&self) -> Vec<ToolSchema> {
63//! Vec::new()
64//! }
65//! }
66//!
67//! #[tokio::main]
68//! async fn main() {
69//! // Execute a tool call (this example uses a no-op executor).
70//! let call = ToolCall {
71//! id: "call-1".to_string(),
72//! tool_type: "function".to_string(),
73//! function: FunctionCall {
74//! name: "read_file".to_string(),
75//! arguments: r#"{\"path\":\"/tmp/test.txt\"}"#.to_string(),
76//! },
77//! };
78//!
79//! let _ = execute_tool_call(&call, &NoopExecutor, None).await;
80//! }
81//! ```
82//!
83//! # Re-exports
84//!
85//! Key types and functions re-exported for convenience:
86//!
87//! - Accumulator: [`ToolCallAccumulator`], [`PartialToolCall`], [`finalize_tool_calls`]
88//! - Agentic: [`AgenticTool`], [`AgenticContext`], [`AgenticToolResult`], [`ToolGoal`]
89//! - Executor: [`ToolExecutor`], [`execute_tool_call`], [`ToolError`]
90//! - Registry: [`ToolRegistry`], [`Tool`], [`global_registry`]
91//! - Types: [`ToolCall`], [`ToolResult`], [`ToolSchema`]
92
93pub mod accumulator;
94pub mod agentic;
95pub mod bash_completion;
96pub mod context;
97pub mod executor;
98pub mod registry;
99pub mod result_handler;
100pub mod smart_code_review;
101pub mod tool_runtime;
102pub mod types;
103
104pub use bash_completion::{BashCompletionInfo, BashCompletionSink};
105pub use tool_runtime::{
106 AsyncToolCompletionInfo, AsyncToolCompletionSink, AsyncWaitKind, RunningCompletion,
107 RunningHandle, ToolClass, ToolCtx, ToolOutcome, ToolResultFuture,
108};
109pub use accumulator::{
110 finalize_tool_calls, update_partial_tool_call, PartialToolCall, ToolCallAccumulator,
111};
112pub use agentic::{
113 convert_from_standard_result, convert_to_standard_result, AgenticContext, AgenticTool,
114 AgenticToolExecutor, AgenticToolResult, Interaction, InteractionRole, ToolGoal,
115};
116pub use context::{ToolExecutionContext, ToolExecutionSessionFlags};
117pub use executor::{execute_tool_call, execute_tool_call_with_context, ToolError, ToolExecutor};
118pub use registry::{
119 global_registry, normalize_tool_name, RegistryError, SharedTool, Tool, ToolRegistry,
120};
121pub use result_handler::{
122 execute_sub_actions, handle_tool_result_with_agentic_support, parse_tool_args,
123 parse_tool_args_best_effort, send_clarification_request, try_parse_agentic_result,
124 ToolHandlingOutcome, MAX_SUB_ACTIONS,
125};
126pub use smart_code_review::SmartCodeReviewTool;
127pub use types::{FunctionCall, FunctionSchema, ToolCall, ToolResult, ToolResultImage, ToolSchema};
128
129/// Classification of a tool call for approval purposes.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum ToolMutability {
132 ReadOnly,
133 Mutating,
134}
135
136/// Read-only tools that don't require user approval.
137const READ_ONLY_TOOLS: &[&str] = &[
138 "Read",
139 "GetFileInfo",
140 "Glob",
141 "Grep",
142 "WebFetch",
143 "WebSearch",
144 "Workspace",
145 "BashOutput",
146 "session_note",
147 "memory_note",
148 "session_history",
149 "recall",
150 "session_inspector",
151 "compact_context",
152 "Sleep",
153 // The goal self-report tool records a status only; the durable goal-state
154 // mutation happens in the engine, and it touches no user-facing state, so it
155 // never needs approval.
156 "update_goal",
157];
158
159/// Classify a tool call as read-only or mutating.
160pub fn classify_tool(tool_name: &str) -> ToolMutability {
161 if READ_ONLY_TOOLS
162 .iter()
163 .any(|&t| t.eq_ignore_ascii_case(tool_name))
164 {
165 ToolMutability::ReadOnly
166 } else {
167 ToolMutability::Mutating
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn classify_compact_context_as_read_only() {
177 assert_eq!(classify_tool("compact_context"), ToolMutability::ReadOnly);
178 }
179
180 #[test]
181 fn classify_compact_context_case_insensitive() {
182 assert_eq!(classify_tool("Compact_Context"), ToolMutability::ReadOnly);
183 assert_eq!(classify_tool("COMPACT_CONTEXT"), ToolMutability::ReadOnly);
184 }
185
186 #[test]
187 fn classify_write_as_mutating() {
188 assert_eq!(classify_tool("Write"), ToolMutability::Mutating);
189 }
190
191 #[test]
192 fn classify_unknown_as_mutating() {
193 assert_eq!(
194 classify_tool("totally_unknown_tool"),
195 ToolMutability::Mutating
196 );
197 }
198
199 #[test]
200 fn classify_all_read_only_tools() {
201 for name in READ_ONLY_TOOLS {
202 assert_eq!(
203 classify_tool(name),
204 ToolMutability::ReadOnly,
205 "{name} should be classified as read-only"
206 );
207 }
208 }
209}