horizons-ai 0.1.2-dev.0

Async Rust SDK for the Horizons REST API
Documentation
use crate::{ActionProposal, HorizonsClient, HorizonsError, RiskLevel};
use reqwest::Method;
use serde::Serialize;
use serde_json::Value;
use uuid::Uuid;

#[derive(Debug, Clone)]
pub struct ActionsApi {
    client: HorizonsClient,
}

impl ActionsApi {
    pub(crate) fn new(client: HorizonsClient) -> Self {
        Self { client }
    }

    pub async fn propose(
        &self,
        agent_id: impl Into<String>,
        action_type: impl Into<String>,
        payload: Value,
        risk_level: RiskLevel,
        dedupe_key: Option<String>,
        context: Value,
        ttl_seconds: Option<i64>,
        project_id: Option<Uuid>,
    ) -> Result<Uuid, HorizonsError> {
        #[derive(Serialize)]
        struct Body {
            agent_id: String,
            action_type: String,
            payload: Value,
            risk_level: RiskLevel,
            context: Value,
            #[serde(skip_serializing_if = "Option::is_none")]
            dedupe_key: Option<String>,
            #[serde(skip_serializing_if = "Option::is_none")]
            ttl_seconds: Option<i64>,
            #[serde(skip_serializing_if = "Option::is_none")]
            project_id: Option<String>,
        }
        let body = Body {
            agent_id: agent_id.into(),
            action_type: action_type.into(),
            payload,
            risk_level,
            context,
            dedupe_key,
            ttl_seconds,
            project_id: project_id.map(|u| u.to_string()),
        };
        let v = self
            .client
            .request_value(Method::POST, "/api/v1/actions/propose", None::<&()>, Some(&body))
            .await?;
        let s = v
            .get("action_id")
            .and_then(|x| x.as_str())
            .ok_or_else(|| crate::HorizonsError::new(crate::HorizonsErrorKind::Serialization, None, "missing action_id"))?;
        Ok(Uuid::parse_str(s).map_err(|e| crate::HorizonsError::new(crate::HorizonsErrorKind::Serialization, None, e.to_string()))?)
    }

    pub async fn approve(
        &self,
        action_id: Uuid,
        reason: impl Into<String>,
        project_id: Option<Uuid>,
    ) -> Result<Value, HorizonsError> {
        #[derive(Serialize)]
        struct Body {
            reason: String,
            #[serde(skip_serializing_if = "Option::is_none")]
            project_id: Option<String>,
        }
        let body = Body {
            reason: reason.into(),
            project_id: project_id.map(|u| u.to_string()),
        };
        self.client
            .request_value(
                Method::POST,
                &format!("/api/v1/actions/{action_id}/approve"),
                None::<&()>,
                Some(&body),
            )
            .await
    }

    pub async fn deny(
        &self,
        action_id: Uuid,
        reason: impl Into<String>,
        project_id: Option<Uuid>,
    ) -> Result<Value, HorizonsError> {
        #[derive(Serialize)]
        struct Body {
            reason: String,
            #[serde(skip_serializing_if = "Option::is_none")]
            project_id: Option<String>,
        }
        let body = Body {
            reason: reason.into(),
            project_id: project_id.map(|u| u.to_string()),
        };
        self.client
            .request_value(
                Method::POST,
                &format!("/api/v1/actions/{action_id}/deny"),
                None::<&()>,
                Some(&body),
            )
            .await
    }

    pub async fn pending(
        &self,
        project_id: Option<Uuid>,
        limit: i64,
        offset: i64,
    ) -> Result<Vec<ActionProposal>, HorizonsError> {
        #[derive(Serialize)]
        struct Query {
            #[serde(skip_serializing_if = "Option::is_none")]
            project_id: Option<String>,
            limit: i64,
            offset: i64,
        }
        let q = Query {
            project_id: project_id.map(|u| u.to_string()),
            limit,
            offset,
        };
        self.client
            .request_json(Method::GET, "/api/v1/actions/pending", Some(&q), None::<&()>)
            .await
    }
}