monoloop_loop/transaction/
tool_handler.rs1use monoloop_contracts::{
4 ToolCall, ToolCallContext, ToolCompletion, ToolExecutionId, ToolStartError,
5};
6use std::future::Future;
7use std::pin::Pin;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::sync::Arc;
10use tokio::sync::{oneshot, Notify};
11use tokio::task::AbortHandle;
12
13pub trait ToolHandler: Send + Sync {
15 fn start(
17 &self,
18 call: ToolCall,
19 context: ToolCallContext,
20 ) -> Result<LinkedToolExecutionHandle, ToolStartError>;
21
22 fn supports_abort(&self) -> bool {
25 false
26 }
27
28 fn supports_isolated_kill(&self) -> bool {
31 false
32 }
33}
34
35#[derive(Clone, Debug)]
37pub struct ToolExecutionControl {
38 cancelled: Arc<AtomicBool>,
39 notify: Arc<Notify>,
40}
41
42impl ToolExecutionControl {
43 pub fn new() -> Self {
45 Self {
46 cancelled: Arc::new(AtomicBool::new(false)),
47 notify: Arc::new(Notify::new()),
48 }
49 }
50
51 pub fn cancel(&self) {
53 self.cancelled.store(true, Ordering::SeqCst);
54 self.notify.notify_waiters();
55 }
56
57 pub fn is_cancelled(&self) -> bool {
59 self.cancelled.load(Ordering::SeqCst)
60 }
61
62 pub async fn cancelled(&self) {
64 loop {
65 if self.is_cancelled() {
66 return;
67 }
68 self.notify.notified().await;
69 }
70 }
71}
72
73impl Default for ToolExecutionControl {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79#[derive(Debug)]
81pub struct ToolExecutionCompletion {
82 rx: oneshot::Receiver<ToolCompletion>,
83}
84
85impl ToolExecutionCompletion {
86 pub fn new(rx: oneshot::Receiver<ToolCompletion>) -> Self {
88 Self { rx }
89 }
90
91 pub async fn wait(self) -> ToolCompletion {
93 self.rx.await.unwrap_or(ToolCompletion::RuntimeFailed(
94 monoloop_contracts::ToolRuntimeError::CompletionLost,
95 ))
96 }
97}
98
99#[derive(Clone, Debug)]
101pub struct ToolKillHandle {
102 abort: AbortHandle,
103}
104
105impl ToolKillHandle {
106 pub fn new(abort: AbortHandle) -> Self {
108 Self { abort }
109 }
110
111 pub fn kill(&self) {
113 self.abort.abort();
114 }
115}
116
117#[derive(Debug)]
119pub struct LinkedToolExecutionHandle {
120 pub execution_id: ToolExecutionId,
122 pub control: ToolExecutionControl,
124 pub completion: ToolExecutionCompletion,
126 pub kill: Option<ToolKillHandle>,
128}
129
130pub struct ImmediateToolHandler<F> {
132 f: F,
133}
134
135impl<F> ImmediateToolHandler<F>
136where
137 F: Fn(ToolCall, ToolCallContext) -> Result<ToolCompletion, ToolStartError> + Send + Sync,
138{
139 pub fn new(f: F) -> Self {
141 Self { f }
142 }
143}
144
145impl<F> ToolHandler for ImmediateToolHandler<F>
146where
147 F: Fn(ToolCall, ToolCallContext) -> Result<ToolCompletion, ToolStartError> + Send + Sync,
148{
149 fn start(
150 &self,
151 call: ToolCall,
152 context: ToolCallContext,
153 ) -> Result<LinkedToolExecutionHandle, ToolStartError> {
154 let completion = (self.f)(call, context)?;
155 let (tx, rx) = oneshot::channel();
156 let _ = tx.send(completion);
157 Ok(LinkedToolExecutionHandle {
158 execution_id: ToolExecutionId::generate(),
159 control: ToolExecutionControl::new(),
160 completion: ToolExecutionCompletion::new(rx),
161 kill: None,
162 })
163 }
164}
165
166type BoxFut = Pin<Box<dyn Future<Output = ToolCompletion> + Send>>;
167
168pub struct AsyncToolHandler<F> {
170 f: F,
171}
172
173impl<F> AsyncToolHandler<F>
174where
175 F: Fn(ToolCall, ToolCallContext, ToolExecutionControl) -> BoxFut + Send + Sync,
176{
177 pub fn new(f: F) -> Self {
179 Self { f }
180 }
181}
182
183impl<F> ToolHandler for AsyncToolHandler<F>
184where
185 F: Fn(ToolCall, ToolCallContext, ToolExecutionControl) -> BoxFut + Send + Sync,
186{
187 fn start(
188 &self,
189 call: ToolCall,
190 context: ToolCallContext,
191 ) -> Result<LinkedToolExecutionHandle, ToolStartError> {
192 let control = ToolExecutionControl::new();
193 let control_body = control.clone();
194 let fut = (self.f)(call, context, control_body.clone());
195 let (tx, rx) = oneshot::channel();
196 let join = tokio::spawn(async move {
198 tokio::select! {
199 biased;
200 _ = control_body.cancelled() => {
201 let _ = tx.send(ToolCompletion::RuntimeFailed(
202 monoloop_contracts::ToolRuntimeError::TerminationFailed,
203 ));
204 }
205 result = fut => {
206 let _ = tx.send(result);
207 }
208 }
209 });
210 let kill = ToolKillHandle::new(join.abort_handle());
211 Ok(LinkedToolExecutionHandle {
212 execution_id: ToolExecutionId::generate(),
213 control,
214 completion: ToolExecutionCompletion::new(rx),
215 kill: Some(kill),
216 })
217 }
218
219 fn supports_abort(&self) -> bool {
220 true
221 }
222}
223
224pub struct IsolatedKillableToolHandler<F> {
226 f: F,
227}
228
229impl<F> IsolatedKillableToolHandler<F>
230where
231 F: Fn(ToolCall, ToolCallContext) -> BoxFut + Send + Sync,
232{
233 pub fn new(f: F) -> Self {
235 Self { f }
236 }
237}
238
239impl<F> ToolHandler for IsolatedKillableToolHandler<F>
240where
241 F: Fn(ToolCall, ToolCallContext) -> BoxFut + Send + Sync,
242{
243 fn start(
244 &self,
245 call: ToolCall,
246 context: ToolCallContext,
247 ) -> Result<LinkedToolExecutionHandle, ToolStartError> {
248 let control = ToolExecutionControl::new();
249 let fut = (self.f)(call, context);
250 let (tx, rx) = oneshot::channel();
251 let join = tokio::spawn(async move {
252 let result = fut.await;
253 let _ = tx.send(result);
254 });
255 let kill = ToolKillHandle::new(join.abort_handle());
256 Ok(LinkedToolExecutionHandle {
257 execution_id: ToolExecutionId::generate(),
258 control,
259 completion: ToolExecutionCompletion::new(rx),
260 kill: Some(kill),
261 })
262 }
263
264 fn supports_abort(&self) -> bool {
265 false
267 }
268
269 fn supports_isolated_kill(&self) -> bool {
270 true
271 }
272}
273
274#[derive(Debug, Default)]
276pub struct StartFailHandler {
277 pub reason: &'static str,
279}
280
281impl ToolHandler for StartFailHandler {
282 fn start(
283 &self,
284 _call: ToolCall,
285 _context: ToolCallContext,
286 ) -> Result<LinkedToolExecutionHandle, ToolStartError> {
287 Err(ToolStartError::Rejected(self.reason))
288 }
289}
290
291#[derive(Debug, Default)]
293pub struct PanicOnStartHandler;
294
295impl ToolHandler for PanicOnStartHandler {
296 fn start(
297 &self,
298 _call: ToolCall,
299 _context: ToolCallContext,
300 ) -> Result<LinkedToolExecutionHandle, ToolStartError> {
301 panic!("deliberate tool panic");
302 }
303}
304
305#[derive(Debug, Default)]
307pub struct LostCompletionHandler;
308
309impl ToolHandler for LostCompletionHandler {
310 fn start(
311 &self,
312 _call: ToolCall,
313 _context: ToolCallContext,
314 ) -> Result<LinkedToolExecutionHandle, ToolStartError> {
315 let (tx, rx) = oneshot::channel();
316 drop(tx);
317 Ok(LinkedToolExecutionHandle {
318 execution_id: ToolExecutionId::generate(),
319 control: ToolExecutionControl::new(),
320 completion: ToolExecutionCompletion::new(rx),
321 kill: None,
322 })
323 }
324}