1mod batch;
4
5use alloc::{boxed::Box, sync::Arc, vec::Vec};
6
7pub use batch::{
8 AllowDestructiveBatch, BatchExecutionMode, BatchToolCall, BatchToolCallOutcome,
9 BatchToolCallStr,
10};
11
12use super::{
13 rust_tool::{ErasedTool, RustTool, definition_of},
14 types::{RegistryItem, ToolContext, ToolDefinition, ToolError, ToolOutput},
15};
16use crate::{
17 compat::{HashMap, RwLock, read_lock_recover, write_lock_recover},
18 rust_tool::BoxToolFuture,
19 types::ToolEffect,
20};
21
22struct RegisteredTool {
28 definition: ToolDefinition,
29 erased: Box<dyn ErasedTool>,
30}
31
32pub struct ToolRegistry {
37 tools: HashMap<&'static str, RegisteredTool>,
38 cached_definitions: RwLock<Option<Arc<[ToolDefinition]>>>,
39}
40
41impl core::fmt::Debug for ToolRegistry {
42 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43 let names: Vec<&str> = self
44 .tools
45 .values()
46 .map(|r| r.definition.name.as_str())
47 .collect();
48 f.debug_struct("ToolRegistry")
49 .field("tool_count", &self.tools.len())
50 .field("tool_names", &names)
51 .finish_non_exhaustive()
52 }
53}
54
55impl Default for ToolRegistry {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl ToolRegistry {
62 #[must_use]
64 pub fn new() -> Self {
65 Self {
66 tools: HashMap::new(),
67 cached_definitions: RwLock::new(None),
68 }
69 }
70
71 fn invalidate_cache(&self) {
72 let mut guard = write_lock_recover(&self.cached_definitions);
73 *guard = None;
74 }
75
76 pub fn register<T: RustTool + 'static>(&mut self, tool: T) -> &mut Self {
89 if let Err(e) = self.try_register(tool) {
90 panic!("Failed to build definition for tool '{}': {e}", T::NAME);
91 }
92 self
93 }
94
95 pub fn try_register<T: RustTool + 'static>(&mut self, tool: T) -> Result<&mut Self, ToolError> {
108 let definition = definition_of(&tool)?;
109 self.tools.insert(
110 T::NAME,
111 RegisteredTool {
112 definition,
113 erased: Box::new(tool),
114 },
115 );
116 self.invalidate_cache();
117 Ok(self)
118 }
119
120 #[must_use]
122 pub fn with_tool<T: RustTool + 'static>(mut self, tool: T) -> Self {
123 self.register(tool);
124 self
125 }
126
127 #[must_use]
130 pub fn definitions_slice(&self) -> Arc<[ToolDefinition]> {
131 {
132 let guard = read_lock_recover(&self.cached_definitions);
133 if let Some(ref slice) = *guard {
134 return Arc::clone(slice);
135 }
136 }
137 let mut defs: Vec<ToolDefinition> = self
138 .tools
139 .values()
140 .map(|entry| entry.definition.clone())
141 .collect();
142 defs.sort_by(|a, b| a.name.cmp(&b.name));
143 let arc_slice: Arc<[ToolDefinition]> = Arc::from(defs);
144 {
145 let mut guard = write_lock_recover(&self.cached_definitions);
146 *guard = Some(Arc::clone(&arc_slice));
147 }
148 arc_slice
149 }
150
151 #[must_use]
155 pub fn definitions(&self) -> Vec<ToolDefinition> {
156 self.definitions_slice().to_vec()
157 }
158
159 pub fn dispatch_boxed<'a>(
166 &'a self,
167 name: &str,
168 args: serde_json::Value,
169 ctx: &'a ToolContext,
170 ) -> Result<BoxToolFuture<'a>, ToolError> {
171 let Some(entry) = self.tools.get(name) else {
172 return Err(ToolError::not_found(RegistryItem::Tool, name));
173 };
174 Ok(entry.erased.call_erased(args, ctx))
175 }
176
177 pub fn dispatch_boxed_str<'a>(
184 &'a self,
185 name: &str,
186 args_json: &'a str,
187 ctx: &'a ToolContext,
188 ) -> Result<BoxToolFuture<'a>, ToolError> {
189 let Some(entry) = self.tools.get(name) else {
190 return Err(ToolError::not_found(RegistryItem::Tool, name));
191 };
192 Ok(entry.erased.call_erased_str(args_json, ctx))
193 }
194
195 pub async fn dispatch(
203 &self,
204 name: &str,
205 args: serde_json::Value,
206 ctx: &ToolContext,
207 ) -> Result<ToolOutput, ToolError> {
208 self.dispatch_boxed(name, args, ctx)?.await
209 }
210
211 pub async fn dispatch_str(
218 &self,
219 name: &str,
220 args_json: &str,
221 ctx: &ToolContext,
222 ) -> Result<ToolOutput, ToolError> {
223 self.dispatch_boxed_str(name, args_json, ctx)?.await
224 }
225
226 #[must_use]
228 pub fn is_destructive(&self, name: &str) -> bool {
229 self.definition(name)
230 .is_some_and(|def| matches!(def.effect, ToolEffect::Destructive))
231 }
232
233 pub fn remove(&mut self, name: &str) -> bool {
235 let removed = self.tools.remove(name).is_some();
236 if removed {
237 self.invalidate_cache();
238 }
239 removed
240 }
241
242 pub fn clear(&mut self) {
244 self.tools.clear();
245 self.invalidate_cache();
246 }
247
248 #[must_use]
250 pub fn len(&self) -> usize {
251 self.tools.len()
252 }
253
254 #[must_use]
256 pub fn is_empty(&self) -> bool {
257 self.tools.is_empty()
258 }
259
260 #[must_use]
262 pub fn contains(&self, name: &str) -> bool {
263 self.tools.contains_key(name)
264 }
265
266 #[must_use]
271 pub fn definition(&self, name: &str) -> Option<&ToolDefinition> {
272 self.tools.get(name).map(|entry| &entry.definition)
273 }
274
275 #[must_use]
279 pub fn iter(&self) -> ToolDefinitions<'_> {
280 ToolDefinitions {
281 inner: self.tools.iter(),
282 }
283 }
284}
285
286pub struct ToolDefinitions<'a> {
293 inner: crate::compat::HashMapIter<'a, &'static str, RegisteredTool>,
294}
295
296impl Iterator for ToolDefinitions<'_> {
297 type Item = (&'static str, ToolDefinition);
298
299 fn next(&mut self) -> Option<Self::Item> {
300 self.inner
301 .next()
302 .map(|(name, entry)| (*name, entry.definition.clone()))
303 }
304
305 fn size_hint(&self) -> (usize, Option<usize>) {
306 self.inner.size_hint()
307 }
308}
309
310impl ExactSizeIterator for ToolDefinitions<'_> {
311 fn len(&self) -> usize {
312 self.inner.len()
313 }
314}
315
316impl<'a> IntoIterator for &'a ToolRegistry {
320 type Item = (&'static str, ToolDefinition);
321 type IntoIter = ToolDefinitions<'a>;
322
323 fn into_iter(self) -> Self::IntoIter {
324 self.iter()
325 }
326}
327
328#[cfg(all(test, feature = "std"))]
329mod tests;