Skip to main content

oxi_sdk/
closure_tool.rs

1//! ClosureTool — ergonomic custom tool from a closure
2
3use oxi_agent::{AgentTool, AgentToolResult, ToolContext, ToolError};
4use serde_json::Value;
5use std::sync::Arc;
6
7/// Handler function type for ClosureTool.
8pub type ToolHandler =
9    Arc<dyn Fn(Value, &ToolContext) -> Result<AgentToolResult, ToolError> + Send + Sync>;
10
11/// Async handler function type for ClosureTool.
12pub type AsyncToolHandler = Arc<
13    dyn Fn(
14            Value,
15            &ToolContext,
16        ) -> std::pin::Pin<
17            Box<dyn std::future::Future<Output = Result<AgentToolResult, ToolError>> + Send>,
18        > + Send
19        + Sync,
20>;
21
22/// A tool defined by a closure function.
23///
24/// Created via [`ClosureTool::new_sync`] or [`ClosureTool::new_async`].
25/// For ergonomic creation, use the macros or AgentBuilder's `custom_tool` method.
26pub struct ClosureTool {
27    name: String,
28    description: String,
29    schema: Value,
30    handler: AsyncToolHandler,
31}
32
33impl ClosureTool {
34    /// Create a new sync tool from a closure.
35    ///
36    /// The closure receives `(params: Value, ctx: &ToolContext)`.
37    pub fn new_sync(
38        name: impl Into<String>,
39        description: impl Into<String>,
40        schema: Value,
41        handler: impl Fn(Value, &ToolContext) -> Result<AgentToolResult, ToolError>
42            + Send
43            + Sync
44            + 'static,
45    ) -> Self {
46        #[allow(clippy::type_complexity)]
47        let handler_arc: Arc<
48            dyn Fn(Value, &ToolContext) -> Result<AgentToolResult, ToolError> + Send + Sync,
49        > = Arc::new(handler);
50        Self {
51            name: name.into(),
52            description: description.into(),
53            schema,
54            handler: Arc::new(move |params, ctx| {
55                let result = handler_arc(params, ctx);
56                Box::pin(async move { result })
57            }),
58        }
59    }
60
61    /// Create a new async tool from a closure.
62    ///
63    /// The closure receives `(params: Value, ctx: &ToolContext)` and returns a Future.
64    pub fn new_async(
65        name: impl Into<String>,
66        description: impl Into<String>,
67        schema: Value,
68        handler: impl Fn(
69                Value,
70                &ToolContext,
71            ) -> std::pin::Pin<
72                Box<dyn std::future::Future<Output = Result<AgentToolResult, ToolError>> + Send>,
73            > + Send
74            + Sync
75            + 'static,
76    ) -> Self {
77        Self {
78            name: name.into(),
79            description: description.into(),
80            schema,
81            handler: Arc::new(handler),
82        }
83    }
84}
85
86#[async_trait::async_trait]
87impl AgentTool for ClosureTool {
88    fn name(&self) -> &str {
89        &self.name
90    }
91
92    fn label(&self) -> &str {
93        &self.name
94    }
95
96    fn description(&self) -> &str {
97        &self.description
98    }
99
100    fn parameters_schema(&self) -> Value {
101        self.schema.clone()
102    }
103
104    async fn execute(
105        &self,
106        _tool_call_id: &str,
107        params: Value,
108        _signal: Option<tokio::sync::oneshot::Receiver<()>>,
109        ctx: &ToolContext,
110    ) -> Result<AgentToolResult, ToolError> {
111        (self.handler)(params, ctx).await
112    }
113}