use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
#[cfg(feature = "json-schema")]
use schemars::{schema_for, JsonSchema};
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters_schema(&self) -> JsonValue;
fn execute(&self, args: JsonValue) -> Result<JsonValue, Box<dyn Error + Send + Sync>>;
fn validate(&self, _args: &JsonValue) -> Result<(), Box<dyn Error + Send + Sync>> {
Ok(())
}
}
#[cfg(feature = "json-schema")]
pub trait SchemaBasedTool: Send + Sync {
type Params: JsonSchema + for<'de> Deserialize<'de>;
fn name(&self) -> &str;
fn description(&self) -> &str;
fn execute_typed(
&self,
params: Self::Params,
) -> Result<JsonValue, Box<dyn Error + Send + Sync>>;
fn validate_typed(&self, _params: &Self::Params) -> Result<(), Box<dyn Error + Send + Sync>> {
Ok(())
}
}
#[cfg(feature = "json-schema")]
impl<T: SchemaBasedTool> Tool for T {
fn name(&self) -> &str {
SchemaBasedTool::name(self)
}
fn description(&self) -> &str {
SchemaBasedTool::description(self)
}
fn parameters_schema(&self) -> JsonValue {
let schema = schema_for!(T::Params);
serde_json::to_value(&schema).unwrap_or_else(|_| JsonValue::Null)
}
fn execute(&self, args: JsonValue) -> Result<JsonValue, Box<dyn Error + Send + Sync>> {
let params: T::Params = serde_json::from_value(args)
.map_err(|e| format!("Failed to deserialize parameters: {}", e))?;
self.validate_typed(¶ms)?;
self.execute_typed(params)
}
fn validate(&self, args: &JsonValue) -> Result<(), Box<dyn Error + Send + Sync>> {
let params: T::Params = serde_json::from_value(args.clone())
.map_err(|e| format!("Invalid parameters: {}", e))?;
self.validate_typed(¶ms)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub parameters: JsonValue,
#[serde(rename = "type", default = "default_tool_type")]
pub tool_type: String,
}
fn default_tool_type() -> String {
"function".to_string()
}
impl ToolDefinition {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
parameters: JsonValue,
) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
tool_type: "function".to_string(),
}
}
pub fn from_tool(tool: &dyn Tool) -> Self {
Self::new(tool.name(), tool.description(), tool.parameters_schema())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: JsonValue,
}
impl ToolCall {
pub fn new(id: impl Into<String>, name: impl Into<String>, arguments: JsonValue) -> Self {
Self {
id: id.into(),
name: name.into(),
arguments,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
pub tool_call_id: String,
pub tool_name: String,
pub content: JsonValue,
pub success: bool,
pub error: Option<String>,
}
impl ToolResult {
pub fn success(
tool_call_id: impl Into<String>,
tool_name: impl Into<String>,
content: JsonValue,
) -> Self {
Self {
tool_call_id: tool_call_id.into(),
tool_name: tool_name.into(),
content,
success: true,
error: None,
}
}
pub fn error(
tool_call_id: impl Into<String>,
tool_name: impl Into<String>,
error: impl Into<String>,
) -> Self {
Self {
tool_call_id: tool_call_id.into(),
tool_name: tool_name.into(),
content: JsonValue::Null,
success: false,
error: Some(error.into()),
}
}
}
pub struct ToolRegistry {
tools: HashMap<String, Box<dyn Tool>>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
}
}
pub fn register(&mut self, tool: Box<dyn Tool>) -> Result<(), ToolRegistryError> {
let name = tool.name().to_string();
if self.tools.contains_key(&name) {
return Err(ToolRegistryError::DuplicateTool(name));
}
self.tools.insert(name, tool);
Ok(())
}
pub fn get(&self, name: &str) -> Option<&dyn Tool> {
self.tools.get(name).map(|b| b.as_ref())
}
pub fn contains(&self, name: &str) -> bool {
self.tools.contains_key(name)
}
pub fn tool_names(&self) -> Vec<&str> {
self.tools.keys().map(|s| s.as_str()).collect()
}
pub fn tool_definitions(&self) -> Vec<ToolDefinition> {
self.tools
.values()
.map(|tool| ToolDefinition::from_tool(tool.as_ref()))
.collect()
}
pub fn execute(&self, tool_call: &ToolCall) -> ToolResult {
match self.get(&tool_call.name) {
Some(tool) => {
if let Err(e) = tool.validate(&tool_call.arguments) {
return ToolResult::error(
&tool_call.id,
&tool_call.name,
format!("Validation failed: {}", e),
);
}
match tool.execute(tool_call.arguments.clone()) {
Ok(result) => ToolResult::success(&tool_call.id, &tool_call.name, result),
Err(e) => ToolResult::error(&tool_call.id, &tool_call.name, e.to_string()),
}
}
None => ToolResult::error(
&tool_call.id,
&tool_call.name,
format!("Tool '{}' not found", tool_call.name),
),
}
}
pub fn execute_batch(&self, tool_calls: &[ToolCall]) -> Vec<ToolResult> {
tool_calls.iter().map(|tc| self.execute(tc)).collect()
}
pub fn len(&self) -> usize {
self.tools.len()
}
pub fn is_empty(&self) -> bool {
self.tools.is_empty()
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for ToolRegistry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ToolRegistry")
.field("tools", &self.tool_names())
.finish()
}
}
#[derive(Debug, Clone)]
pub enum ToolRegistryError {
DuplicateTool(String),
}
impl fmt::Display for ToolRegistryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ToolRegistryError::DuplicateTool(name) => {
write!(f, "Tool '{}' is already registered", name)
}
}
}
}
impl Error for ToolRegistryError {}
#[macro_export]
macro_rules! simple_tool {
(
name: $name:expr,
description: $desc:expr,
parameters: $params:expr,
execute: |$args:ident| $body:expr
) => {{
struct SimpleTool;
impl $crate::tools::Tool for SimpleTool {
fn name(&self) -> &str {
$name
}
fn description(&self) -> &str {
$desc
}
fn parameters_schema(&self) -> serde_json::Value {
$params
}
fn execute(
&self,
$args: serde_json::Value,
) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
Ok($body)
}
}
Box::new(SimpleTool)
}};
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
struct TestTool;
impl Tool for TestTool {
fn name(&self) -> &str {
"test_tool"
}
fn description(&self) -> &str {
"A test tool"
}
fn parameters_schema(&self) -> JsonValue {
json!({"type": "object", "properties": {"input": {"type": "string"}}})
}
fn execute(&self, args: JsonValue) -> Result<JsonValue, Box<dyn Error + Send + Sync>> {
Ok(json!({"result": format!("Processed: {}", args["input"])}))
}
}
#[test]
fn test_tool_registry() {
let mut registry = ToolRegistry::new();
assert_eq!(registry.len(), 0);
registry.register(Box::new(TestTool)).unwrap();
assert_eq!(registry.len(), 1);
assert!(registry.contains("test_tool"));
}
#[test]
fn test_tool_execution() {
let mut registry = ToolRegistry::new();
registry.register(Box::new(TestTool)).unwrap();
let call = ToolCall::new("call-1", "test_tool", json!({"input": "hello"}));
let result = registry.execute(&call);
assert!(result.success);
assert_eq!(result.tool_name, "test_tool");
}
#[test]
fn test_simple_tool_macro() {
let tool = simple_tool!(
name: "echo",
description: "Echoes input",
parameters: json!({"type": "object", "properties": {"text": {"type": "string"}}}),
execute: |args| {
json!({"echo": args["text"]})
}
);
assert_eq!(tool.name(), "echo");
let result = tool.execute(json!({"text": "hello"})).unwrap();
assert_eq!(result["echo"], "hello");
}
}