use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum MemoryType {
Episodic,
Semantic,
Procedural,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum MemoryDecayStrategy {
NoDecay,
LinearDecay,
AccessBasedDecay,
CustomDecay,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Memory {
pub id: Uuid,
pub paladin_id: String,
pub content: String,
pub memory_type: MemoryType,
pub importance: f32,
pub access_count: u32,
pub last_accessed: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub metadata: HashMap<String, Value>,
}
impl Memory {
pub fn increment_access(&mut self) {
self.access_count += 1;
self.last_accessed = Utc::now();
}
pub fn validate_importance(importance: f32) -> Result<(), String> {
if !(0.0..=1.0).contains(&importance) {
return Err(format!(
"Importance must be between 0.0 and 1.0, got: {}",
importance
));
}
Ok(())
}
}
pub struct MemoryBuilder {
paladin_id: String,
content: String,
memory_type: MemoryType,
importance: f32,
metadata: HashMap<String, Value>,
}
impl MemoryBuilder {
pub fn new(paladin_id: String, content: String) -> Self {
Self {
paladin_id,
content,
memory_type: MemoryType::Episodic,
importance: 0.5,
metadata: HashMap::new(),
}
}
pub fn memory_type(mut self, memory_type: MemoryType) -> Self {
self.memory_type = memory_type;
self
}
pub fn importance(mut self, importance: f32) -> Self {
self.importance = importance;
self
}
pub fn metadata(mut self, metadata: HashMap<String, Value>) -> Self {
self.metadata = metadata;
self
}
pub fn add_metadata(mut self, key: String, value: Value) -> Self {
self.metadata.insert(key, value);
self
}
pub fn build(self) -> Result<Memory, String> {
Memory::validate_importance(self.importance)?;
if self.content.is_empty() {
return Err("Content cannot be empty".to_string());
}
let now = Utc::now();
Ok(Memory {
id: Uuid::new_v4(),
paladin_id: self.paladin_id,
content: self.content,
memory_type: self.memory_type,
importance: self.importance,
access_count: 0,
last_accessed: now,
created_at: now,
metadata: self.metadata,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SanctumEntry {
pub memory: Memory,
pub embedding: Vec<f32>,
pub dimension: usize,
}
impl SanctumEntry {
pub fn new(memory: Memory, embedding: Vec<f32>) -> Result<Self, String> {
if embedding.is_empty() {
return Err("Embedding cannot be empty".to_string());
}
let dimension = embedding.len();
Ok(Self {
memory,
embedding,
dimension,
})
}
pub fn paladin_id(&self) -> &str {
&self.memory.paladin_id
}
pub fn id(&self) -> Uuid {
self.memory.id
}
pub fn validate_dimension(&self, expected_dimension: usize) -> Result<(), String> {
if self.dimension != expected_dimension {
return Err(format!(
"Embedding dimension {} does not match expected dimension {}",
self.dimension, expected_dimension
));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_type_default() {
let builder = MemoryBuilder::new("p1".to_string(), "content".to_string());
assert_eq!(builder.memory_type, MemoryType::Episodic);
}
#[test]
fn test_importance_default() {
let builder = MemoryBuilder::new("p1".to_string(), "content".to_string());
assert_eq!(builder.importance, 0.5);
}
#[test]
fn test_empty_content_error() {
let result = MemoryBuilder::new("p1".to_string(), "".to_string()).build();
assert!(result.is_err());
}
}