devist 0.15.0

Project bootstrap CLI for AI-assisted development. Spin up new projects from templates, manage backends, and keep your codebase comprehensible.
#![allow(dead_code)]
// Minimal mem0 Cloud HTTP client (https://docs.mem0.ai)

use anyhow::{anyhow, Context, Result};
use reqwest::blocking::Client;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::time::Duration;

const BASE_URL: &str = "https://api.mem0.ai";

pub struct Mem0Client {
    http: Client,
    api_key: String,
    user_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Memory {
    #[serde(default)]
    pub id: String,
    #[serde(default)]
    pub memory: String,
    #[serde(default)]
    pub score: Option<f64>,
    #[serde(default)]
    pub metadata: Option<Value>,
}

impl Mem0Client {
    pub fn new(api_key: impl Into<String>, user_id: impl Into<String>) -> Result<Self> {
        let http = Client::builder().timeout(Duration::from_secs(20)).build()?;
        Ok(Self {
            http,
            api_key: api_key.into(),
            user_id: user_id.into(),
        })
    }

    /// Add a memory. `text` becomes a single user message.
    /// Returns the response payload (caller can extract IDs if needed).
    pub fn add(&self, text: &str, metadata: Option<Value>) -> Result<Value> {
        let body = json!({
            "messages": [{ "role": "user", "content": text }],
            "user_id": self.user_id,
            "metadata": metadata.unwrap_or(json!({})),
        });
        let resp = self
            .http
            .post(format!("{}/v1/memories/", BASE_URL))
            .header("Authorization", format!("Token {}", self.api_key))
            .header("Content-Type", "application/json")
            .json(&body)
            .send()
            .context("mem0 add: request failed")?;

        let status = resp.status();
        let payload: Value = resp
            .json()
            .unwrap_or_else(|_| json!({"error": "non-json response"}));
        if !status.is_success() {
            return Err(anyhow!("mem0 add HTTP {}: {}", status, payload));
        }
        Ok(payload)
    }

    /// Semantic search. Returns top-N memories.
    pub fn search(&self, query: &str, limit: usize) -> Result<Vec<Memory>> {
        let body = json!({
            "query": query,
            "user_id": self.user_id,
            "limit": limit,
        });
        let resp = self
            .http
            .post(format!("{}/v1/memories/search/", BASE_URL))
            .header("Authorization", format!("Token {}", self.api_key))
            .header("Content-Type", "application/json")
            .json(&body)
            .send()
            .context("mem0 search: request failed")?;

        let status = resp.status();
        if !status.is_success() {
            let txt = resp.text().unwrap_or_default();
            return Err(anyhow!("mem0 search HTTP {}: {}", status, txt));
        }

        let payload: Value = resp.json().context("mem0 search: parse json")?;
        // Response may be { "results": [...] } or a bare array. Handle both.
        let arr = payload
            .get("results")
            .cloned()
            .or_else(|| Some(payload.clone()))
            .and_then(|v| v.as_array().cloned())
            .unwrap_or_default();

        let mut out = Vec::with_capacity(arr.len());
        for v in arr {
            if let Ok(m) = serde_json::from_value::<Memory>(v) {
                out.push(m);
            }
        }
        Ok(out)
    }
}