Skip to main content

sac/sessions/
snapshot.rs

1use super::*;
2
3fn now_utc() -> String {
4    let d = std::time::SystemTime::now()
5        .duration_since(std::time::UNIX_EPOCH)
6        .unwrap_or_default();
7    let secs = d.as_secs();
8    let nanos = d.subsec_nanos();
9    let (days, rem) = (secs / 86_400, secs % 86_400);
10    let (h, rem) = (rem / 3_600, rem % 3_600);
11    let (m, s) = (rem / 60, rem % 60);
12    let (mut y, mut mo, mut day) = (1970i64, 1u32, 1u32);
13    let mut remaining = days as i64;
14    loop {
15        let yd = if y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) {
16            366
17        } else {
18            365
19        };
20        if remaining < yd {
21            break;
22        }
23        remaining -= yd;
24        y += 1;
25    }
26    let leap = y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
27    let mdays = [
28        31,
29        if leap { 29 } else { 28 },
30        31,
31        30,
32        31,
33        30,
34        31,
35        31,
36        30,
37        31,
38        30,
39        31,
40    ];
41    for md in mdays {
42        if remaining < md as i64 {
43            break;
44        }
45        remaining -= md as i64;
46        mo += 1;
47    }
48    day += remaining as u32;
49    format!(
50        "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09}",
51        y, mo, day, h, m, s, nanos
52    )
53}
54
55pub fn new_snapshot(
56    session_id: String,
57    cwd: PathBuf,
58    store_path: PathBuf,
59    model: String,
60    base_url: String,
61    backend: BackendKind,
62    reasoning_effort: Option<ReasoningEffort>,
63    sandbox_spec: Option<SandboxSpec>,
64    messages: Vec<Message>,
65) -> SessionSnapshot {
66    tracing::debug!(
67        session_id = %session_id,
68        cwd = %cwd.display(),
69        store_path = %store_path.display(),
70        model = %model,
71        backend = ?backend,
72        message_count = messages.len(),
73        sandboxed = sandbox_spec.is_some(),
74        "constructing new session snapshot"
75    );
76    let now = now_utc();
77    SessionSnapshot {
78        session_id,
79        cwd,
80        store_path,
81        model,
82        base_url,
83        backend,
84        reasoning_effort,
85        sandbox_spec,
86        messages,
87        last_response_duration_ms: None,
88        previous_response_duration_ms: None,
89        response_durations_ms: None,
90        timeline_json: None,
91        created_at: now.clone(),
92        updated_at: now,
93    }
94}
95
96pub fn refresh_snapshot(
97    snapshot: &SessionSnapshot,
98    messages: Vec<Message>,
99    last_response_duration_ms: Option<u64>,
100    previous_response_duration_ms: Option<u64>,
101    response_durations_ms: Option<Vec<Option<u64>>>,
102    timeline_json: Option<String>,
103) -> SessionSnapshot {
104    tracing::debug!(
105        session_id = %snapshot.session_id,
106        cwd = %snapshot.cwd.display(),
107        store_path = %snapshot.store_path.display(),
108        model = %snapshot.model,
109        backend = ?snapshot.backend,
110        message_count = messages.len(),
111        sandboxed = snapshot.sandbox_spec.is_some(),
112        "refreshing session snapshot"
113    );
114    SessionSnapshot {
115        session_id: snapshot.session_id.clone(),
116        cwd: snapshot.cwd.clone(),
117        store_path: snapshot.store_path.clone(),
118        model: snapshot.model.clone(),
119        base_url: snapshot.base_url.clone(),
120        backend: snapshot.backend,
121        reasoning_effort: snapshot.reasoning_effort.clone(),
122        sandbox_spec: snapshot.sandbox_spec.clone(),
123        messages,
124        last_response_duration_ms,
125        previous_response_duration_ms,
126        response_durations_ms,
127        timeline_json,
128        created_at: snapshot.created_at.clone(),
129        updated_at: now_utc(),
130    }
131}