agentik_sdk/tools/mod.rs
1//! Tool execution and management for the Anthropic SDK.
2//!
3//! This module provides the infrastructure for registering, validating, and executing
4//! tools (functions) that Claude can call during conversations.
5//!
6//! # Examples
7//!
8//! ## Basic Tool Registration and Execution
9//!
10//! ```rust
11//! use agentik_sdk::tools::{ToolRegistry, ToolFunction};
12//! use agentik_sdk::types::{Tool, ToolUse, ToolResult};
13//! use serde_json::{json, Value};
14//! use async_trait::async_trait;
15//!
16//! // Define a tool function
17//! struct WeatherTool;
18//!
19//! #[async_trait]
20//! impl ToolFunction for WeatherTool {
21//! async fn execute(&self, input: Value) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
22//! let location = input["location"].as_str().unwrap_or("Unknown");
23//! let weather_data = format!("The weather in {} is 72°F and sunny", location);
24//! Ok(ToolResult::success("tool_use_id", weather_data))
25//! }
26//! }
27//!
28//! // Register and use the tool
29//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
30//! let mut registry = ToolRegistry::new();
31//!
32//! let weather_tool_def = Tool::new("get_weather", "Get the current weather in a given location")
33//! .parameter("location", "string", "The city and state")
34//! .required("location")
35//! .build();
36//!
37//! registry.register("get_weather", weather_tool_def, Box::new(WeatherTool))?;
38//!
39//! // Execute a tool call
40//! let tool_use = ToolUse {
41//! id: "toolu_123".to_string(),
42//! name: "get_weather".to_string(),
43//! input: json!({"location": "San Francisco, CA"}),
44//! };
45//!
46//! let result = registry.execute(&tool_use).await?;
47//! # Ok(())
48//! # }
49//! ```
50
51pub mod executor;
52pub mod registry;
53pub mod conversation;
54
55use async_trait::async_trait;
56use serde_json::Value;
57use std::error::Error;
58
59pub use executor::{ToolExecutor, ToolExecutionConfig, ToolExecutionConfigBuilder};
60pub use registry::{ToolRegistry, SharedToolRegistry};
61pub use conversation::{ToolConversation, ConversationConfig, ConversationConfigBuilder};
62
63use crate::types::ToolResult;
64
65/// Trait for implementing tool functions.
66///
67/// This trait allows you to define custom tools that Claude can call.
68/// Each tool receives structured input and returns a structured result.
69#[async_trait]
70pub trait ToolFunction: Send + Sync {
71 /// Execute the tool with the given input.
72 ///
73 /// # Arguments
74 /// * `input` - JSON input parameters from Claude
75 ///
76 /// # Returns
77 /// A `ToolResult` containing the execution result, or an error if execution fails.
78 async fn execute(&self, input: Value) -> Result<ToolResult, Box<dyn Error + Send + Sync>>;
79
80 /// Validate input before execution (optional).
81 ///
82 /// Override this method to provide custom validation logic beyond
83 /// the JSON schema validation.
84 fn validate_input(&self, _input: &Value) -> Result<(), Box<dyn Error + Send + Sync>> {
85 Ok(())
86 }
87
88 /// Get the tool's timeout in seconds (optional).
89 ///
90 /// Override to set a custom timeout for this tool.
91 /// Default is 30 seconds.
92 fn timeout_seconds(&self) -> u64 {
93 30
94 }
95}
96
97/// Simple function wrapper for tool functions.
98///
99/// This allows you to register simple async functions as tools without
100/// implementing the full `ToolFunction` trait.
101pub struct SimpleTool<F>
102where
103 F: Fn(Value) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ToolResult, Box<dyn Error + Send + Sync>>> + Send>> + Send + Sync,
104{
105 function: F,
106}
107
108impl<F> SimpleTool<F>
109where
110 F: Fn(Value) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ToolResult, Box<dyn Error + Send + Sync>>> + Send>> + Send + Sync,
111{
112 /// Create a new simple tool from a function.
113 pub fn new(function: F) -> Self {
114 Self { function }
115 }
116}
117
118#[async_trait]
119impl<F> ToolFunction for SimpleTool<F>
120where
121 F: Fn(Value) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ToolResult, Box<dyn Error + Send + Sync>>> + Send>> + Send + Sync,
122{
123 async fn execute(&self, input: Value) -> Result<ToolResult, Box<dyn Error + Send + Sync>> {
124 (self.function)(input).await
125 }
126}
127
128/// Tool execution errors.
129#[derive(Debug, thiserror::Error)]
130pub enum ToolError {
131 /// Tool not found in registry.
132 #[error("Tool '{name}' not found")]
133 NotFound { name: String },
134
135 /// Tool validation failed.
136 #[error("Tool validation failed: {message}")]
137 ValidationFailed { message: String },
138
139 /// Tool execution failed.
140 #[error("Tool execution failed: {source}")]
141 ExecutionFailed {
142 #[source]
143 source: Box<dyn Error + Send + Sync>
144 },
145
146 /// Tool execution timed out.
147 #[error("Tool execution timed out after {seconds} seconds")]
148 Timeout { seconds: u64 },
149
150 /// Tool registry error.
151 #[error("Tool registry error: {message}")]
152 RegistryError { message: String },
153}
154
155/// Result type for tool operations.
156pub type ToolOperationResult<T> = Result<T, ToolError>;
157
158/// Helper macro for creating simple tool functions.
159///
160/// # Example
161///
162/// ```rust
163/// use agentik_sdk::tool_function;
164/// use serde_json::{json, Value};
165///
166/// let weather_tool = tool_function!(|input: Value| async move {
167/// let location = input["location"].as_str().unwrap_or("Unknown");
168/// let result = format!("Weather in {}: 72°F and sunny", location);
169/// Ok(agentik_sdk::ToolResult::success("tool_id", result))
170/// });
171/// ```
172#[macro_export]
173macro_rules! tool_function {
174 (|$input:ident: Value| $body:expr) => {
175 $crate::tools::SimpleTool::new(move |$input: Value| {
176 Box::pin($body)
177 })
178 };
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::types::{Tool, ToolResult};
185 use serde_json::json;
186
187 struct TestTool;
188
189 #[async_trait]
190 impl ToolFunction for TestTool {
191 async fn execute(&self, input: Value) -> Result<ToolResult, Box<dyn Error + Send + Sync>> {
192 let message = input["message"].as_str().unwrap_or("Hello");
193 Ok(ToolResult::success("test_id", format!("Echo: {}", message)))
194 }
195 }
196
197 #[tokio::test]
198 async fn test_tool_function_execution() {
199 let tool = TestTool;
200 let input = json!({"message": "Hello, World!"});
201
202 let result = tool.execute(input).await.unwrap();
203
204 if let crate::types::ToolResultContent::Text(content) = result.content {
205 assert_eq!(content, "Echo: Hello, World!");
206 } else {
207 panic!("Expected text content");
208 }
209 }
210
211 #[tokio::test]
212 async fn test_simple_tool() {
213 let tool = SimpleTool::new(|input: Value| {
214 Box::pin(async move {
215 let number = input["number"].as_f64().unwrap_or(0.0);
216 let result = number * 2.0;
217 Ok(ToolResult::success("test_id", format!("Result: {}", result)))
218 })
219 });
220
221 let input = json!({"number": 21.0});
222 let result = tool.execute(input).await.unwrap();
223
224 if let crate::types::ToolResultContent::Text(content) = result.content {
225 assert_eq!(content, "Result: 42");
226 } else {
227 panic!("Expected text content");
228 }
229 }
230
231 #[test]
232 fn test_tool_function_macro() {
233 let _tool = tool_function!(|input: Value| async move {
234 let value = input["test"].as_str().unwrap_or("default");
235 Ok(ToolResult::success("macro_test", format!("Processed: {}", value)))
236 });
237
238 // Test compilation of the macro
239 assert!(true);
240 }
241}