Skip to main content

agent_first_http/sdk/
capabilities.rs

1//! `/capabilities` client. Shape from `architecture.md §6`.
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::sdk::client::Client;
8use crate::shared::error::{Error, ErrorCode};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct CapabilitiesResponse {
12    pub code: String,
13    pub backend: BackendFamily,
14    pub artifacts: BTreeMap<String, ArtifactSupport>,
15    pub wait_modes: Vec<String>,
16    pub takeover: TakeoverSupport,
17    pub profile: ProfileSupport,
18    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
19    pub features: BTreeMap<String, FeatureSupport>,
20    pub limits: BTreeMap<String, serde_json::Value>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct BackendFamily {
25    pub family: String,
26    pub version: String,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ArtifactSupport {
31    pub supported: bool,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub source: Option<String>,
34    #[serde(default, skip_serializing_if = "Vec::is_empty")]
35    pub body_capture: Vec<String>,
36}
37
38/// Human-takeover panel support. `provider` names the concrete screen-share
39/// method (`kasmvnc`, …) — open-ended like `BackendFamily.family`, so adding a
40/// provider does not change this shape.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct TakeoverSupport {
43    /// Whether this backend can expose a takeover panel at all when the host is
44    /// started with `--takeover-provider <provider>` (false for lightpanda).
45    #[serde(default)]
46    pub backend_capable: bool,
47    /// Whether this host actually has a takeover panel enabled right now.
48    pub supported: bool,
49    #[serde(default)]
50    pub panel_url: Option<String>,
51    #[serde(default)]
52    pub provider: Option<String>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct ProfileSupport {
57    pub persistent: bool,
58    pub ephemeral: bool,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct FeatureSupport {
63    pub supported: bool,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub detail: Option<String>,
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub risk: Option<String>,
68}
69
70impl Client {
71    pub async fn capabilities(&self) -> Result<CapabilitiesResponse, Error> {
72        let endpoint = self.effective_endpoint().await?;
73        let base = endpoint.http_base();
74        let url = format!("{base}/capabilities");
75        let mut req = self.http().get(&url);
76        if let Some(token) = self.token() {
77            req = req.bearer_auth(token);
78        }
79        let resp = req
80            .send()
81            .await
82            .map_err(|e| Error::new(ErrorCode::HostUnreachable, format!("GET {url}: {e}")))?;
83        let status = resp.status();
84        let bytes = resp.bytes().await.map_err(|e| {
85            Error::new(
86                ErrorCode::InternalError,
87                format!("capabilities: read response: {e}"),
88            )
89        })?;
90        if !status.is_success() {
91            if let Ok(err) = serde_json::from_slice::<Error>(&bytes) {
92                return Err(err);
93            }
94            return Err(Error::new(
95                ErrorCode::InternalError,
96                format!(
97                    "capabilities: status {status}; failed to decode error envelope: {}",
98                    String::from_utf8_lossy(&bytes)
99                ),
100            ));
101        }
102        serde_json::from_slice::<CapabilitiesResponse>(&bytes).map_err(|e| {
103            Error::new(
104                ErrorCode::InternalError,
105                format!("capabilities: decode response: {e}"),
106            )
107        })
108    }
109
110    /// Build a raw CDP request. Returns a [`crate::sdk::cdp::CdpBuilder`].
111    pub fn cdp(&self, method: impl Into<String>) -> crate::sdk::cdp::CdpBuilder {
112        crate::sdk::cdp::CdpBuilder::new(self.clone(), method)
113    }
114}