deepstrike-sdk 0.2.63

DeepStrike Rust SDK — agent framework built on deepstrike-core
Documentation
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);
    }
}