Skip to main content

agent_first_http/sdk/
takeover.rs

1//! Client helpers for short-lived human-takeover handoff URLs.
2
3use serde::{Deserialize, Serialize};
4
5use crate::sdk::client::Client;
6use crate::shared::error::{Error, ErrorCode};
7
8#[derive(Debug, Clone, Serialize)]
9struct TakeoverHandoffRequest<'a> {
10    #[serde(skip_serializing_if = "Option::is_none")]
11    ttl_s: Option<u64>,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    tab_id: Option<&'a str>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17pub struct TakeoverHandoffResponse {
18    pub takeover_url: String,
19    pub takeover_url_expires_at_rfc3339: String,
20    pub takeover_url_ttl_s: u64,
21    pub takeover_url_scope: String,
22}
23
24impl Client {
25    /// Mint a short-lived URL capability for `/takeover/*`.
26    pub async fn takeover_handoff(
27        &self,
28        ttl_s: Option<u64>,
29        tab_id: Option<&str>,
30    ) -> Result<TakeoverHandoffResponse, Error> {
31        let endpoint = self.effective_endpoint().await?;
32        let base = endpoint.http_base();
33        let url = format!("{base}/takeover/handoff");
34        let body = TakeoverHandoffRequest { ttl_s, tab_id };
35        let mut req = self.http().post(&url).json(&body);
36        if let Some(token) = self.token() {
37            req = req.bearer_auth(token);
38        }
39        let resp = req
40            .send()
41            .await
42            .map_err(|e| Error::new(ErrorCode::HostUnreachable, format!("POST {url}: {e}")))?;
43        let status = resp.status();
44        let bytes = resp.bytes().await.map_err(|e| {
45            Error::new(
46                ErrorCode::InternalError,
47                format!("takeover_handoff: read response: {e}"),
48            )
49        })?;
50        if !status.is_success() {
51            if let Ok(err) = serde_json::from_slice::<Error>(&bytes) {
52                return Err(err);
53            }
54            return Err(Error::new(
55                ErrorCode::InternalError,
56                format!(
57                    "takeover_handoff: status {status}; failed to decode error envelope: {}",
58                    String::from_utf8_lossy(&bytes)
59                ),
60            ));
61        }
62        serde_json::from_slice::<TakeoverHandoffResponse>(&bytes).map_err(|e| {
63            Error::new(
64                ErrorCode::InternalError,
65                format!("takeover_handoff: decode response: {e}"),
66            )
67        })
68    }
69}