use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::SqlitePool;
use uuid::Uuid;
use crate::error::{ClawError, ClawResult};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActiveMemoryRecord {
pub id: Uuid,
pub agent_id: String,
pub key: String,
pub value: serde_json::Value,
pub updated_at: DateTime<Utc>,
pub expires_at: Option<DateTime<Utc>>,
}
#[derive(Debug)]
pub struct ActiveMemoryStore<'a> {
pool: &'a SqlitePool,
}
impl<'a> ActiveMemoryStore<'a> {
pub fn new(pool: &'a SqlitePool) -> Self {
ActiveMemoryStore { pool }
}
pub async fn upsert(&self, record: &ActiveMemoryRecord) -> ClawResult<()> {
sqlx::query(
r#"
INSERT INTO active_memory (id, agent_id, key, value, updated_at, expires_at)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT (agent_id, key)
DO UPDATE SET value = excluded.value,
updated_at = excluded.updated_at,
expires_at = excluded.expires_at
"#,
)
.bind(record.id.to_string())
.bind(&record.agent_id)
.bind(&record.key)
.bind(serde_json::to_string(&record.value)?)
.bind(record.updated_at.to_rfc3339())
.bind(record.expires_at.map(|t| t.to_rfc3339()))
.execute(self.pool)
.await?;
Ok(())
}
pub async fn get(&self, agent_id: &str, key: &str) -> ClawResult<Option<ActiveMemoryRecord>> {
let row = sqlx::query_as::<_, (String, String, String, String, String, Option<String>)>(
"SELECT id, agent_id, key, value, updated_at, expires_at \
FROM active_memory WHERE agent_id = ? AND key = ?",
)
.bind(agent_id)
.bind(key)
.fetch_optional(self.pool)
.await?;
row.map(|(id, agent_id, key, value, updated_at, expires_at)| {
Ok(ActiveMemoryRecord {
id: Uuid::parse_str(&id).map_err(|e| ClawError::Store(e.to_string()))?,
agent_id,
key,
value: serde_json::from_str(&value)?,
updated_at: DateTime::parse_from_rfc3339(&updated_at)
.map_err(|e| ClawError::Store(e.to_string()))?
.with_timezone(&Utc),
expires_at: expires_at
.map(|s| {
DateTime::parse_from_rfc3339(&s)
.map(|dt| dt.with_timezone(&Utc))
.map_err(|e| ClawError::Store(e.to_string()))
})
.transpose()?,
})
})
.transpose()
}
pub async fn clear_agent(&self, agent_id: &str) -> ClawResult<u64> {
let result = sqlx::query("DELETE FROM active_memory WHERE agent_id = ?")
.bind(agent_id)
.execute(self.pool)
.await?;
Ok(result.rows_affected())
}
}