Skip to main content

agentic_tools_core/
tool.rs

1//! Core tool traits for native-first tool definitions.
2
3use crate::context::ToolContext;
4use crate::error::ToolError;
5use futures::future::BoxFuture;
6
7/// Native-first Tool trait with NO serde bounds.
8///
9/// This trait defines a tool that can be called with native Rust types.
10/// Serialization is handled separately by [`ToolCodec`] at protocol boundaries.
11///
12/// # Example
13///
14/// ```ignore
15/// use agentic_tools_core::{Tool, ToolContext, ToolError};
16/// use futures::future::BoxFuture;
17///
18/// struct GreetTool;
19///
20/// impl Tool for GreetTool {
21///     type Input = String;
22///     type Output = String;
23///     const NAME: &'static str = "greet";
24///     const DESCRIPTION: &'static str = "Greet someone by name";
25///
26///     fn call(&self, input: Self::Input, _ctx: &ToolContext)
27///         -> BoxFuture<'static, Result<Self::Output, ToolError>>
28///     {
29///         Box::pin(async move {
30///             Ok(format!("Hello, {}!", input))
31///         })
32///     }
33/// }
34/// ```
35pub trait Tool: Send + Sync + 'static {
36    /// Input type for the tool (no serde bounds required).
37    type Input: Send + 'static;
38
39    /// Output type for the tool (no serde bounds required).
40    type Output: Send + 'static;
41
42    /// Unique name identifying the tool.
43    const NAME: &'static str;
44
45    /// Human-readable description of what the tool does.
46    const DESCRIPTION: &'static str;
47
48    /// Execute the tool with the given input and context.
49    fn call(
50        &self,
51        input: Self::Input,
52        ctx: &ToolContext,
53    ) -> BoxFuture<'static, Result<Self::Output, ToolError>>;
54}
55
56/// Codec for serializing tool inputs/outputs at protocol boundaries.
57///
58/// Serde and schemars bounds reside here, NOT on the [`Tool`] trait.
59/// This keeps native Rust calls zero-serialization while supporting
60/// MCP, napi, and provider schema generation.
61///
62/// # Identity Codec
63///
64/// When `T::Input` and `T::Output` already implement the required serde/schemars
65/// traits, use `()` as the codec (blanket implementation provided).
66pub trait ToolCodec<T: Tool>: Send + Sync + 'static {
67    /// Wire format for input (must be deserializable and have JSON schema).
68    type WireIn: serde::de::DeserializeOwned + schemars::JsonSchema + Send + 'static;
69
70    /// Wire format for output (must be serializable and have JSON schema).
71    type WireOut: serde::Serialize + schemars::JsonSchema + Send + 'static;
72
73    /// Decode wire input to native input.
74    fn decode(wire: Self::WireIn) -> Result<T::Input, ToolError>;
75
76    /// Encode native output to wire output.
77    fn encode(native: T::Output) -> Result<Self::WireOut, ToolError>;
78}
79
80/// Identity codec: when Input/Output already have serde/schemars, use `()` as codec.
81impl<T> ToolCodec<T> for ()
82where
83    T: Tool,
84    T::Input: serde::de::DeserializeOwned + schemars::JsonSchema,
85    T::Output: serde::Serialize + schemars::JsonSchema,
86{
87    type WireIn = T::Input;
88    type WireOut = T::Output;
89
90    fn decode(wire: Self::WireIn) -> Result<T::Input, ToolError> {
91        Ok(wire)
92    }
93
94    fn encode(native: T::Output) -> Result<Self::WireOut, ToolError> {
95        Ok(native)
96    }
97}