claude_agent/tools/
traits.rs

1//! Tool trait definitions.
2
3use async_trait::async_trait;
4use schemars::JsonSchema;
5use serde::de::DeserializeOwned;
6
7use super::context::ExecutionContext;
8use crate::types::{ToolDefinition, ToolResult};
9
10/// Core tool trait for all tool implementations.
11#[async_trait]
12pub trait Tool: Send + Sync {
13    fn name(&self) -> &str;
14    fn description(&self) -> &str;
15    fn input_schema(&self) -> serde_json::Value;
16    async fn execute(&self, input: serde_json::Value, context: &ExecutionContext) -> ToolResult;
17
18    fn definition(&self) -> ToolDefinition {
19        ToolDefinition::new(self.name(), self.description(), self.input_schema())
20    }
21}
22
23/// Schema-based tool trait with automatic JSON schema generation.
24///
25/// Provides a higher-level abstraction over `Tool` with typed inputs
26/// and automatic schema derivation via schemars.
27#[async_trait]
28pub trait SchemaTool: Send + Sync {
29    type Input: JsonSchema + DeserializeOwned + Send;
30    const NAME: &'static str;
31    const DESCRIPTION: &'static str;
32    const STRICT: bool = false;
33
34    async fn handle(&self, input: Self::Input, context: &ExecutionContext) -> ToolResult;
35
36    fn input_schema() -> serde_json::Value {
37        let schema = schemars::schema_for!(Self::Input);
38        let mut value =
39            serde_json::to_value(schema).unwrap_or_else(|_| serde_json::json!({"type": "object"}));
40
41        if let Some(obj) = value.as_object_mut() {
42            if !obj.contains_key("properties") {
43                obj.insert(
44                    "properties".to_string(),
45                    serde_json::Value::Object(serde_json::Map::new()),
46                );
47            }
48            if !obj.contains_key("additionalProperties") {
49                obj.insert(
50                    "additionalProperties".to_string(),
51                    serde_json::Value::Bool(true),
52                );
53            }
54        }
55
56        value
57    }
58}
59
60#[async_trait]
61impl<T: SchemaTool + 'static> Tool for T {
62    fn name(&self) -> &str {
63        T::NAME
64    }
65
66    fn description(&self) -> &str {
67        T::DESCRIPTION
68    }
69
70    fn input_schema(&self) -> serde_json::Value {
71        T::input_schema()
72    }
73
74    fn definition(&self) -> ToolDefinition {
75        let mut definition = ToolDefinition::new(T::NAME, T::DESCRIPTION, T::input_schema());
76        if T::STRICT {
77            definition = definition.with_strict(true);
78        }
79        definition
80    }
81
82    async fn execute(&self, input: serde_json::Value, context: &ExecutionContext) -> ToolResult {
83        match serde_json::from_value::<T::Input>(input) {
84            Ok(typed) => SchemaTool::handle(self, typed, context).await,
85            Err(e) => ToolResult::error(format!("Invalid input: {}", e)),
86        }
87    }
88}