use crate::{
context::ConversationReplayLimits,
providers::ProviderRequest,
sessions::{Session, SessionEvent, SessionEventKind},
};
use sha2::{Digest, Sha256};
pub(crate) struct CachedCompactionRequest {
provider: String,
request: ProviderRequest,
history_digest: [u8; 32],
}
impl CachedCompactionRequest {
pub(crate) fn remember(session: &Session, provider: &str, request: ProviderRequest) {
let generation = session.replay_generation();
let limits = ConversationReplayLimits::default();
let Ok(snapshot) = session.read_events_tolerant_bounded(limits.max_lines, limits.max_bytes)
else {
return;
};
let Some(history_digest) = history_digest(&snapshot.events) else {
return;
};
if !snapshot.diagnostics.is_empty() || session.replay_generation() != generation {
return;
}
if let Ok(mut cached) = session.compaction_request.lock() {
*cached = Some(Self {
provider: provider.to_string(),
request,
history_digest,
});
}
}
pub(super) fn take_matching(
session: &Session,
provider: &str,
model: &str,
events: &[SessionEvent],
) -> Option<ProviderRequest> {
let cached = session.compaction_request.lock().ok()?.take()?;
(cached.provider == provider
&& cached.request.model == model
&& Some(cached.history_digest) == history_digest(events))
.then_some(cached.request)
}
}
fn history_digest(events: &[SessionEvent]) -> Option<[u8; 32]> {
let mut digest = Sha256::new();
for event in events {
if matches!(
event.kind(),
Some(
SessionEventKind::SessionUsage
| SessionEventKind::SessionActiveTime
| SessionEventKind::SessionTitle
| SessionEventKind::ContextCache
| SessionEventKind::ProviderStreamTrace
| SessionEventKind::HookLifecycle
| SessionEventKind::HookDiagnostic
| SessionEventKind::ToolDisplayResult
| SessionEventKind::ToolAppliedPreview
)
) {
continue;
}
digest.update(serde_json::to_vec(event).ok()?);
digest.update(b"\n");
}
Some(digest.finalize().into())
}