pub mod executor;
pub mod registry;
pub mod conversation;
use async_trait::async_trait;
use serde_json::Value;
use std::error::Error;
pub use executor::{ToolExecutor, ToolExecutionConfig, ToolExecutionConfigBuilder};
pub use registry::{ToolRegistry, SharedToolRegistry};
pub use conversation::{ToolConversation, ConversationConfig, ConversationConfigBuilder};
use crate::types::ToolResult;
#[async_trait]
pub trait ToolFunction: Send + Sync {
async fn execute(&self, input: Value) -> Result<ToolResult, Box<dyn Error + Send + Sync>>;
fn validate_input(&self, _input: &Value) -> Result<(), Box<dyn Error + Send + Sync>> {
Ok(())
}
fn timeout_seconds(&self) -> u64 {
30
}
}
pub struct SimpleTool<F>
where
F: Fn(Value) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ToolResult, Box<dyn Error + Send + Sync>>> + Send>> + Send + Sync,
{
function: F,
}
impl<F> SimpleTool<F>
where
F: Fn(Value) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ToolResult, Box<dyn Error + Send + Sync>>> + Send>> + Send + Sync,
{
pub fn new(function: F) -> Self {
Self { function }
}
}
#[async_trait]
impl<F> ToolFunction for SimpleTool<F>
where
F: Fn(Value) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ToolResult, Box<dyn Error + Send + Sync>>> + Send>> + Send + Sync,
{
async fn execute(&self, input: Value) -> Result<ToolResult, Box<dyn Error + Send + Sync>> {
(self.function)(input).await
}
}
#[derive(Debug, thiserror::Error)]
pub enum ToolError {
#[error("Tool '{name}' not found")]
NotFound { name: String },
#[error("Tool validation failed: {message}")]
ValidationFailed { message: String },
#[error("Tool execution failed: {source}")]
ExecutionFailed {
#[source]
source: Box<dyn Error + Send + Sync>
},
#[error("Tool execution timed out after {seconds} seconds")]
Timeout { seconds: u64 },
#[error("Tool registry error: {message}")]
RegistryError { message: String },
}
pub type ToolOperationResult<T> = Result<T, ToolError>;
#[macro_export]
macro_rules! tool_function {
(|$input:ident: Value| $body:expr) => {
$crate::tools::SimpleTool::new(move |$input: Value| {
Box::pin($body)
})
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Tool, ToolResult};
use serde_json::json;
struct TestTool;
#[async_trait]
impl ToolFunction for TestTool {
async fn execute(&self, input: Value) -> Result<ToolResult, Box<dyn Error + Send + Sync>> {
let message = input["message"].as_str().unwrap_or("Hello");
Ok(ToolResult::success("test_id", format!("Echo: {}", message)))
}
}
#[tokio::test]
async fn test_tool_function_execution() {
let tool = TestTool;
let input = json!({"message": "Hello, World!"});
let result = tool.execute(input).await.unwrap();
if let crate::types::ToolResultContent::Text(content) = result.content {
assert_eq!(content, "Echo: Hello, World!");
} else {
panic!("Expected text content");
}
}
#[tokio::test]
async fn test_simple_tool() {
let tool = SimpleTool::new(|input: Value| {
Box::pin(async move {
let number = input["number"].as_f64().unwrap_or(0.0);
let result = number * 2.0;
Ok(ToolResult::success("test_id", format!("Result: {}", result)))
})
});
let input = json!({"number": 21.0});
let result = tool.execute(input).await.unwrap();
if let crate::types::ToolResultContent::Text(content) = result.content {
assert_eq!(content, "Result: 42");
} else {
panic!("Expected text content");
}
}
#[test]
fn test_tool_function_macro() {
let _tool = tool_function!(|input: Value| async move {
let value = input["test"].as_str().unwrap_or("default");
Ok(ToolResult::success("macro_test", format!("Processed: {}", value)))
});
assert!(true);
}
}