1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//! Bounded-memory session load using the tail-cap deserializer.
//!
//! `Session::load_tail` opens the session file with an incremental JSON
//! reader and keeps only the last `window` messages/tool uses, so resuming
//! a very large session does not allocate the full transcript.
use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use super::tail_seed::with_tail_cap;
use super::types::Session;
/// Result of a bounded session load.
#[derive(Debug)]
pub struct TailLoad {
/// The session with its message/tool_use vectors capped to the window.
pub session: Session,
/// Total number of messages + tool_use entries discarded from the tail
/// cap. Zero means the full transcript fit within the window.
pub dropped: usize,
/// Size of the session file on disk, in bytes.
pub file_bytes: u64,
}
impl Session {
/// Load a session by UUID, keeping only the last `window` messages and
/// tool uses. Use `window = 0` to drop all messages (cheap directory
/// scan).
///
/// # Errors
/// Returns an error if the file cannot be opened or the JSON is malformed.
pub async fn load_tail(id: &str, window: usize) -> Result<TailLoad> {
let path = Self::session_path(id)?;
Self::load_tail_from_path(path, window).await
}
/// Load a session from an explicit path with a bounded message window.
pub async fn load_tail_from_path(path: PathBuf, window: usize) -> Result<TailLoad> {
tokio::task::spawn_blocking(move || parse_tail(&path, window))
.await
.context("session tail-load task panicked")?
}
}
fn parse_tail(path: &Path, window: usize) -> Result<TailLoad> {
let file_bytes = std::fs::metadata(path).map(|m| m.len()).unwrap_or(0);
let file = File::open(path).with_context(|| format!("open session file {}", path.display()))?;
let reader = BufReader::with_capacity(64 * 1024, file);
let (parsed, dropped) = with_tail_cap(window, || {
serde_json::from_reader::<_, Session>(reader)
.with_context(|| format!("parse session file {}", path.display()))
});
Ok(TailLoad {
session: parsed?,
dropped,
file_bytes,
})
}