Skip to main content

synapto_interface/
tool.rs

1#![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(Default)]
92pub struct ToolRegistryBuilder {
93    pub tools: std::sync::RwLock<std::collections::HashMap<String, std::sync::Arc<dyn ErasedTool>>>,
94}
95
96impl ToolRegistryBuilder {
97    pub fn register<T>(&self, tool: T)
98    where
99        T: ErasedTool + 'static,
100    {
101        let tool_arc: std::sync::Arc<dyn ErasedTool> = std::sync::Arc::new(tool);
102        self.register_erased(tool_arc);
103    }
104    pub fn register_erased(&self, tool: std::sync::Arc<dyn ErasedTool>) {
105        self.tools
106            .write()
107            .unwrap_or_else(|e| panic!("Failed to acquire write lock on tools: {:?}", e))
108            .insert(tool.name().to_string(), tool);
109    }
110    pub fn get(&self, name: &str) -> Option<std::sync::Arc<dyn ErasedTool>> {
111        self.tools
112            .read()
113            .unwrap_or_else(|e| panic!("Failed to acquire read lock on tools: {:?}", e))
114            .get(name)
115            .cloned()
116    }
117    pub fn get_all(&self) -> Vec<std::sync::Arc<dyn ErasedTool>> {
118        self.tools
119            .read()
120            .unwrap_or_else(|e| panic!("Failed to acquire read lock on tools: {:?}", e))
121            .values()
122            .cloned()
123            .collect()
124    }
125}
126
127#[derive(
128    Serialize,
129    Deserialize,
130    JsonSchema,
131    PartialEq,
132    Eq,
133    Debug,
134    Clone,
135    derive_more :: Display,
136    derive_more :: From,
137    derive_more :: Deref,
138)]
139pub struct ToolCallId(pub String);