Skip to main content

tools_kit/
handler.rs

1use std::{future::Future, pin::Pin};
2
3use crate::{
4    ToolError,
5    execution::{ToolHandlerInput, ToolHandlerOutput},
6    sources::{local, mcp},
7};
8
9/// Public implementation contract for one unique resumable tool handler.
10/// One handler owns one invocation. Start with `Invoke`, resume only the current
11/// interaction with its exact id, and use `Cancel` for cancellation. A completed
12/// result is terminal; a model-visible failure is a completed result with `is_error`.
13/// Import [`ToolHandlerBackend`] to call behavior on the supplied handle.
14///
15/// # Examples
16///
17/// ```
18///
19/// # #[tokio::main(flavor = "current_thread")]
20/// # async fn main() -> Result<(), tools_kit::ToolError> {
21/// use tools_kit::{declaration::{ToolInfo, ToolName}, schema::JsonSchema};
22/// let info = ToolInfo::new(ToolName::new("ping")?, JsonSchema::strict(serde_json::json!({"type":"object"})));
23/// use tools_kit::{local::ToolRegistration, execution::ToolResult};
24/// let registration = ToolRegistration::function(info, |_| async { Ok(ToolResult::text("pong")) });
25/// use tools_kit::{Toolset, ToolsetBackend, ToolHandlerBackend, execution::{ToolHandlerInput, ToolHandlerOutput, ToolInvocation}};
26/// let tools = Toolset::local(vec![registration])?;
27/// let mut handler = tools.tool_handler(ToolName::new("ping")?).await?;
28/// let result = handler.step(ToolHandlerInput::Invoke(ToolInvocation { call_id: None, arguments: serde_json::json!({}) })).await;
29/// let cleanup = tools.shutdown().await;
30/// assert!(matches!(result?, ToolHandlerOutput::Completed(_)));
31/// cleanup?;
32///
33/// # Ok(())
34/// # }
35/// ```
36///
37pub trait ToolHandlerBackend: Send + 'static {
38    /// Advances the unique handler with an invocation, interaction response,
39    /// or cancellation request.
40    ///
41    /// # Errors
42    ///
43    /// Returns [`ToolError`] for invalid transitions, arguments or interaction replies, unsupported behavior, cancellation, closure, transport or protocol failure. An accepted tool-authored failure is returned as a completed error result.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    ///
49    /// # #[tokio::main(flavor = "current_thread")]
50    /// # async fn main() -> Result<(), tools_kit::ToolError> {
51    /// use tools_kit::{declaration::{ToolInfo, ToolName}, schema::JsonSchema};
52    /// let info = ToolInfo::new(ToolName::new("ping")?, JsonSchema::strict(serde_json::json!({"type":"object"})));
53    /// use tools_kit::{local::ToolRegistration, execution::ToolResult};
54    /// let registration = ToolRegistration::function(info, |_| async { Ok(ToolResult::text("pong")) });
55    /// use tools_kit::{Toolset, ToolsetBackend, ToolHandlerBackend, execution::{ToolHandlerInput, ToolHandlerOutput, ToolInvocation}};
56    /// let tools = Toolset::local(vec![registration])?;
57    /// let mut handler = tools.tool_handler(ToolName::new("ping")?).await?;
58    /// let result = handler.step(ToolHandlerInput::Invoke(ToolInvocation { call_id: None, arguments: serde_json::json!({}) })).await;
59    /// let cleanup = tools.shutdown().await;
60    /// assert!(matches!(result?, ToolHandlerOutput::Completed(_)));
61    /// cleanup?;
62    ///
63    /// # Ok(())
64    /// # }
65    /// ```
66    ///
67    fn step(
68        &mut self,
69        input: ToolHandlerInput,
70    ) -> impl Future<Output = Result<ToolHandlerOutput, ToolError>> + Send + '_;
71}
72
73/// Opaque resumable handler for one selected executable tool.
74/// One handler owns one invocation. Start with `Invoke`, resume only the current
75/// interaction with its exact id, and use `Cancel` for cancellation. A completed
76/// result is terminal; a model-visible failure is a completed result with `is_error`.
77/// Import [`ToolHandlerBackend`] to call behavior on the supplied handle.
78///
79/// # Examples
80///
81/// ```
82///
83/// # #[tokio::main(flavor = "current_thread")]
84/// # async fn main() -> Result<(), tools_kit::ToolError> {
85/// use tools_kit::{declaration::{ToolInfo, ToolName}, schema::JsonSchema};
86/// let info = ToolInfo::new(ToolName::new("ping")?, JsonSchema::strict(serde_json::json!({"type":"object"})));
87/// use tools_kit::{local::ToolRegistration, execution::ToolResult};
88/// let registration = ToolRegistration::function(info, |_| async { Ok(ToolResult::text("pong")) });
89/// use tools_kit::{Toolset, ToolsetBackend, ToolHandlerBackend, execution::{ToolHandlerInput, ToolHandlerOutput, ToolInvocation}};
90/// let tools = Toolset::local(vec![registration])?;
91/// let mut handler = tools.tool_handler(ToolName::new("ping")?).await?;
92/// let result = handler.step(ToolHandlerInput::Invoke(ToolInvocation { call_id: None, arguments: serde_json::json!({}) })).await;
93/// let cleanup = tools.shutdown().await;
94/// assert!(matches!(result?, ToolHandlerOutput::Completed(_)));
95/// cleanup?;
96///
97/// # Ok(())
98/// # }
99/// ```
100///
101pub struct ToolHandler {
102    inner: InnerToolHandler,
103}
104
105enum InnerToolHandler {
106    Local(local::handler::LocalToolHandler),
107    Mcp(mcp::handler::McpToolHandler),
108    Extension(Box<dyn ErasedToolHandlerBackend>),
109}
110
111impl ToolHandler {
112    pub(crate) fn from_local(handler: local::handler::LocalToolHandler) -> Self {
113        Self {
114            inner: InnerToolHandler::Local(handler),
115        }
116    }
117
118    pub(crate) fn from_mcp(handler: mcp::handler::McpToolHandler) -> Self {
119        Self {
120            inner: InnerToolHandler::Mcp(handler),
121        }
122    }
123
124    pub(crate) fn from_extension<B: ToolHandlerBackend>(backend: B) -> Self {
125        Self {
126            inner: InnerToolHandler::Extension(Box::new(backend)),
127        }
128    }
129}
130
131impl std::fmt::Debug for ToolHandler {
132    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        formatter
134            .debug_struct("ToolHandler")
135            .finish_non_exhaustive()
136    }
137}
138
139trait ErasedToolHandlerBackend: Send {
140    fn step_erased(
141        &mut self,
142        input: ToolHandlerInput,
143    ) -> Pin<Box<dyn Future<Output = Result<ToolHandlerOutput, ToolError>> + Send + '_>>;
144}
145
146impl<B: ToolHandlerBackend> ErasedToolHandlerBackend for B {
147    fn step_erased(
148        &mut self,
149        input: ToolHandlerInput,
150    ) -> Pin<Box<dyn Future<Output = Result<ToolHandlerOutput, ToolError>> + Send + '_>> {
151        Box::pin(self.step(input))
152    }
153}
154
155impl ToolHandlerBackend for InnerToolHandler {
156    async fn step(&mut self, input: ToolHandlerInput) -> Result<ToolHandlerOutput, ToolError> {
157        match self {
158            Self::Local(handler) => handler.step(input).await,
159            Self::Mcp(handler) => handler.step(input).await,
160            Self::Extension(handler) => handler.step_erased(input).await,
161        }
162    }
163}
164
165impl ToolHandlerBackend for ToolHandler {
166    fn step(
167        &mut self,
168        input: ToolHandlerInput,
169    ) -> impl Future<Output = Result<ToolHandlerOutput, ToolError>> + Send + '_ {
170        self.inner.step(input)
171    }
172}