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}