use std::io::{self, Read};
use std::ops::ControlFlow;
use serde::{Deserialize, Serialize};
use crate::run_meta::{ContextSnapshot, RunMeta};
use super::{Frame, RunRecord, apply_delta, read_archive_start, read_frame};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RunPoint {
pub meta: RunMeta,
pub context: ContextSnapshot,
pub at: i64,
}
#[derive(Debug)]
pub struct PointRef<'a> {
pub index: usize,
pub at: i64,
pub meta: &'a RunMeta,
pub context: &'a ContextSnapshot,
}
pub fn visit_points(records: &[RunRecord], visit: &mut dyn FnMut(PointRef<'_>) -> ControlFlow<()>) {
let mut iter = records.iter();
let Some(mut folder) = (match iter.next() {
Some(first) => PointFolder::start(first),
None => None,
}) else {
return;
};
for record in iter {
if folder.push(record, visit).is_break() {
return;
}
}
}
pub fn visit_archive_points(
r: &mut dyn Read,
visit: &mut dyn FnMut(PointRef<'_>) -> ControlFlow<()>,
) -> io::Result<()> {
read_archive_start(r)?;
let mut folder = match read_frame(r) {
Ok(Some(Frame::Record(first))) => match PointFolder::start(&first) {
Some(folder) => folder,
None => return Ok(()),
},
_ => return Ok(()),
};
while let Ok(Some(frame)) = read_frame(r) {
let Frame::Record(record) = frame else {
continue;
};
if folder.push(&record, visit).is_break() {
return Ok(());
}
}
Ok(())
}
struct PointFolder {
meta: RunMeta,
context: ContextSnapshot,
index: usize,
}
impl PointFolder {
fn start(first: &RunRecord) -> Option<Self> {
match first {
RunRecord::Header { meta, .. } => Some(Self {
meta: (**meta).clone(),
context: ContextSnapshot {
stage_name: String::new(),
total_tokens: 0,
max_tokens: 0,
regions: Vec::new(),
},
index: 0,
}),
_ => None,
}
}
fn push(
&mut self,
record: &RunRecord,
visit: &mut dyn FnMut(PointRef<'_>) -> ControlFlow<()>,
) -> ControlFlow<()> {
let at = match record {
RunRecord::Header { meta: m, .. } => {
self.meta = (**m).clone();
return ControlFlow::Continue(());
}
RunRecord::StatusChanged { status, .. } => {
self.meta.status = status.clone();
return ControlFlow::Continue(());
}
RunRecord::ContextCheckpoint { snapshot, at } => {
self.context = snapshot.clone();
*at
}
RunRecord::ContextDiff { delta, at } => {
apply_delta(&mut self.context, delta);
*at
}
RunRecord::Checkpoint {
meta: m,
context: c,
at,
} => {
self.meta = (**m).clone();
self.context = c.clone();
*at
}
RunRecord::Progress { meta: m, delta, at } => {
self.meta = (**m).clone();
apply_delta(&mut self.context, delta);
*at
}
RunRecord::OwnershipChanged { .. }
| RunRecord::Inference { .. }
| RunRecord::InferenceAttempt(_)
| RunRecord::InferenceFailover(_)
| RunRecord::InferenceUsage { .. }
| RunRecord::ToolBatch { .. }
| RunRecord::ToolCallDone { .. }
| RunRecord::ArtifactsProduced { .. }
| RunRecord::Interaction { .. }
| RunRecord::ContextChange { .. }
| RunRecord::ContextTransaction { .. }
| RunRecord::Message { .. } => return ControlFlow::Continue(()),
};
let flow = visit(PointRef {
index: self.index,
at,
meta: &self.meta,
context: &self.context,
});
self.index += 1;
flow
}
}
pub fn replay_points(records: &[RunRecord]) -> Vec<RunPoint> {
let mut points = Vec::new();
visit_points(records, &mut |point| {
points.push(RunPoint {
meta: point.meta.clone(),
context: point.context.clone(),
at: point.at,
});
ControlFlow::Continue(())
});
points
}