Skip to main content

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}