ai-agent 0.88.0

Idiomatic agent sdk inspired by the claude code source leak
Documentation
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

#[derive(Debug, Clone)]
pub struct SkillChange {
    pub skill_id: String,
    pub change_type: SkillChangeType,
    pub timestamp: u64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SkillChangeType {
    Added,
    Removed,
    Updated,
    Enabled,
    Disabled,
}

pub struct SkillsChangeTracker {
    changes: Arc<RwLock<Vec<SkillChange>>>,
    latest_by_skill: Arc<RwLock<HashMap<String, SkillChange>>>,
}

impl SkillsChangeTracker {
    pub fn new() -> Self {
        Self {
            changes: Arc::new(RwLock::new(Vec::new())),
            latest_by_skill: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    pub async fn record_change(&self, skill_id: String, change_type: SkillChangeType) {
        let change = SkillChange {
            skill_id: skill_id.clone(),
            change_type,
            timestamp: now_timestamp(),
        };

        let mut changes = self.changes.write().await;
        changes.push(change.clone());

        let mut latest = self.latest_by_skill.write().await;
        latest.insert(skill_id, change);
    }

    pub async fn get_changes(&self) -> Vec<SkillChange> {
        let changes = self.changes.read().await;
        changes.clone()
    }

    pub async fn get_latest_change(&self, skill_id: &str) -> Option<SkillChange> {
        let latest = self.latest_by_skill.read().await;
        latest.get(skill_id).cloned()
    }

    pub async fn has_changes_since(&self, since: u64) -> bool {
        let changes = self.changes.read().await;
        changes.iter().any(|c| c.timestamp > since)
    }

    pub async fn get_changes_since(&self, since: u64) -> Vec<SkillChange> {
        let changes = self.changes.read().await;
        changes
            .iter()
            .filter(|c| c.timestamp > since)
            .cloned()
            .collect()
    }

    pub async fn clear(&self) {
        let mut changes = self.changes.write().await;
        changes.clear();

        let mut latest = self.latest_by_skill.write().await;
        latest.clear();
    }
}

impl Default for SkillsChangeTracker {
    fn default() -> Self {
        Self::new()
    }
}

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::*;

    #[tokio::test]
    async fn test_skills_change_tracker() {
        let tracker = SkillsChangeTracker::new();

        tracker
            .record_change("skill-1".to_string(), SkillChangeType::Added)
            .await;

        let changes = tracker.get_changes().await;
        assert_eq!(changes.len(), 1);

        let latest = tracker.get_latest_change("skill-1").await;
        assert!(latest.is_some());
    }

    #[tokio::test]
    async fn test_changes_since() {
        let tracker = SkillsChangeTracker::new();

        let before = now_timestamp();
        tracker
            .record_change("skill-1".to_string(), SkillChangeType::Added)
            .await;

        let has_changes = tracker.has_changes_since(before).await;
        assert!(has_changes);
    }
}