1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! HTTP client for fetching status data from the governance gateway.
use reqwest::Client;
use super::models::{
AdminStatusResponse, AgentResponse, ApprovalResponse, CostResponse, HealthResponse, HealthzResponse,
PaginatedResponse,
};
use crate::error::CliError;
/// Client for making status-related API requests.
pub struct StatusClient {
base_url: String,
http: Client,
}
impl StatusClient {
/// Create a new `StatusClient` targeting the given gateway base URL.
pub fn new(base_url: &str) -> Self {
Self {
base_url: base_url.trim_end_matches('/').to_string(),
http: Client::new(),
}
}
/// Build a full URL for the given API path.
fn url(&self, path: &str) -> String {
format!("{}{}", self.base_url, path)
}
/// Return the base URL (for error messages).
#[allow(dead_code)]
pub fn base_url(&self) -> &str {
&self.base_url
}
/// Check gateway health via `GET /api/v1/health`.
pub async fn check_health(&self) -> Result<HealthResponse, CliError> {
let resp = self.http.get(self.url("/api/v1/health")).send().await?;
let body = resp.json::<HealthResponse>().await?;
Ok(body)
}
/// Fetch the storage-aware admin status block via
/// `GET /api/v1/admin/status` (AAASM-1591 / Epic 18 S-J).
///
/// Returns an error when the gateway is unreachable or returns a
/// body the CLI cannot decode; in particular, an older gateway that
/// does not yet expose this route will respond with a `404` whose
/// non-JSON body fails decoding. Callers map both failures to a
/// missing storage section in `aasm status` rather than surfacing
/// the error directly.
pub async fn fetch_admin_status(&self) -> Result<AdminStatusResponse, CliError> {
let resp = self.http.get(self.url("/api/v1/admin/status")).send().await?;
let body = resp.json::<AdminStatusResponse>().await?;
Ok(body)
}
/// Fetch the lightweight gateway liveness probe via `GET /healthz`.
///
/// Backs the deployment-overview section of `aasm status` — surfaces the
/// `mode`, `version`, `storage`, and `uptime_secs` fields published by
/// `aa-gateway::routes::healthz::healthz` regardless of deployment mode.
/// Returns an error when the gateway is unreachable or returns a body the
/// client cannot decode; callers map that to `health = "unreachable"`.
pub async fn check_healthz(&self) -> Result<HealthzResponse, CliError> {
let resp = self.http.get(self.url("/healthz")).send().await?;
let body = resp.json::<HealthzResponse>().await?;
Ok(body)
}
/// List all agents via `GET /api/v1/agents`.
pub async fn list_agents(&self) -> Result<Vec<AgentResponse>, CliError> {
let resp = self
.http
.get(self.url("/api/v1/agents"))
.query(&[("per_page", "100")])
.send()
.await?;
let body = resp.json::<PaginatedResponse<AgentResponse>>().await?;
Ok(body.items)
}
/// List all approvals via `GET /api/v1/approvals`.
pub async fn list_approvals(&self) -> Result<Vec<ApprovalResponse>, CliError> {
let resp = self
.http
.get(self.url("/api/v1/approvals"))
.query(&[("per_page", "100")])
.send()
.await?;
let body = resp.json::<PaginatedResponse<ApprovalResponse>>().await?;
Ok(body.items)
}
/// Fetch cost summary via `GET /api/v1/costs`.
pub async fn get_costs(&self) -> Result<CostResponse, CliError> {
let resp = self.http.get(self.url("/api/v1/costs")).send().await?;
let body = resp.json::<CostResponse>().await?;
Ok(body)
}
}