Skip to main content

synwire_core/runnables/
as_tool.rs

1//! Wraps a runnable as a tool with a schema.
2
3use crate::BoxFuture;
4use crate::error::SynwireError;
5use crate::runnables::config::RunnableConfig;
6use crate::runnables::core::RunnableCore;
7use crate::tools::ToolSchema;
8use serde_json::Value;
9
10/// A runnable wrapped with a tool schema for use in tool-calling workflows.
11///
12/// Delegates all execution to the inner runnable while exposing
13/// the tool schema for model integration.
14pub struct RunnableTool {
15    inner: Box<dyn RunnableCore>,
16    schema: ToolSchema,
17}
18
19impl RunnableTool {
20    /// Create a new tool-wrapped runnable.
21    pub fn new(inner: Box<dyn RunnableCore>, schema: ToolSchema) -> Self {
22        Self { inner, schema }
23    }
24
25    /// Return a reference to this tool's schema.
26    pub const fn schema(&self) -> &ToolSchema {
27        &self.schema
28    }
29}
30
31impl RunnableCore for RunnableTool {
32    fn invoke<'a>(
33        &'a self,
34        input: Value,
35        config: Option<&'a RunnableConfig>,
36    ) -> BoxFuture<'a, Result<Value, SynwireError>> {
37        self.inner.invoke(input, config)
38    }
39
40    fn name(&self) -> &str {
41        &self.schema.name
42    }
43}
44
45#[cfg(test)]
46#[allow(clippy::unwrap_used)]
47mod tests {
48    use super::*;
49    use crate::runnables::passthrough::RunnablePassthrough;
50
51    #[tokio::test]
52    async fn test_runnable_tool_delegates() {
53        let schema = ToolSchema {
54            name: "my_tool".into(),
55            description: "A test tool".into(),
56            parameters: serde_json::json!({"type": "object"}),
57        };
58        let tool = RunnableTool::new(Box::new(RunnablePassthrough), schema);
59
60        assert_eq!(tool.name(), "my_tool");
61        assert_eq!(tool.schema().description, "A test tool");
62
63        let result = tool.invoke(Value::from(42), None).await.unwrap();
64        assert_eq!(result, Value::from(42));
65    }
66}