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}