use std::collections::BTreeMap;
use rho_sdk::{CompactionState, Revision, SessionId, SessionSnapshot};
use serde::{Deserialize, Serialize};
use rho_providers::model::{Message, ModelIdentity};
#[derive(Clone, Debug)]
pub(super) struct SnapshotDeltaBase<'a> {
session_id: &'a SessionId,
revision: Revision,
history: &'a [Message],
compaction: &'a CompactionState,
}
impl<'a> SnapshotDeltaBase<'a> {
pub(super) fn from_snapshot(snapshot: &'a SessionSnapshot) -> Self {
Self {
session_id: snapshot.session_id(),
revision: snapshot.revision(),
history: snapshot.history(),
compaction: snapshot.compaction(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct StoredSnapshotDelta {
base_revision: Revision,
session_id: SessionId,
revision: Revision,
appended_history: Vec<Message>,
provider: ModelIdentity,
compaction: CompactionState,
metadata: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
prompt_cache_key: Option<String>,
}
#[derive(Clone, Debug)]
pub(super) struct SnapshotReplay {
pub(super) header: SessionSnapshot,
pub(super) history: Vec<Message>,
}
impl SnapshotReplay {
pub(super) fn new(snapshot: &SessionSnapshot, history: Vec<Message>) -> Self {
let mut header = SessionSnapshot::new(
snapshot.session_id().clone(),
snapshot.revision(),
Vec::new(),
snapshot.provider().clone(),
snapshot.compaction().clone(),
);
for (key, value) in snapshot.metadata() {
header = header.with_metadata(key.clone(), value.clone());
}
if let Some(key) = snapshot.prompt_cache_key() {
header = header.with_prompt_cache_key(key);
}
Self { header, history }
}
pub(super) fn into_snapshot(self) -> SessionSnapshot {
let mut snapshot = SessionSnapshot::new(
self.header.session_id().clone(),
self.header.revision(),
self.history,
self.header.provider().clone(),
self.header.compaction().clone(),
);
for (key, value) in self.header.metadata() {
snapshot = snapshot.with_metadata(key.clone(), value.clone());
}
if let Some(key) = self.header.prompt_cache_key() {
snapshot = snapshot.with_prompt_cache_key(key);
}
snapshot
}
}
impl StoredSnapshotDelta {
pub(super) fn after(base: &SnapshotDeltaBase<'_>, current: &SessionSnapshot) -> Option<Self> {
if base.session_id != current.session_id()
|| base.revision > current.revision()
|| base.compaction != current.compaction()
|| base.history.len() > current.history().len()
|| current.history().get(..base.history.len()) != Some(base.history)
{
return None;
}
let appended_history = current.history()[base.history.len()..].to_vec();
Some(Self {
base_revision: base.revision,
session_id: current.session_id().clone(),
revision: current.revision(),
appended_history,
provider: current.provider().clone(),
compaction: current.compaction().clone(),
metadata: current.metadata().clone(),
prompt_cache_key: current.prompt_cache_key().map(str::to_owned),
})
}
pub(super) fn restore(&self, previous: &SessionSnapshot) -> anyhow::Result<SessionSnapshot> {
let mut replay = SnapshotReplay::new(previous, previous.history().to_vec());
self.apply(&mut replay)?;
Ok(replay.into_snapshot())
}
pub(super) fn apply(&self, replay: &mut SnapshotReplay) -> anyhow::Result<bool> {
let previous = &replay.header;
if previous.session_id() != &self.session_id {
anyhow::bail!(
"snapshot delta session id '{}' does not match base session id '{}'",
self.session_id,
previous.session_id()
);
}
if previous.revision() != self.base_revision {
anyhow::bail!(
"snapshot delta base revision {} does not match previous revision {}",
self.base_revision,
previous.revision()
);
}
if self.revision < self.base_revision {
anyhow::bail!(
"snapshot delta revision {} precedes base revision {}",
self.revision,
self.base_revision
);
}
let mut snapshot = SessionSnapshot::new(
self.session_id.clone(),
self.revision,
Vec::new(),
self.provider.clone(),
self.compaction.clone(),
);
for (key, value) in &self.metadata {
snapshot = snapshot.with_metadata(key.clone(), value.clone());
}
if let Some(prompt_cache_key) = &self.prompt_cache_key {
snapshot = snapshot.with_prompt_cache_key(prompt_cache_key);
}
let changed = snapshot != replay.header || !self.appended_history.is_empty();
replay.history.extend_from_slice(&self.appended_history);
replay.header = snapshot;
Ok(changed)
}
}