use crate::embedding_profiles::EmbeddingRole;
use crate::errors::Error;
use crate::memory::lifecycle::{MemoryImportance, MemoryStatus, MemoryType};
use crate::memory_types::{AddResult, BatchIngestItemResult, BatchIngestResult, IngestPolicy};
use crate::sqlite::NewRow;
use super::NewMemory;
use super::store::MemoryStore;
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct NewSupersedingMemory<'a> {
pub row: NewMemory<'a>,
pub old_id: &'a str,
pub importance: MemoryImportance,
}
impl<'a> NewSupersedingMemory<'a> {
#[must_use = "the composed row is the argument supersede_with takes"]
pub fn new(row: NewMemory<'a>, old_id: &'a str) -> Self {
Self {
row,
old_id,
importance: MemoryImportance::default(),
}
}
#[must_use = "the returned row carries the level; the setter does not mutate in place"]
pub fn with_importance(mut self, importance: MemoryImportance) -> Self {
self.importance = importance;
self
}
}
impl MemoryStore {
#[must_use = "handle the error or results may be lost"]
pub fn batch_ingest(
&mut self,
project_id: &str,
items: Vec<(&str, Option<&str>)>,
policy: IngestPolicy,
) -> Result<BatchIngestResult, Error> {
let mut results = Vec::with_capacity(items.len());
let force = matches!(policy, IngestPolicy::Force);
for (content, metadata) in items {
let item_result = match Self::validate_input_length(content) {
Err(e) => BatchIngestItemResult::Error {
message: format!("{}", e),
},
Ok(()) => match self.add_with_conflict(
project_id,
content,
metadata,
force,
MemoryType::Fact,
MemoryStatus::Active,
) {
Ok(AddResult::Added { id }) => BatchIngestItemResult::Added { id },
Ok(AddResult::Conflicts {
proposed,
conflicts,
}) => BatchIngestItemResult::Conflicts {
proposed,
conflicts,
},
Err(e) => BatchIngestItemResult::Error {
message: format!("{}", e),
},
},
};
results.push(item_result);
}
Ok(BatchIngestResult { results })
}
#[allow(dead_code)] #[must_use = "the new memory ID is needed for downstream operations"]
pub fn supersede(
&mut self,
project_id: &str,
content: &str,
metadata: Option<&str>,
memory_type: MemoryType,
old_id: &str,
) -> Result<String, Error> {
self.supersede_with(&NewSupersedingMemory::new(
NewMemory {
project_id,
content,
metadata,
memory_type,
status: MemoryStatus::Active,
},
old_id,
))
}
#[must_use = "the new memory ID is needed for downstream operations"]
pub fn supersede_with(&mut self, row: &NewSupersedingMemory<'_>) -> Result<String, Error> {
row.row.status.ensure_valid_for_insert()?;
Self::validate_input_length(row.row.content)?;
Self::validate_metadata(row.row.metadata)?;
let embedding = self.get_embedding(row.row.content, EmbeddingRole::Passage)?;
Ok(self.db.supersede_with_importance(
&NewRow {
project_id: row.row.project_id,
content: row.row.content,
metadata: row.row.metadata,
memory_type: row.row.memory_type.as_str(),
status: row.row.status.as_str(),
importance: row.importance.as_str(),
},
&embedding,
row.old_id,
)?)
}
#[cfg_attr(not(test), allow(dead_code))] #[must_use = "the new memory ID is needed for downstream operations"]
pub fn supersede_with_importance(
&mut self,
project_id: &str,
content: &str,
metadata: Option<&str>,
memory_type: MemoryType,
old_id: &str,
importance: MemoryImportance,
) -> Result<String, Error> {
self.supersede_with(
&NewSupersedingMemory::new(
NewMemory {
project_id,
content,
metadata,
memory_type,
status: MemoryStatus::Active,
},
old_id,
)
.with_importance(importance),
)
}
}