ai-agent 0.88.0

Idiomatic agent sdk inspired by the claude code source leak
Documentation
use std::collections::VecDeque;

#[derive(Debug, Clone, PartialEq)]
pub struct TurnDiff {
    pub turn_index: usize,
    pub additions: usize,
    pub deletions: usize,
    pub timestamp: u64,
}

pub struct TurnDiffsTracker {
    diffs: VecDeque<TurnDiff>,
    max_history: usize,
}

impl TurnDiffsTracker {
    pub fn new(max_history: usize) -> Self {
        Self {
            diffs: VecDeque::new(),
            max_history,
        }
    }

    pub fn add_diff(&mut self, turn_index: usize, additions: usize, deletions: usize) {
        let diff = TurnDiff {
            turn_index,
            additions,
            deletions,
            timestamp: now_timestamp(),
        };

        self.diffs.push_back(diff);

        while self.diffs.len() > self.max_history {
            self.diffs.pop_front();
        }
    }

    pub fn get_diffs(&self) -> Vec<TurnDiff> {
        self.diffs.iter().cloned().collect()
    }

    pub fn get_latest(&self) -> Option<TurnDiff> {
        self.diffs.back().cloned()
    }

    pub fn get_total_additions(&self) -> usize {
        self.diffs.iter().map(|d| d.additions).sum()
    }

    pub fn get_total_deletions(&self) -> usize {
        self.diffs.iter().map(|d| d.deletions).sum()
    }

    pub fn get_net_change(&self) -> isize {
        let additions = self.get_total_additions() as isize;
        let deletions = self.get_total_deletions() as isize;
        additions - deletions
    }

    pub fn get_turn_count(&self) -> usize {
        self.diffs.len()
    }

    pub fn clear(&mut self) {
        self.diffs.clear();
    }

    pub fn get_turn_diff(&self, turn_index: usize) -> Option<TurnDiff> {
        self.diffs
            .iter()
            .find(|d| d.turn_index == turn_index)
            .cloned()
    }
}

fn now_timestamp() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_millis() as u64)
        .unwrap_or(0)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_turn_diffs_tracker() {
        let mut tracker = TurnDiffsTracker::new(10);

        tracker.add_diff(0, 10, 5);
        tracker.add_diff(1, 20, 3);

        assert_eq!(tracker.get_turn_count(), 2);
        assert_eq!(tracker.get_total_additions(), 30);
        assert_eq!(tracker.get_total_deletions(), 8);
    }

    #[test]
    fn test_net_change() {
        let mut tracker = TurnDiffsTracker::new(10);

        tracker.add_diff(0, 10, 5);
        assert_eq!(tracker.get_net_change(), 5);

        tracker.add_diff(1, 3, 10);
        assert_eq!(tracker.get_net_change(), -2);
    }

    #[test]
    fn test_max_history() {
        let mut tracker = TurnDiffsTracker::new(2);

        tracker.add_diff(0, 10, 0);
        tracker.add_diff(1, 10, 0);
        tracker.add_diff(2, 10, 0);

        assert_eq!(tracker.get_turn_count(), 2);
    }
}