use deepstrike_core::runtime::repair::{
reconstruct_messages_with_fallback, repair_events_with_cap,
};
use deepstrike_core::runtime::session::SessionEvent;
use deepstrike_core::types::message::Message;
use super::session_log::SessionEntry;
pub fn repair_entries(entries: &[SessionEntry]) -> Vec<SessionEntry> {
repair_entries_with_cap(entries, 0)
}
pub fn repair_entries_with_cap(entries: &[SessionEntry], max_bytes: usize) -> Vec<SessionEntry> {
let events: Vec<SessionEvent> = entries.iter().map(|e| e.event.clone()).collect();
repair_events_with_cap(events, max_bytes)
.into_iter()
.zip(entries.iter())
.map(|(event, entry)| SessionEntry {
seq: entry.seq,
event,
})
.collect()
}
pub fn is_mid_run(entries: &[SessionEntry]) -> bool {
let latest_start = entries
.iter()
.rposition(|entry| matches!(entry.event, SessionEvent::RunStarted { .. }));
let latest_terminal = entries
.iter()
.rposition(|entry| matches!(entry.event, SessionEvent::RunTerminal { .. }));
latest_start.is_some_and(|start| latest_terminal.map_or(true, |terminal| start > terminal))
}
pub fn replay_messages(entries: &[SessionEntry]) -> Vec<Message> {
replay_messages_with_cap(entries, 0)
}
pub fn replay_messages_with_cap(entries: &[SessionEntry], max_bytes: usize) -> Vec<Message> {
replay_messages_with_cap_and_loader(entries, max_bytes, |_| {
Err(
deepstrike_core::context::fault::ContextFault::MissingArchive {
session_id: String::new(),
seq: 0,
},
)
})
}
pub fn replay_messages_with_cap_and_loader<F>(
entries: &[SessionEntry],
max_bytes: usize,
load_archive: F,
) -> Vec<Message>
where
F: FnMut(&str) -> Result<Vec<Message>, deepstrike_core::context::fault::ContextFault>,
{
let events: Vec<SessionEvent> = entries.iter().map(|e| e.event.clone()).collect();
reconstruct_messages_with_fallback(&events, "", max_bytes, load_archive)
}
#[cfg(test)]
mod tests {
use super::*;
use deepstrike_core::runtime::session::SessionEvent;
use deepstrike_core::types::message::{Content, Role, ToolResult};
#[test]
fn is_mid_run_when_no_terminal() {
let entries = vec![SessionEntry {
seq: 0,
event: SessionEvent::RunStarted {
run_id: "r1".into(),
goal: "hi".into(),
criteria: vec![],
agent_id: None,
system_prompt: None,
attachments: vec![],
},
}];
assert!(is_mid_run(&entries));
}
#[test]
fn is_mid_run_uses_the_latest_run_segment() {
let mut entries = vec![
SessionEntry {
seq: 0,
event: SessionEvent::RunStarted {
run_id: "r1".into(),
goal: "first".into(),
criteria: vec![],
agent_id: None,
system_prompt: None,
attachments: vec![],
},
},
SessionEntry {
seq: 1,
event: SessionEvent::RunTerminal {
reason: "completed".into(),
turns_used: 1,
total_tokens: 0,
},
},
SessionEntry {
seq: 2,
event: SessionEvent::RunStarted {
run_id: "r2".into(),
goal: "second".into(),
criteria: vec![],
agent_id: None,
system_prompt: None,
attachments: vec![],
},
},
];
assert!(is_mid_run(&entries));
entries.push(SessionEntry {
seq: 3,
event: SessionEvent::RunTerminal {
reason: "completed".into(),
turns_used: 1,
total_tokens: 0,
},
});
assert!(!is_mid_run(&entries));
}
#[test]
fn replay_includes_user_and_assistant() {
let entries = vec![
SessionEntry {
seq: 0,
event: SessionEvent::RunStarted {
run_id: "r1".into(),
goal: "ping".into(),
criteria: vec![],
agent_id: None,
system_prompt: None,
attachments: vec![],
},
},
SessionEntry {
seq: 1,
event: SessionEvent::LlmCompleted {
turn: 0,
message: Message {
role: Role::Assistant,
content: Content::Text("pong".into()),
tool_calls: vec![],
token_count: None,
},
provider_replay: None,
},
},
];
let msgs = replay_messages(&entries);
assert_eq!(msgs.len(), 2);
assert_eq!(msgs[0].role, Role::User);
assert_eq!(msgs[1].role, Role::Assistant);
}
#[test]
fn replay_tool_completed() {
let call_id = compact_str::CompactString::new("c1");
let entries = vec![SessionEntry {
seq: 0,
event: SessionEvent::ToolCompleted {
turn: 0,
results: vec![ToolResult {
call_id: call_id.clone(),
output: Content::Text("ok".into()),
durable_content: None,
is_error: false,
is_fatal: false,
error_kind: None,
token_count: None,
}],
},
}];
let msgs = replay_messages(&entries);
assert_eq!(msgs.len(), 1);
assert_eq!(msgs[0].role, Role::Tool);
}
}