1use crate::tools::{AgentTool, AgentToolResult, ToolContext, ToolError};
4use crate::types::ToolDefinition;
5use async_trait::async_trait;
6use serde_json::Value;
7use std::sync::Arc;
8use tokio::sync::oneshot;
9
10pub struct DynamicTool {
15 name: String,
16 label: String,
17 description: String,
18 parameters: Value,
19 #[allow(clippy::type_complexity)]
20 execute_fn: Arc<
21 dyn Fn(
22 &str,
23 Value,
24 Option<oneshot::Receiver<()>>,
25 ) -> std::pin::Pin<
26 Box<dyn std::future::Future<Output = Result<AgentToolResult, ToolError>> + Send>,
27 > + Send
28 + Sync,
29 >,
30}
31
32impl DynamicTool {
33 pub fn new(
35 name: impl Into<String>,
36 label: impl Into<String>,
37 description: impl Into<String>,
38 parameters: Value,
39 execute_fn: impl Fn(
40 &str,
41 Value,
42 Option<oneshot::Receiver<()>>,
43 ) -> std::pin::Pin<
44 Box<dyn std::future::Future<Output = Result<AgentToolResult, ToolError>> + Send>,
45 > + Send
46 + Sync
47 + 'static,
48 ) -> Self {
49 Self {
50 name: name.into(),
51 label: label.into(),
52 description: description.into(),
53 parameters,
54 execute_fn: Arc::new(execute_fn),
55 }
56 }
57
58 pub fn from_definition(
60 def: ToolDefinition,
61 execute_fn: impl Fn(
62 &str,
63 Value,
64 Option<oneshot::Receiver<()>>,
65 ) -> std::pin::Pin<
66 Box<dyn std::future::Future<Output = Result<AgentToolResult, ToolError>> + Send>,
67 > + Send
68 + Sync
69 + 'static,
70 ) -> Self {
71 let name_for_label = def.name.clone();
72 let schema =
73 serde_json::to_value(&def.input_schema).unwrap_or(Value::Object(Default::default()));
74 Self {
75 name: def.name,
76 label: name_for_label, description: def.description,
78 parameters: schema,
79 execute_fn: Arc::new(execute_fn),
80 }
81 }
82}
83
84#[async_trait]
85impl AgentTool for DynamicTool {
86 fn name(&self) -> &str {
87 &self.name
88 }
89
90 fn label(&self) -> &str {
91 &self.label
92 }
93
94 fn description(&self) -> &str {
95 &self.description
96 }
97
98 fn parameters_schema(&self) -> Value {
99 self.parameters.clone()
100 }
101
102 async fn execute(
103 &self,
104 tool_call_id: &str,
105 params: Value,
106 signal: Option<oneshot::Receiver<()>>,
107 _ctx: &ToolContext,
108 ) -> Result<AgentToolResult, ToolError> {
109 (self.execute_fn)(tool_call_id, params, signal).await
110 }
111}
112
113pub trait ToolDefinitionLike: Send + Sync {
115 fn tool_name(&self) -> &str;
117 fn tool_label(&self) -> &str;
119 fn tool_description(&self) -> &str;
121 fn tool_parameters(&self) -> Value;
123 fn tool_execute(
125 &self,
126 tool_call_id: &str,
127 params: Value,
128 signal: Option<oneshot::Receiver<()>>,
129 ) -> std::pin::Pin<
130 Box<dyn std::future::Future<Output = Result<AgentToolResult, ToolError>> + Send>,
131 >;
132}
133
134pub fn wrap_tool_definition<T: ToolDefinitionLike + 'static>(def: T) -> Arc<dyn AgentTool> {
136 Arc::new(DefinitionWrapper(def))
137}
138
139struct DefinitionWrapper<T>(T);
141
142#[async_trait]
143impl<T: ToolDefinitionLike + 'static> AgentTool for DefinitionWrapper<T> {
144 fn name(&self) -> &str {
145 self.0.tool_name()
146 }
147
148 fn label(&self) -> &str {
149 self.0.tool_label()
150 }
151
152 fn description(&self) -> &str {
153 self.0.tool_description()
154 }
155
156 fn parameters_schema(&self) -> Value {
157 self.0.tool_parameters()
158 }
159
160 async fn execute(
161 &self,
162 tool_call_id: &str,
163 params: Value,
164 signal: Option<oneshot::Receiver<()>>,
165 _ctx: &ToolContext,
166 ) -> Result<AgentToolResult, ToolError> {
167 self.0.tool_execute(tool_call_id, params, signal).await
168 }
169}
170
171pub fn create_tool_definition_from_agent_tool(tool: &dyn AgentTool) -> ToolDefinition {
176 let schema_map: std::collections::HashMap<String, Value> = {
177 let schema = tool.parameters_schema();
178 if let Value::Object(map) = schema {
179 map.into_iter().collect()
180 } else {
181 let mut map = std::collections::HashMap::new();
182 map.insert("type".to_string(), Value::String("object".to_string()));
183 map
184 }
185 };
186
187 ToolDefinition::new(tool.name(), tool.description(), schema_map)
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_create_tool_definition_from_agent_tool() {
196 struct TestTool;
197
198 #[async_trait]
199 impl AgentTool for TestTool {
200 fn name(&self) -> &str {
201 "test_tool"
202 }
203 fn label(&self) -> &str {
204 "Test Tool"
205 }
206 fn description(&self) -> &str {
207 "A test tool"
208 }
209 fn parameters_schema(&self) -> Value {
210 serde_json::json!({
211 "type": "object",
212 "properties": {
213 "input": { "type": "string" }
214 }
215 })
216 }
217 async fn execute(
218 &self,
219 _tool_call_id: &str,
220 _params: Value,
221 _signal: Option<oneshot::Receiver<()>>,
222 _ctx: &ToolContext,
223 ) -> Result<AgentToolResult, ToolError> {
224 Ok(AgentToolResult::success("test"))
225 }
226 }
227
228 let tool = TestTool;
229 let def = create_tool_definition_from_agent_tool(&tool);
230 assert_eq!(def.name, "test_tool");
231 assert_eq!(def.description, "A test tool");
232 }
233
234 #[test]
235 fn test_dynamic_tool_creation() {
236 let tool = DynamicTool::new(
237 "dynamic_test",
238 "Dynamic Test",
239 "A dynamic test tool",
240 serde_json::json!({"type": "object"}),
241 |_id, _params, _signal| {
242 Box::pin(async { Ok(AgentToolResult::success("dynamic result")) })
243 },
244 );
245
246 assert_eq!(tool.name(), "dynamic_test");
247 assert_eq!(tool.label(), "Dynamic Test");
248 assert_eq!(tool.description(), "A dynamic test tool");
249 }
250
251 #[tokio::test]
252 async fn test_dynamic_tool_execution() {
253 let tool = DynamicTool::new(
254 "exec_test",
255 "Exec Test",
256 "Exec test tool",
257 serde_json::json!({"type": "object"}),
258 |_id, params, _signal| {
259 let result = format!("got: {}", params);
260 Box::pin(async move { Ok(AgentToolResult::success(result)) })
261 },
262 );
263
264 let result = tool
265 .execute(
266 "call_1",
267 serde_json::json!({"key": "value"}),
268 None,
269 &ToolContext::default(),
270 )
271 .await;
272 assert!(result.is_ok());
273 assert_eq!(result.unwrap().output, "got: {\"key\":\"value\"}");
274 }
275
276 struct MockDefLike;
277
278 impl ToolDefinitionLike for MockDefLike {
279 fn tool_name(&self) -> &str {
280 "mock_def"
281 }
282 fn tool_label(&self) -> &str {
283 "Mock Definition"
284 }
285 fn tool_description(&self) -> &str {
286 "Mock description"
287 }
288 fn tool_parameters(&self) -> Value {
289 serde_json::json!({"type": "object"})
290 }
291 fn tool_execute(
292 &self,
293 _tool_call_id: &str,
294 _params: Value,
295 _signal: Option<oneshot::Receiver<()>>,
296 ) -> std::pin::Pin<
297 Box<dyn std::future::Future<Output = Result<AgentToolResult, ToolError>> + Send>,
298 > {
299 Box::pin(async { Ok(AgentToolResult::success("mock result")) })
300 }
301 }
302
303 #[test]
304 fn test_wrap_tool_definition() {
305 let wrapped = wrap_tool_definition(MockDefLike);
306 assert_eq!(wrapped.name(), "mock_def");
307 assert_eq!(wrapped.label(), "Mock Definition");
308 }
309
310 #[tokio::test]
311 async fn test_wrapped_tool_execution() {
312 let wrapped = wrap_tool_definition(MockDefLike);
313 let result = wrapped
314 .execute("call_1", Value::Null, None, &ToolContext::default())
315 .await;
316 assert!(result.is_ok());
317 assert_eq!(result.unwrap().output, "mock result");
318 }
319}