adk_computer_use/cancellation.rs
1//! Cancellation that revokes desktop authority *before* stopping ADK reasoning.
2//!
3//! [`CancellationBridge`] pairs a [`ComputerUseRuntime`] with an
4//! [`AgentInterrupter`] (typically `adk_runner::Runner::interrupt`). On pause,
5//! stop, or emergency-stop it first revokes the runtime's desktop authority,
6//! then interrupts the agent — never the other way around.
7
8use crate::ComputerUseRuntime;
9use std::sync::Arc;
10use thiserror::Error;
11
12/// Minimal boundary implemented by `adk_runner::Runner::interrupt` or another host.
13///
14/// Blanket-implemented for any `Fn(&str) -> bool + Send + Sync`, so a closure
15/// can be passed directly.
16pub trait AgentInterrupter: Send + Sync {
17 /// Interrupt the ADK session, returning whether an active run was cancelled.
18 fn interrupt(&self, session_id: &str) -> bool;
19}
20
21impl<F> AgentInterrupter for F
22where
23 F: Fn(&str) -> bool + Send + Sync,
24{
25 fn interrupt(&self, session_id: &str) -> bool {
26 self(session_id)
27 }
28}
29
30/// Failure propagating a cancellation control to the desktop runtime.
31#[derive(Debug, Error, PartialEq, Eq)]
32pub enum CancellationError {
33 /// The desktop runtime rejected or failed the cancellation control.
34 #[error("desktop cancellation failed: {0}")]
35 Runtime(String),
36}
37
38/// Propagates controls in the safe order: revoke desktop authority, then stop reasoning.
39pub struct CancellationBridge {
40 runtime: Arc<dyn ComputerUseRuntime>,
41 interrupter: Arc<dyn AgentInterrupter>,
42}
43
44impl CancellationBridge {
45 /// Pair a desktop runtime with an agent interrupter.
46 pub fn new(
47 runtime: Arc<dyn ComputerUseRuntime>,
48 interrupter: Arc<dyn AgentInterrupter>,
49 ) -> Self {
50 Self { runtime, interrupter }
51 }
52
53 /// Pause desktop authority for the runtime session, then interrupt the ADK agent.
54 ///
55 /// # Errors
56 ///
57 /// Returns [`CancellationError::Runtime`] if the pause call fails; the
58 /// ADK agent is not interrupted in that case.
59 pub async fn pause(
60 &self,
61 runtime_session_id: &str,
62 adk_session_id: &str,
63 reason: &str,
64 ) -> Result<bool, CancellationError> {
65 self.runtime
66 .pause_session(runtime_session_id, reason)
67 .await
68 .map_err(|error| CancellationError::Runtime(error.to_string()))?;
69 Ok(self.interrupter.interrupt(adk_session_id))
70 }
71
72 /// Stop desktop authority for the runtime session, then interrupt the ADK agent.
73 ///
74 /// # Errors
75 ///
76 /// Returns [`CancellationError::Runtime`] if the stop call fails.
77 pub async fn stop(
78 &self,
79 runtime_session_id: &str,
80 adk_session_id: &str,
81 reason: &str,
82 ) -> Result<bool, CancellationError> {
83 self.runtime
84 .stop_session(runtime_session_id, reason)
85 .await
86 .map_err(|error| CancellationError::Runtime(error.to_string()))?;
87 Ok(self.interrupter.interrupt(adk_session_id))
88 }
89
90 /// Revoke all desktop authority immediately, then interrupt the ADK agent.
91 ///
92 /// # Errors
93 ///
94 /// Returns [`CancellationError::Runtime`] if the emergency-stop call fails.
95 pub async fn emergency_stop(
96 &self,
97 adk_session_id: &str,
98 reason: &str,
99 ) -> Result<bool, CancellationError> {
100 self.runtime
101 .emergency_stop(reason)
102 .await
103 .map_err(|error| CancellationError::Runtime(error.to_string()))?;
104 Ok(self.interrupter.interrupt(adk_session_id))
105 }
106}