Skip to main content

zeph_tools/
executor_delegate.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Boilerplate-reduction macros for [`ToolExecutor`](crate::ToolExecutor) and
5//! [`ErasedToolExecutor`](crate::ErasedToolExecutor) implementors.
6//!
7//! Issue #6019: both traits used to give the six risk-bearing methods
8//! (`requires_confirmation`, `execute_tool_call_confirmed`, the checkpoint trio, and
9//! `is_tool_speculatable` — plus their `_erased` counterparts) permissive default bodies.
10//! Wrapper types that forgot to override one silently inherited a default that could
11//! disable a security check, a checkpoint capability, or a confirmation gate. This
12//! recurred five times across prior PRs. The traits no longer provide those defaults —
13//! every implementor must now supply all six, and the compiler enforces it.
14//!
15//! These four macros exist only to keep that compiler-forced boilerplate short. They do
16//! **not** themselves close the defect class — only the removal of the default bodies
17//! does that. A macro invoked on the wrong type (e.g. `tool_executor_no_inner_defaults!()`
18//! on a wrapper that owns an inner executor) silently reintroduces the exact bug this
19//! issue fixes, because `macro_rules!` cannot check "this type has no delegate field."
20//! Read each macro's own doc comment before using it.
21
22/// Forwards the four mechanical capability methods of
23/// [`ToolExecutor`](crate::ToolExecutor) — the checkpoint trio and `is_tool_speculatable`
24/// — to `self.$inner`.
25///
26/// Use inside `impl ToolExecutor for YourWrapper` where `$inner` is the **field name**
27/// (an identifier, not an expression — macro hygiene forbids capturing `self` at item
28/// position) of a field whose type implements [`ToolExecutor`](crate::ToolExecutor).
29///
30/// The two policy methods, `requires_confirmation` and `execute_tool_call_confirmed`, are
31/// intentionally **not** emitted by this macro — wrappers that gate on confirmation or
32/// checkpoint policy must implement those two explicitly, so the compiler forces every
33/// wrapper author to make a deliberate decision about them instead of inheriting one
34/// silently.
35///
36/// # Examples
37///
38/// ```rust
39/// use zeph_tools::{ToolExecutor, ToolCall, ToolOutput, ToolError};
40///
41/// struct PassThrough<T> {
42///     inner: T,
43/// }
44///
45/// impl<T: ToolExecutor> ToolExecutor for PassThrough<T> {
46///     async fn execute(&self, response: &str) -> Result<Option<ToolOutput>, ToolError> {
47///         self.inner.execute(response).await
48///     }
49///
50///     fn requires_confirmation(&self, call: &ToolCall) -> bool {
51///         self.inner.requires_confirmation(call)
52///     }
53///
54///     async fn execute_tool_call_confirmed(
55///         &self,
56///         call: &ToolCall,
57///     ) -> Result<Option<ToolOutput>, ToolError> {
58///         self.inner.execute_tool_call_confirmed(call).await
59///     }
60///
61///     zeph_tools::tool_executor_forward!(inner);
62/// }
63/// ```
64#[macro_export]
65macro_rules! tool_executor_forward {
66    ($inner:ident) => {
67        fn checkpoint_undo(&self, n: usize) -> $crate::CheckpointActionResult {
68            $crate::ToolExecutor::checkpoint_undo(&self.$inner, n)
69        }
70
71        fn checkpoint_redo(&self) -> $crate::CheckpointActionResult {
72            $crate::ToolExecutor::checkpoint_redo(&self.$inner)
73        }
74
75        fn checkpoint_list(&self) -> $crate::CheckpointListResult {
76            $crate::ToolExecutor::checkpoint_list(&self.$inner)
77        }
78
79        fn is_tool_speculatable(&self, tool_id: &str) -> bool {
80            $crate::ToolExecutor::is_tool_speculatable(&self.$inner, tool_id)
81        }
82    };
83}
84
85/// Emits the six risk-bearing [`ToolExecutor`](crate::ToolExecutor) methods with the same
86/// trivial bodies the trait's removed defaults used to provide: no confirmation required,
87/// confirmed execution falls back to `execute_tool_call`, checkpoints unsupported, not
88/// speculatable.
89///
90/// # Use ONLY on leaf executors that own no wrapped executor
91///
92/// Invoking this macro on a type that wraps another [`ToolExecutor`](crate::ToolExecutor)
93/// (i.e. has an `inner`/delegate field) silently disables forwarding for all six methods
94/// and **reintroduces issue #6019**. Wrappers must use [`tool_executor_forward!`] for the
95/// mechanical four and hand-write `requires_confirmation` /
96/// `execute_tool_call_confirmed`. `macro_rules!` has no way to enforce this at compile
97/// time — review carefully.
98///
99/// # Examples
100///
101/// ```rust
102/// use zeph_tools::{ToolExecutor, ToolOutput, ToolError};
103///
104/// struct EchoExecutor;
105///
106/// impl ToolExecutor for EchoExecutor {
107///     async fn execute(&self, _response: &str) -> Result<Option<ToolOutput>, ToolError> {
108///         Ok(None)
109///     }
110///
111///     zeph_tools::tool_executor_no_inner_defaults!();
112/// }
113/// ```
114#[macro_export]
115macro_rules! tool_executor_no_inner_defaults {
116    () => {
117        fn requires_confirmation(&self, _call: &$crate::ToolCall) -> bool {
118            false
119        }
120
121        fn execute_tool_call_confirmed(
122            &self,
123            call: &$crate::ToolCall,
124        ) -> impl ::std::future::Future<
125            Output = ::std::result::Result<Option<$crate::ToolOutput>, $crate::ToolError>,
126        > + Send {
127            $crate::ToolExecutor::execute_tool_call(self, call)
128        }
129
130        fn checkpoint_undo(&self, _n: usize) -> $crate::CheckpointActionResult {
131            $crate::CheckpointActionResult::unsupported()
132        }
133
134        fn checkpoint_redo(&self) -> $crate::CheckpointActionResult {
135            $crate::CheckpointActionResult::unsupported()
136        }
137
138        fn checkpoint_list(&self) -> $crate::CheckpointListResult {
139            $crate::CheckpointListResult::default()
140        }
141
142        fn is_tool_speculatable(&self, _tool_id: &str) -> bool {
143            false
144        }
145    };
146}
147
148/// Erased-trait counterpart of [`tool_executor_forward!`]: forwards the checkpoint trio
149/// and `is_tool_speculatable_erased` to `self.$inner`.
150///
151/// Use inside `impl ErasedToolExecutor for YourWrapper` where `$inner` is the **field
152/// name** (an identifier, not an expression — see [`tool_executor_forward!`] for why) of
153/// a field whose type implements [`ErasedToolExecutor`](crate::ErasedToolExecutor). As
154/// with the static-side macro, the policy methods `requires_confirmation_erased` and
155/// `execute_tool_call_confirmed_erased` are not emitted and must be hand-written.
156#[macro_export]
157macro_rules! erased_tool_executor_forward {
158    ($inner:ident) => {
159        fn checkpoint_undo_erased(&self, n: usize) -> $crate::CheckpointActionResult {
160            $crate::ErasedToolExecutor::checkpoint_undo_erased(&*self.$inner, n)
161        }
162
163        fn checkpoint_redo_erased(&self) -> $crate::CheckpointActionResult {
164            $crate::ErasedToolExecutor::checkpoint_redo_erased(&*self.$inner)
165        }
166
167        fn checkpoint_list_erased(&self) -> $crate::CheckpointListResult {
168            $crate::ErasedToolExecutor::checkpoint_list_erased(&*self.$inner)
169        }
170
171        fn is_tool_speculatable_erased(&self, tool_id: &str) -> bool {
172            $crate::ErasedToolExecutor::is_tool_speculatable_erased(&*self.$inner, tool_id)
173        }
174    };
175}
176
177/// Erased-trait counterpart of [`tool_executor_no_inner_defaults!`]: emits the six
178/// risk-bearing [`ErasedToolExecutor`](crate::ErasedToolExecutor) methods with the same
179/// trivial bodies the trait's removed defaults used to provide.
180///
181/// # Use ONLY on leaf executors that own no wrapped executor
182///
183/// Invoking this macro on a type that wraps another
184/// [`ErasedToolExecutor`](crate::ErasedToolExecutor) silently disables forwarding for all
185/// six methods and **reintroduces issue #6019**. Wrappers must use
186/// [`erased_tool_executor_forward!`] for the mechanical four and hand-write
187/// `requires_confirmation_erased` / `execute_tool_call_confirmed_erased`.
188#[macro_export]
189macro_rules! erased_tool_executor_no_inner_defaults {
190    () => {
191        fn requires_confirmation_erased(&self, _call: &$crate::ToolCall) -> bool {
192            true
193        }
194
195        fn execute_tool_call_confirmed_erased<'a>(
196            &'a self,
197            call: &'a $crate::ToolCall,
198        ) -> ::std::pin::Pin<
199            Box<
200                dyn ::std::future::Future<
201                        Output = ::std::result::Result<
202                            Option<$crate::ToolOutput>,
203                            $crate::ToolError,
204                        >,
205                    > + Send
206                    + 'a,
207            >,
208        > {
209            $crate::ErasedToolExecutor::execute_tool_call_erased(self, call)
210        }
211
212        fn checkpoint_undo_erased(&self, _n: usize) -> $crate::CheckpointActionResult {
213            $crate::CheckpointActionResult::unsupported()
214        }
215
216        fn checkpoint_redo_erased(&self) -> $crate::CheckpointActionResult {
217            $crate::CheckpointActionResult::unsupported()
218        }
219
220        fn checkpoint_list_erased(&self) -> $crate::CheckpointListResult {
221            $crate::CheckpointListResult::default()
222        }
223
224        fn is_tool_speculatable_erased(&self, _tool_id: &str) -> bool {
225            false
226        }
227    };
228}