use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct MemoryEntry {
#[serde(default)]
pub id: String,
pub content: String,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub source: Option<String>,
pub created_ms: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_ms: Option<i64>,
}
impl MemoryEntry {
pub fn new(content: impl Into<String>) -> Self {
Self {
id: String::new(),
content: content.into(),
tags: Vec::new(),
source: None,
created_ms: 0,
expires_ms: None,
}
}
pub fn with_tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.tags = tags.into_iter().map(Into::into).collect();
self
}
pub fn with_source(mut self, source: impl Into<String>) -> Self {
self.source = Some(source.into());
self
}
pub fn with_ttl_days(mut self, days: u32) -> Self {
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as i64)
.unwrap_or(0);
self.expires_ms = Some(now_ms + (days as i64) * 86_400_000);
self
}
pub fn is_expired(&self, now_ms: i64) -> bool {
matches!(self.expires_ms, Some(t) if t <= now_ms)
}
}
#[async_trait::async_trait]
pub trait Memory: Send + Sync {
async fn recall(&self, query: &str, k: usize) -> Result<Vec<MemoryEntry>, MemoryError>;
async fn write(&self, entry: MemoryEntry) -> Result<(), MemoryError>;
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum MemoryError {
#[error("memory io: {0}")]
Io(String),
#[error("memory backend: {0}")]
Backend(String),
#[error("memory serde: {0}")]
Serde(String),
}