mantr_sdk/
lib.rs

1//! Mantr Rust SDK - Deterministic Semantic Memory for AI
2
3use reqwest::Client as HttpClient;
4use serde::{Deserialize, Serialize};
5use std::time::Duration;
6use thiserror::Error;
7
8#[derive(Error, Debug)]
9pub enum MantrError {
10    #[error("Authentication failed: invalid API key")]
11    Authentication,
12
13    #[error("Insufficient credits")]
14    InsufficientCredits,
15
16    #[error("Rate limit exceeded")]
17    RateLimit,
18
19    #[error("API error: {0}")]
20    Api(String),
21
22    #[error("Network error: {0}")]
23    Network(#[from] reqwest::Error),
24}
25
26#[derive(Debug, Serialize)]
27pub struct WalkRequest {
28    pub phonemes: Vec<String>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub pod: Option<String>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub depth: Option<u32>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub limit: Option<u32>,
35}
36
37#[derive(Debug, Deserialize)]
38pub struct PathResult {
39    pub nodes: Vec<String>,
40    pub score: f64,
41    pub depth: u32,
42}
43
44#[derive(Debug, Deserialize)]
45pub struct WalkResponse {
46    pub paths: Vec<PathResult>,
47    pub latency_us: u64,
48    pub credits_used: u32,
49}
50
51/// Mantr API client
52pub struct MantrClient {
53    api_key: String,
54    base_url: String,
55    client: HttpClient,
56}
57
58impl MantrClient {
59    /// Create a new Mantr client
60    ///
61    /// # Arguments
62    /// * `api_key` - Your Mantr API key (starts with 'vak_')
63    ///
64    /// # Examples
65    /// ```no_run
66    /// use mantr_sdk::MantrClient;
67    ///
68    /// let client = MantrClient::new("vak_live_...")?;
69    /// ```
70    pub fn new(api_key: impl Into<String>) -> Result<Self, MantrError> {
71        let api_key = api_key.into();
72
73        if !api_key.starts_with("vak_") {
74            return Err(MantrError::Api(
75                "Invalid API key format. Must start with 'vak_'".to_string(),
76            ));
77        }
78
79        Ok(Self {
80            api_key,
81            base_url: "https://api.mantr.net".to_string(),
82            client: HttpClient::builder()
83                .timeout(Duration::from_secs(30))
84                .build()?,
85        })
86    }
87
88    /// Traverse the semantic graph
89    ///
90    /// # Examples
91    /// ```no_run
92    /// use mantr_sdk::{MantrClient, WalkRequest};
93    ///
94    /// #[tokio::main]
95    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
96    ///     let client = MantrClient::new("vak_live_...")?;
97    ///     
98    ///     let result = client.walk(WalkRequest {
99    ///         phonemes: vec!["dharma".to_string(), "karma".to_string()],
100    ///         pod: None,
101    ///         depth: Some(3),
102    ///         limit: Some(100),
103    ///     }).await?;
104    ///     
105    ///     println!("Found {} paths", result.paths.len());
106    ///     Ok(())
107    /// }
108    /// ```
109    pub async fn walk(&self, request: WalkRequest) -> Result<WalkResponse, MantrError> {
110        let resp = self
111            .client
112            .post(format!("{}/v1/walk", self.base_url))
113            .header("Authorization", format!("Bearer {}", self.api_key))
114            .header("User-Agent", "mantr-rust/1.0.0")
115            .json(&request)
116            .send()
117            .await?;
118
119        match resp.status().as_u16() {
120            401 => Err(MantrError::Authentication),
121            402 => Err(MantrError::InsufficientCredits),
122            429 => Err(MantrError::RateLimit),
123            200 => Ok(resp.json().await?),
124            status => Err(MantrError::Api(format!("HTTP {}", status))),
125        }
126    }
127}