Skip to main content

atman_runtime/event_log/
reader.rs

1use std::path::Path;
2
3use crate::session::{ContextSnapshot, SessionOpenError};
4use serde_json;
5
6pub fn read_event_envelopes(
7    path: &Path,
8) -> Result<Vec<crate::event::EventEnvelope>, SessionOpenError> {
9    let text = match std::fs::read_to_string(path) {
10        Ok(t) => t,
11        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
12        Err(e) => {
13            return Err(SessionOpenError::Replay {
14                path: path.to_path_buf(),
15                source: e,
16            });
17        }
18    };
19    let mut out = Vec::new();
20    for line in text.lines() {
21        if line.trim().is_empty() {
22            continue;
23        }
24        if let Ok(env) = serde_json::from_str::<crate::event::EventEnvelope>(line) {
25            out.push(env);
26        }
27    }
28    Ok(out)
29}
30
31pub fn parse_json_lines(text: &str) -> Vec<serde_json::Value> {
32    text.lines()
33        .filter_map(|line| {
34            let t = line.trim();
35            if t.is_empty() {
36                None
37            } else {
38                serde_json::from_str::<serde_json::Value>(t).ok()
39            }
40        })
41        .collect()
42}
43
44pub fn find_last_seq(path: &Path) -> Result<Option<u64>, SessionOpenError> {
45    let envelopes = read_event_envelopes(path)?;
46    Ok(envelopes.last().map(|env| env.seq))
47}
48
49pub fn replay_context_snapshot_from(path: &Path) -> ContextSnapshot {
50    let mut snap = ContextSnapshot::default();
51    let text = match std::fs::read_to_string(path) {
52        Ok(t) => t,
53        Err(_) => return snap,
54    };
55    for value in parse_json_lines(&text) {
56        if value["type"].as_str() != Some("llm_call") {
57            continue;
58        }
59        // Only count main-agent LLM calls — subagent calls have run_id: null.
60        if value["run_id"].is_null() {
61            continue;
62        }
63        if let Some(model) = value["model"].as_str() {
64            snap.model = model.to_string();
65        }
66        let usage = &value["usage"];
67        let input = usage["input"].as_u64().unwrap_or(0);
68        let cached = usage["cached_input"].as_u64().unwrap_or(0);
69        let output = usage["output"].as_u64().unwrap_or(0);
70        snap.tokens_in = snap.tokens_in.saturating_add(input).saturating_add(cached);
71        snap.tokens_out = snap.tokens_out.saturating_add(output);
72    }
73    snap
74}