agent_first_http/sdk/
capabilities.rs1use 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 display_takeover: bool,
19 pub ops_panel: OpsPanelSupport,
20 pub profile: ProfileSupport,
21 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
22 pub features: BTreeMap<String, FeatureSupport>,
23 pub limits: BTreeMap<String, serde_json::Value>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct BackendFamily {
28 pub family: String,
29 pub version: String,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ArtifactSupport {
34 pub supported: bool,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub source: Option<String>,
37 #[serde(default, skip_serializing_if = "Vec::is_empty")]
38 pub body_capture: Vec<String>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct OpsPanelSupport {
43 pub supported: bool,
44 pub screencast: bool,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct ProfileSupport {
49 pub persistent: bool,
50 pub ephemeral: bool,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct FeatureSupport {
55 pub supported: bool,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub detail: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub risk: Option<String>,
60}
61
62impl Client {
63 pub async fn capabilities(&self) -> Result<CapabilitiesResponse, Error> {
64 let endpoint = self.effective_endpoint().await?;
65 let base = endpoint.http_base();
66 let url = format!("{base}/capabilities");
67 let mut req = self.http().get(&url);
68 if let Some(token) = self.token() {
69 req = req.bearer_auth(token);
70 }
71 let resp = req
72 .send()
73 .await
74 .map_err(|e| Error::new(ErrorCode::HostUnreachable, format!("GET {url}: {e}")))?;
75 let status = resp.status();
76 let bytes = resp.bytes().await.map_err(|e| {
77 Error::new(
78 ErrorCode::InternalError,
79 format!("capabilities: read response: {e}"),
80 )
81 })?;
82 if !status.is_success() {
83 if let Ok(err) = serde_json::from_slice::<Error>(&bytes) {
84 return Err(err);
85 }
86 return Err(Error::new(
87 ErrorCode::InternalError,
88 format!(
89 "capabilities: status {status}; failed to decode error envelope: {}",
90 String::from_utf8_lossy(&bytes)
91 ),
92 ));
93 }
94 serde_json::from_slice::<CapabilitiesResponse>(&bytes).map_err(|e| {
95 Error::new(
96 ErrorCode::InternalError,
97 format!("capabilities: decode response: {e}"),
98 )
99 })
100 }
101
102 pub fn cdp(&self, method: impl Into<String>) -> crate::sdk::cdp::CdpBuilder {
104 crate::sdk::cdp::CdpBuilder::new(self.clone(), method)
105 }
106}