entelix_runnable/adapter.rs
1//! `ToolToRunnableAdapter` — bridges the `Tool` trait into the `Runnable`
2//! composition contract.
3//!
4//! `Tool` lives in `entelix-core` (DAG root) and does not extend `Runnable`.
5//! When a user wants to drop a tool into a `.pipe()` chain, they wrap it
6//! here:
7//!
8//! ```ignore
9//! use entelix_runnable::{RunnableExt, ToolToRunnableAdapter};
10//! let chain = prompt.pipe(model).pipe(ToolToRunnableAdapter::new(my_tool));
11//! ```
12//!
13//! The adapter forwards `invoke` to `Tool::execute`; metadata
14//! (`name`, `description`, `input_schema`) stays accessible via
15//! `inner()` for callers that need it.
16
17use std::sync::Arc;
18
19use entelix_core::tools::Tool;
20use entelix_core::{AgentContext, ExecutionContext, Result};
21
22use crate::runnable::Runnable;
23
24/// Wraps any `Tool` as `Runnable<serde_json::Value, serde_json::Value>`.
25///
26/// Cheap to clone (internal `Arc`).
27pub struct ToolToRunnableAdapter {
28 inner: Arc<dyn Tool>,
29}
30
31impl ToolToRunnableAdapter {
32 /// Wrap a concrete `Tool`.
33 pub fn new<T: Tool>(tool: T) -> Self {
34 Self {
35 inner: Arc::new(tool),
36 }
37 }
38
39 /// Wrap an already-shared `Arc<dyn Tool>` (e.g. one stored in a
40 /// `ToolRegistry`). Avoids a needless `Arc::new`.
41 pub fn from_arc(tool: Arc<dyn Tool>) -> Self {
42 Self { inner: tool }
43 }
44
45 /// Borrow the wrapped tool — useful for inspecting metadata.
46 pub fn inner(&self) -> &Arc<dyn Tool> {
47 &self.inner
48 }
49}
50
51impl Clone for ToolToRunnableAdapter {
52 fn clone(&self) -> Self {
53 Self {
54 inner: Arc::clone(&self.inner),
55 }
56 }
57}
58
59#[async_trait::async_trait]
60impl Runnable<serde_json::Value, serde_json::Value> for ToolToRunnableAdapter {
61 async fn invoke(
62 &self,
63 input: serde_json::Value,
64 ctx: &ExecutionContext,
65 ) -> Result<serde_json::Value> {
66 // Bridge `Runnable` (D-free, ExecutionContext) → `Tool`
67 // (typed `D`). Adapter-wrapped tools are always `Tool<()>`
68 // because composition does not carry typed deps; operators
69 // threading deps reach for the typed registry path
70 // (slice 103).
71 let agent_ctx = AgentContext::<()>::from(ctx.clone());
72 self.inner.execute(input, &agent_ctx).await
73 }
74
75 fn name(&self) -> std::borrow::Cow<'_, str> {
76 std::borrow::Cow::Borrowed(&self.inner.metadata().name)
77 }
78}