1use std::future::Future;
9use std::pin::Pin;
10use std::sync::Arc;
11
12use async_trait::async_trait;
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15
16use crate::registry::KernelError;
17
18pub type ToolName = String;
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ToolSchema {
29 pub name: ToolName,
30 pub description: String,
31 pub args_schema: Value,
32 pub result_schema: Value,
33}
34
35#[async_trait]
41pub trait Tool: Send + Sync {
42 fn schema(&self) -> ToolSchema;
44
45 fn name(&self) -> ToolName {
47 self.schema().name
48 }
49
50 async fn invoke(&self, args: Value) -> Result<Value, KernelError>;
52}
53
54pub struct LocalTool {
58 schema: ToolSchema,
59 #[allow(clippy::type_complexity)]
60 f: Arc<
61 dyn Fn(Value) -> Pin<Box<dyn Future<Output = Result<Value, KernelError>> + Send>>
62 + Send
63 + Sync,
64 >,
65}
66
67impl LocalTool {
68 pub fn new<F, Fut>(schema: ToolSchema, f: F) -> Self
69 where
70 F: Fn(Value) -> Fut + Send + Sync + 'static,
71 Fut: Future<Output = Result<Value, KernelError>> + Send + 'static,
72 {
73 Self {
74 schema,
75 f: Arc::new(move |v| Box::pin(f(v))),
76 }
77 }
78}
79
80#[async_trait]
81impl Tool for LocalTool {
82 fn schema(&self) -> ToolSchema {
83 self.schema.clone()
84 }
85
86 fn name(&self) -> ToolName {
87 self.schema.name.clone()
88 }
89
90 async fn invoke(&self, args: Value) -> Result<Value, KernelError> {
91 (self.f)(args).await
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use crate::*;
98 use serde_json::json;
99
100 #[tokio::test]
101 async fn local_tool_roundtrip() {
102 let schema = ToolSchema {
103 name: "test.echo".into(),
104 description: "echoes the input".into(),
105 args_schema: json!({"type": "object"}),
106 result_schema: json!({"type": "object"}),
107 };
108 let tool = LocalTool::new(schema, |v| async move { Ok(v) });
109 let out = tool.invoke(json!({"hello": "world"})).await.unwrap();
110 assert_eq!(out, json!({"hello": "world"}));
111 assert_eq!(tool.name(), "test.echo");
112 }
113}