Skip to main content

agent_first_http/sdk/
health.rs

1//! `/health` client. Hits the host's HTTP endpoint and returns the parsed
2//! shape from `architecture.md §6`.
3
4use serde::{Deserialize, Serialize};
5
6use crate::sdk::client::Client;
7use crate::shared::error::{Error, ErrorCode};
8use crate::shared::profile_snapshot::ProfileSnapshot;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct HealthResponse {
12    pub code: String,
13    pub status: String,
14    pub version: String,
15    pub uptime_s: u64,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub backend: Option<BackendInfo>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub backend_error: Option<BackendError>,
20    /// Snapshot of the active/default profile.
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub profile: Option<ProfileSnapshot>,
23    #[serde(default)]
24    pub tabs_active: u32,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub capabilities_url: Option<String>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct BackendInfo {
31    pub family: String,
32    pub version: String,
33    pub connected: bool,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct BackendError {
38    pub error_code: ErrorCode,
39    pub error: String,
40}
41
42impl Client {
43    /// Fetch `/health`. Returns an error wrapped with
44    /// `ErrorCode::HostUnreachable` on transport failure.
45    pub async fn health(&self) -> Result<HealthResponse, Error> {
46        let endpoint = self.effective_endpoint().await?;
47        let base = endpoint.http_base();
48        let url = format!("{base}/health");
49        let mut req = self.http().get(&url);
50        if let Some(token) = self.token() {
51            req = req.bearer_auth(token);
52        }
53        let resp = req
54            .send()
55            .await
56            .map_err(|e| Error::new(ErrorCode::HostUnreachable, format!("GET {url}: {e}")))?;
57        let status = resp.status();
58        let bytes = resp.bytes().await.map_err(|e| {
59            Error::new(
60                ErrorCode::InternalError,
61                format!("health: read response: {e}"),
62            )
63        })?;
64        if !status.is_success() {
65            if let Ok(err) = crate::shared::afdata::decode_error(&bytes) {
66                return Err(err);
67            }
68            return Err(Error::new(
69                ErrorCode::InternalError,
70                format!(
71                    "health: status {status}; failed to decode error envelope: {}",
72                    String::from_utf8_lossy(&bytes)
73                ),
74            ));
75        }
76        crate::shared::afdata::decode_result(&bytes)
77    }
78}