yoagent-state 0.3.0

Durable state and lineage for long-running agents.
Documentation
use crate::{Event, EventId, ForkId, Graph, StateError, replay};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ForkSnapshot {
    pub id: ForkId,
    pub parent_event: Option<EventId>,
    pub events: Vec<Event>,
    pub graph: Graph,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GraphDiff {
    pub added_nodes: Vec<String>,
    pub removed_nodes: Vec<String>,
    pub changed_nodes: Vec<String>,
    pub added_relations: Vec<String>,
    pub removed_relations: Vec<String>,
}

pub fn fork_events_at(
    events: &[Event],
    id: ForkId,
    parent_event: Option<EventId>,
) -> Result<ForkSnapshot, StateError> {
    let cutoff = match &parent_event {
        Some(parent_event) => {
            events
                .iter()
                .position(|event| &event.id == parent_event)
                .ok_or_else(|| StateError::EventNotFound(parent_event.clone()))?
                + 1
        }
        None => events.len(),
    };
    let forked_events = events.iter().take(cutoff).cloned().collect::<Vec<_>>();
    let graph = replay(&forked_events)?;
    Ok(ForkSnapshot {
        id,
        parent_event,
        events: forked_events,
        graph,
    })
}

pub fn diff_graphs(left: &Graph, right: &Graph) -> GraphDiff {
    let left_nodes = left
        .nodes
        .keys()
        .map(|id| id.0.clone())
        .collect::<BTreeSet<_>>();
    let right_nodes = right
        .nodes
        .keys()
        .map(|id| id.0.clone())
        .collect::<BTreeSet<_>>();

    let changed_nodes = left
        .nodes
        .iter()
        .filter_map(|(id, node)| {
            right
                .nodes
                .get(id)
                .filter(|right_node| *right_node != node)
                .map(|_| id.0.clone())
        })
        .collect();

    let left_relations = left
        .relations
        .iter()
        .map(|rel| format!("{}:{}:{}", rel.from, rel.rel, rel.to))
        .collect::<BTreeSet<_>>();
    let right_relations = right
        .relations
        .iter()
        .map(|rel| format!("{}:{}:{}", rel.from, rel.rel, rel.to))
        .collect::<BTreeSet<_>>();

    GraphDiff {
        added_nodes: right_nodes.difference(&left_nodes).cloned().collect(),
        removed_nodes: left_nodes.difference(&right_nodes).cloned().collect(),
        changed_nodes,
        added_relations: right_relations
            .difference(&left_relations)
            .cloned()
            .collect(),
        removed_relations: left_relations
            .difference(&right_relations)
            .cloned()
            .collect(),
    }
}