use rkyv::{Archive, Deserialize, Serialize};
use serde::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize};
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Archive,
Serialize,
Deserialize,
SerdeSerialize,
SerdeDeserialize,
clap::ValueEnum,
)]
#[serde(rename_all = "lowercase")]
#[clap(rename_all = "lower")]
#[derive(Default)]
#[repr(u8)]
pub enum EntryType {
Intent = 0,
#[default]
Discovery = 1,
Decision = 2,
Problem = 3,
Solution = 4,
Pattern = 5,
Warning = 6,
Success = 7,
Refactor = 8,
Bugfix = 9,
Feature = 10,
}
impl std::fmt::Display for EntryType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EntryType::Intent => write!(f, "intent"),
EntryType::Discovery => write!(f, "discovery"),
EntryType::Decision => write!(f, "decision"),
EntryType::Problem => write!(f, "problem"),
EntryType::Solution => write!(f, "solution"),
EntryType::Pattern => write!(f, "pattern"),
EntryType::Warning => write!(f, "warning"),
EntryType::Success => write!(f, "success"),
EntryType::Refactor => write!(f, "refactor"),
EntryType::Bugfix => write!(f, "bugfix"),
EntryType::Feature => write!(f, "feature"),
}
}
}
#[derive(Debug, Clone, Archive, Serialize, Deserialize, SerdeSerialize, SerdeDeserialize)]
pub struct MemoryEntry {
pub id: String,
pub agent_name: String,
pub entry_type: EntryType,
pub summary: String,
pub content: String,
pub embedding: Option<Vec<f32>>,
pub timestamp: i64,
pub checksum: u32,
pub prev_checksum: u32,
}
impl MemoryEntry {
#[allow(clippy::too_many_arguments)]
pub fn compute_checksum(
id: &str,
agent_name: &str,
entry_type: EntryType,
summary: &str,
content: &str,
timestamp: i64,
prev_checksum: u32,
embedding: Option<&[f32]>,
) -> u32 {
let mut hasher = crc32fast::Hasher::new();
hasher.update(id.as_bytes());
hasher.update(agent_name.as_bytes());
hasher.update(&[entry_type as u8]);
hasher.update(summary.as_bytes());
hasher.update(content.as_bytes());
hasher.update(×tamp.to_le_bytes());
hasher.update(&prev_checksum.to_le_bytes());
match embedding {
Some(values) => {
hasher.update(&[1]);
hasher.update(&(values.len() as u32).to_le_bytes());
for value in values {
hasher.update(&value.to_bits().to_le_bytes());
}
}
None => hasher.update(&[0]),
}
hasher.finalize()
}
pub fn new(
agent_name: String,
entry_type: EntryType,
summary: String,
content: String,
prev_checksum: u32,
) -> Self {
let id = uuid::Uuid::new_v4().to_string();
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64;
let checksum = Self::compute_checksum(
&id,
&agent_name,
entry_type,
&summary,
&content,
timestamp,
prev_checksum,
None,
);
Self {
id,
agent_name,
entry_type,
summary,
content,
embedding: None,
timestamp,
checksum,
prev_checksum,
}
}
}
#[derive(Debug, Clone, SerdeSerialize, SerdeDeserialize)]
pub struct SearchResult {
pub id: String,
pub entry: MemoryEntry,
pub score: f32,
}
#[derive(Debug, Clone, SerdeSerialize, SerdeDeserialize, Default)]
pub struct MemoryStats {
pub total_entries: u64,
pub file_size_bytes: u64,
pub oldest_timestamp: Option<i64>,
pub newest_timestamp: Option<i64>,
}
#[derive(Debug, Clone, SerdeSerialize, SerdeDeserialize)]
pub struct TimelineOptions {
pub limit: usize,
pub since: Option<i64>,
pub until: Option<i64>,
pub reverse: bool,
pub agent_name: Option<String>,
}
impl Default for TimelineOptions {
fn default() -> Self {
Self {
limit: 20,
since: None,
until: None,
reverse: true,
agent_name: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, SerdeSerialize, SerdeDeserialize, Default)]
pub enum DurabilityMode {
#[default]
Fsync,
FlushOnly,
None,
}
#[derive(Debug, Clone, SerdeSerialize, SerdeDeserialize)]
pub struct Config {
pub max_entries: Option<u64>,
pub durability: DurabilityMode,
pub model_id: String,
}
impl Default for Config {
fn default() -> Self {
Self {
max_entries: None,
durability: DurabilityMode::default(),
model_id: crate::constants::DEFAULT_MODEL_ID.to_string(),
}
}
}