ai-agent 0.13.4

Idiomatic agent sdk inspired by the claude code source leak
Documentation
// Source: /data/home/swei/claudecode/openclaudecode/src/commands/compact/compact.ts
//! Main compact service - handles conversation compaction
//!
//! This is a stub implementation. The full implementation handles
//! the complex logic of compacting conversation history.

use serde::{Deserialize, Serialize};

/// Compact direction
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "lowercase")]
pub enum CompactDirection {
    /// Compact from the beginning (oldest messages)
    Head,
    /// Compact from the end (newest messages)
    Tail,
    /// Smart compaction based on token budget
    #[default]
    Smart,
}

/// Compact result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompactResult {
    /// Whether compaction was successful
    pub success: bool,
    /// Number of messages removed
    pub messages_removed: usize,
    /// Token count before compaction
    pub tokens_before: u64,
    /// Token count after compaction
    pub tokens_after: u64,
    /// Direction used
    pub direction: CompactDirection,
    /// Error message if failed
    pub error: Option<String>,
}

/// Compact options
#[derive(Debug, Clone, Default)]
pub struct CompactOptions {
    /// Maximum tokens to keep after compaction
    pub max_tokens: Option<u64>,
    /// Direction to compact
    pub direction: CompactDirection,
    /// Whether to create a boundary message
    pub create_boundary: bool,
    /// Custom system prompt to include
    pub system_prompt: Option<String>,
}

/// Execute conversation compaction
pub async fn compact_messages(
    _messages: &[impl AsRef<dyn std::any::Any>],
    options: CompactOptions,
) -> Result<CompactResult, String> {
    // This is a stub implementation
    // Full implementation would involve:
    // 1. Analyzing token counts
    // 2. Selecting messages to remove
    // 3. Creating boundary messages
    // 4. Updating session state

    Ok(CompactResult {
        success: true,
        messages_removed: 0,
        tokens_before: 0,
        tokens_after: 0,
        direction: options.direction,
        error: None,
    })
}

/// Get the recommended compact direction based on message count and tokens
pub fn get_recommended_direction(
    message_count: usize,
    total_tokens: u64,
    max_tokens: u64,
) -> CompactDirection {
    if total_tokens <= max_tokens {
        return CompactDirection::Smart;
    }

    // If more than half the messages are from the user, compact from head
    // to preserve recent assistant responses
    if message_count > 10 {
        CompactDirection::Head
    } else {
        CompactDirection::Smart
    }
}

/// Calculate the number of messages to remove for compact
pub fn calculate_messages_to_remove(
    current_tokens: u64,
    target_tokens: u64,
    avg_tokens_per_message: u64,
) -> usize {
    if current_tokens <= target_tokens {
        return 0;
    }

    let tokens_to_remove = current_tokens - target_tokens;
    (tokens_to_remove / avg_tokens_per_message) as usize
}

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

    #[test]
    fn test_compact_direction_default() {
        let options = CompactOptions::default();
        assert_eq!(options.direction, CompactDirection::Smart);
    }

    #[test]
    fn test_get_recommended_direction_no_compact() {
        let dir = get_recommended_direction(5, 1000, 2000);
        assert_eq!(dir, CompactDirection::Smart);
    }

    #[test]
    fn test_calculate_messages_to_remove() {
        let count = calculate_messages_to_remove(5000, 2000, 500);
        assert_eq!(count, 6);
    }

    #[test]
    fn test_calculate_messages_to_remove_no_need() {
        let count = calculate_messages_to_remove(1000, 2000, 500);
        assert_eq!(count, 0);
    }
}