//! Request-scoped tool registry. //! //! Tools are registered per-request (not globally) to keep the system testable //! and allow different request contexts to have different tool sets. use std::collections::HashMap; use futures::FutureExt; use super::call::ToolError; use super::context::ToolContext; use super::definition::ToolDefinition; /// Inner function pointer type for tool handlers. type InnerHandlerFn = dyn Fn( ToolContext, serde_json::Value, ) -> std::pin::Pin< Box> + Send>, > + Send + Sync; /// Wrapper around `Arc` for `Clone` implementability. #[derive(Clone)] pub struct ToolHandler(std::sync::Arc); impl ToolHandler { pub async fn execute( &self, ctx: ToolContext, args: serde_json::Value, ) -> Result { (self.0)(ctx, args).await } } /// A request-scoped registry mapping tool names to their handlers. #[derive(Clone, Default)] pub struct ToolRegistry { handlers: HashMap, definitions: HashMap, } impl ToolRegistry { pub fn new() -> Self { Self::default() } pub fn register_fn(&mut self, name: impl Into, handler: F) -> &mut Self where F: Fn(ToolContext, serde_json::Value) -> Fut + Send + Sync + 'static, Fut: std::future::Future> + Send + 'static, { let name_str = name.into(); let def = ToolDefinition::new(&name_str); let handler_fn: std::sync::Arc = std::sync::Arc::new(move |ctx, args| handler(ctx, args).boxed()); self.register(def, ToolHandler(handler_fn)); self } pub fn register(&mut self, def: ToolDefinition, handler: ToolHandler) -> &mut Self { let name = def.name.clone(); if self.handlers.contains_key(&name) { panic!("tool already registered: {}", name); } self.handlers.insert(name.clone(), handler); self.definitions.insert(name, def); self } /// Looks up a handler by tool name. pub fn get(&self, name: &str) -> Option<&ToolHandler> { self.handlers.get(name) } pub fn definitions(&self) -> std::collections::hash_map::Values<'_, String, ToolDefinition> { self.definitions.values() } pub fn to_openai_tools(&self) -> Vec { self.definitions .values() .map(|d| d.to_openai_tool()) .collect() } pub fn len(&self) -> usize { self.handlers.len() } pub fn is_empty(&self) -> bool { self.handlers.is_empty() } }