synapto_interface/
tool.rs1#![doc = include_str!("tool.md")]
2
3use crate::context::ContextRequest;
4use crate::llm::LLMSafe;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8#[async_trait::async_trait]
9pub trait Tool: Send + Sync + 'static {
10 type Arguments: schemars::JsonSchema
11 + serde::de::DeserializeOwned
12 + LLMSafe
13 + Send
14 + Sync
15 + 'static;
16 const NAME: &'static str;
17 const DESCRIPTION: &'static str;
18 #[doc = " Evaluated dynamically every turn AFTER the ContextProviders have compiled the World State."]
19 #[doc = " `compiled_context` is the JSON value generated by all ContextProviders that the LLM is about to see."]
20 async fn is_available(
21 &self,
22 _ctx_request: &ContextRequest,
23 _compiled_context: &serde_json::Value,
24 ) -> Result<bool, String> {
25 Ok(true)
26 }
27 #[doc = " Executes the tool. The result is serialized and fed back to the LLM."]
28 async fn execute(
29 &self,
30 ctx_request: &ContextRequest,
31 args: Self::Arguments,
32 ) -> Result<serde_json::Value, String>;
33}
34
35#[doc = " Type-erased trait for tools registered dynamically at runtime."]
36#[async_trait::async_trait]
37pub trait ErasedTool: Send + Sync + 'static {
38 #[doc = " Unique identifier name of the tool."]
39 fn name(&self) -> &'static str;
40 #[doc = " Human/LLM-readable description explaining the tool's capability."]
41 fn description(&self) -> &'static str;
42 #[doc = " JSON Schema describing expected tool call arguments."]
43 fn schema(&self) -> schemars::Schema;
44 #[doc = " Evaluated per turn to determine if tool is currently active/available."]
45 async fn erased_is_available(
46 &self,
47 _ctx_request: &ContextRequest,
48 _compiled_context: &serde_json::Value,
49 ) -> Result<bool, String> {
50 Ok(true)
51 }
52 #[doc = " Executes the tool with untyped JSON arguments and returns structured JSON output."]
53 async fn erased_execute(
54 &self,
55 ctx_request: &ContextRequest,
56 args: serde_json::Value,
57 ) -> Result<serde_json::Value, String>;
58}
59
60#[async_trait::async_trait]
61impl<T> ErasedTool for T
62where
63 T: Tool,
64{
65 fn name(&self) -> &'static str {
66 <T as Tool>::NAME
67 }
68 fn description(&self) -> &'static str {
69 <T as Tool>::DESCRIPTION
70 }
71 fn schema(&self) -> schemars::Schema {
72 schemars::schema_for!(<T as Tool>::Arguments)
73 }
74 async fn erased_is_available(
75 &self,
76 ctx_request: &ContextRequest,
77 compiled_context: &serde_json::Value,
78 ) -> Result<bool, String> {
79 <T as Tool>::is_available(self, ctx_request, compiled_context).await
80 }
81 async fn erased_execute(
82 &self,
83 ctx_request: &ContextRequest,
84 args: serde_json::Value,
85 ) -> Result<serde_json::Value, String> {
86 let parsed_args = serde_json::from_value(args).map_err(|e| e.to_string())?;
87 <T as Tool>::execute(self, ctx_request, parsed_args).await
88 }
89}
90
91#[derive(Clone)]
93pub struct ToolHandle(std::sync::Arc<dyn ErasedTool>);
94
95impl std::fmt::Debug for ToolHandle {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 f.debug_struct("ToolHandle")
98 .field("name", &self.0.name())
99 .finish()
100 }
101}
102
103impl ToolHandle {
104 pub fn new<T: ErasedTool + 'static>(tool: T) -> Self {
105 Self(std::sync::Arc::new(tool))
106 }
107
108 pub fn from_arc(tool: std::sync::Arc<dyn ErasedTool>) -> Self {
109 Self(tool)
110 }
111
112 pub fn into_inner(self) -> std::sync::Arc<dyn ErasedTool> {
113 self.0
114 }
115
116 pub fn inner(&self) -> &std::sync::Arc<dyn ErasedTool> {
117 &self.0
118 }
119
120 pub fn name(&self) -> &'static str {
121 self.0.name()
122 }
123
124 pub fn description(&self) -> &'static str {
125 self.0.description()
126 }
127
128 pub fn schema(&self) -> schemars::Schema {
129 self.0.schema()
130 }
131
132 pub async fn erased_is_available(
133 &self,
134 ctx_request: &ContextRequest,
135 compiled_context: &serde_json::Value,
136 ) -> Result<bool, String> {
137 self.0
138 .erased_is_available(ctx_request, compiled_context)
139 .await
140 }
141
142 pub async fn erased_execute(
143 &self,
144 ctx_request: &ContextRequest,
145 args: serde_json::Value,
146 ) -> Result<serde_json::Value, String> {
147 self.0.erased_execute(ctx_request, args).await
148 }
149}
150
151#[async_trait::async_trait]
152impl ErasedTool for ToolHandle {
153 fn name(&self) -> &'static str {
154 self.0.name()
155 }
156 fn description(&self) -> &'static str {
157 self.0.description()
158 }
159 fn schema(&self) -> schemars::Schema {
160 self.0.schema()
161 }
162 async fn erased_is_available(
163 &self,
164 ctx_request: &ContextRequest,
165 compiled_context: &serde_json::Value,
166 ) -> Result<bool, String> {
167 self.0
168 .erased_is_available(ctx_request, compiled_context)
169 .await
170 }
171 async fn erased_execute(
172 &self,
173 ctx_request: &ContextRequest,
174 args: serde_json::Value,
175 ) -> Result<serde_json::Value, String> {
176 self.0.erased_execute(ctx_request, args).await
177 }
178}
179
180#[derive(Default)]
181pub struct ToolRegistryBuilder {
182 pub tools: std::sync::RwLock<std::collections::HashMap<String, std::sync::Arc<dyn ErasedTool>>>,
183}
184
185impl ToolRegistryBuilder {
186 pub fn register<T>(&self, tool: T)
187 where
188 T: ErasedTool + 'static,
189 {
190 let tool_arc: std::sync::Arc<dyn ErasedTool> = std::sync::Arc::new(tool);
191 self.register_erased(tool_arc);
192 }
193 pub fn register_erased(&self, tool: std::sync::Arc<dyn ErasedTool>) {
194 self.tools
195 .write()
196 .unwrap_or_else(|e| panic!("Failed to acquire write lock on tools: {:?}", e))
197 .insert(tool.name().to_string(), tool);
198 }
199 pub fn get(&self, name: &str) -> Option<std::sync::Arc<dyn ErasedTool>> {
200 self.tools
201 .read()
202 .unwrap_or_else(|e| panic!("Failed to acquire read lock on tools: {:?}", e))
203 .get(name)
204 .cloned()
205 }
206 pub fn get_all(&self) -> Vec<std::sync::Arc<dyn ErasedTool>> {
207 self.tools
208 .read()
209 .unwrap_or_else(|e| panic!("Failed to acquire read lock on tools: {:?}", e))
210 .values()
211 .cloned()
212 .collect()
213 }
214}
215
216#[derive(
217 Serialize,
218 Deserialize,
219 JsonSchema,
220 PartialEq,
221 Eq,
222 Debug,
223 Clone,
224 derive_more :: Display,
225 derive_more :: From,
226 derive_more :: Deref,
227)]
228pub struct ToolCallId(pub String);