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    #[serde(alias = "takeover_url")]
19    pub takeover_url_secret: String,
20    pub takeover_url_expires_at_rfc3339: String,
21    pub takeover_url_ttl_s: u64,
22    pub takeover_url_scope: String,
23}
24
25impl Client {
26    /// Mint a short-lived URL capability for `/takeover/*`.
27    pub async fn takeover_handoff(
28        &self,
29        ttl_s: Option<u64>,
30        tab_id: Option<&str>,
31    ) -> Result<TakeoverHandoffResponse, Error> {
32        let endpoint = self.effective_endpoint().await?;
33        let base = endpoint.http_base();
34        let url = format!("{base}/takeover/handoff");
35        let body = TakeoverHandoffRequest { ttl_s, tab_id };
36        let mut req = self.http().post(&url).json(&body);
37        if let Some(token) = self.token() {
38            req = req.bearer_auth(token);
39        }
40        let resp = req
41            .send()
42            .await
43            .map_err(|e| Error::new(ErrorCode::HostUnreachable, format!("POST {url}: {e}")))?;
44        let status = resp.status();
45        let bytes = resp.bytes().await.map_err(|e| {
46            Error::new(
47                ErrorCode::InternalError,
48                format!("takeover_handoff: read response: {e}"),
49            )
50        })?;
51        if !status.is_success() {
52            if let Ok(err) = crate::shared::afdata::decode_error(&bytes) {
53                return Err(err);
54            }
55            return Err(Error::new(
56                ErrorCode::InternalError,
57                format!(
58                    "takeover_handoff: status {status}; failed to decode error envelope: {}",
59                    String::from_utf8_lossy(&bytes)
60                ),
61            ));
62        }
63        crate::shared::afdata::decode_result(&bytes)
64    }
65}