use serde::de::DeserializeOwned;
use serde::Serialize;
use super::types::{
Agent, ApprovalRequest, ActionEventStatus, LoginResult, PepConfig, TenantRegistration,
};
use crate::SdkError;
#[derive(Debug, Clone)]
pub struct ControlPlaneClient {
base_url: String,
http: reqwest::Client,
api_key: Option<String>,
}
impl ControlPlaneClient {
pub fn new(base_url: impl Into<String>) -> Self {
Self::with_api_key(base_url, None)
}
pub fn with_api_key(base_url: impl Into<String>, api_key: Option<String>) -> Self {
let http = reqwest::Client::builder()
.cookie_store(true)
.build()
.expect("reqwest client with cookie store");
Self { base_url: base_url.into(), http, api_key }
}
pub async fn login(&self, email: &str, password: &str) -> Result<LoginResult, SdkError> {
let body = serde_json::json!({ "email": email, "password": password });
self.request(reqwest::Method::POST, "/auth/login", Some(&body)).await
}
async fn request<T: DeserializeOwned>(
&self,
method: reqwest::Method,
path: &str,
body: Option<&impl Serialize>,
) -> Result<T, SdkError> {
let url = format!("{}{}", self.base_url, path);
let mut req = self.http.request(method, &url);
if let Some(b) = body {
req = req.json(b);
}
if let Some(key) = &self.api_key {
req = req.header("x-cots-api-key", key);
}
let res = req.send().await?;
let status = res.status();
let text = res.text().await?;
if !status.is_success() {
return Err(SdkError::Api { status: status.as_u16(), body: text });
}
serde_json::from_str(&text).map_err(|e| SdkError::Api { status: status.as_u16(), body: e.to_string() })
}
pub async fn register_tenant(&self, name: &str, admin_password: Option<&str>) -> Result<TenantRegistration, SdkError> {
let mut body = serde_json::json!({ "name": name });
if let Some(pw) = admin_password {
body["admin_password"] = serde_json::json!(pw);
}
self.request(reqwest::Method::POST, "/tenants", Some(&body)).await
}
pub async fn register_agent(&self, name: &str) -> Result<Agent, SdkError> {
let body = serde_json::json!({ "name": name });
self.request(reqwest::Method::POST, "/agents", Some(&body)).await
}
pub async fn create_action_surface(
&self,
agent_id: &str,
target_system: &str,
allowed_action_types: &[&str],
) -> Result<serde_json::Value, SdkError> {
let body = serde_json::json!({
"agent_id": agent_id,
"target_system": target_system,
"allowed_action_types": allowed_action_types,
"sensitive_action_types": allowed_action_types,
});
self.request(reqwest::Method::POST, "/action-surfaces", Some(&body)).await
}
pub async fn activate_agent(&self, agent_id: &str) -> Result<PepConfig, SdkError> {
let empty = serde_json::json!({});
self.request(reqwest::Method::POST, &format!("/agents/{agent_id}/activate"), Some(&empty)).await
}
pub async fn get_action(&self, action_event_id: &str) -> Result<ActionEventStatus, SdkError> {
self.request::<ActionEventStatus>(reqwest::Method::GET, &format!("/actions/{action_event_id}"), None::<&()>).await
}
pub async fn get_approval_for_action(&self, action_event_id: &str) -> Result<ApprovalRequest, SdkError> {
self.request::<ApprovalRequest>(reqwest::Method::GET, &format!("/actions/{action_event_id}/approval"), None::<&()>).await
}
pub async fn list_pending_approvals(&self, tenant_id: &str) -> Result<Vec<ApprovalRequest>, SdkError> {
self.request::<Vec<ApprovalRequest>>(
reqwest::Method::GET,
&format!("/approvals?status=pending&tenant_id={tenant_id}"),
None::<&()>,
).await
}
pub async fn approve(&self, approval_request_id: &str, reason: &str) -> Result<serde_json::Value, SdkError> {
let body = serde_json::json!({ "decision_reason": reason });
self.request(reqwest::Method::POST, &format!("/approvals/{approval_request_id}/approve"), Some(&body)).await
}
pub async fn deny(&self, approval_request_id: &str, reason: &str) -> Result<serde_json::Value, SdkError> {
let body = serde_json::json!({ "decision_reason": reason });
self.request(reqwest::Method::POST, &format!("/approvals/{approval_request_id}/deny"), Some(&body)).await
}
}