vv-agent 0.4.8

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use crate::memory::microcompact::is_microcompacted_tool_content;
use crate::types::{Message, MessageRole};

use super::{ToolResultArtifactConfig, TOOL_RESULT_COMPACT_MARKER};

pub(super) fn is_compacted_tool_content(content: &str) -> bool {
    content.starts_with(TOOL_RESULT_COMPACT_MARKER) || is_microcompacted_tool_content(content)
}

pub(super) fn kept_tool_message_indices(messages: &[Message], keep_last: usize) -> Vec<usize> {
    if keep_last == 0 {
        return Vec::new();
    }
    messages
        .iter()
        .enumerate()
        .filter_map(|(index, message)| (message.role == MessageRole::Tool).then_some(index))
        .rev()
        .take(keep_last)
        .collect()
}

pub(super) fn should_compact_tool_message(
    message: &Message,
    index: usize,
    keep_indices: &[usize],
    compact_threshold: usize,
) -> bool {
    message.role == MessageRole::Tool
        && !keep_indices.contains(&index)
        && message.content.len() > compact_threshold
        && !is_compacted_tool_content(&message.content)
}

pub(super) fn build_compacted_tool_content(
    content: &str,
    artifact_path: Option<&str>,
    tool_name: Option<&str>,
    config: &ToolResultArtifactConfig,
) -> String {
    let head = take_chars(content, config.excerpt_head);
    let tail = take_tail_chars(content, config.excerpt_tail);
    let mut excerpt_parts = Vec::new();
    if !head.is_empty() {
        excerpt_parts.push(head.clone());
    }
    if !tail.is_empty() && tail != head {
        if !excerpt_parts.is_empty() {
            excerpt_parts.push("...<snip>...".to_string());
        }
        excerpt_parts.push(tail);
    }
    let excerpt = excerpt_parts.join("\n");
    let artifact_line = artifact_path.unwrap_or("N/A");
    let tool_line = tool_name
        .map(|tool_name| format!("tool_name: {tool_name}\n"))
        .unwrap_or_default();
    let truncated_chars = content.len().saturating_sub(excerpt.len());
    format!(
        "{TOOL_RESULT_COMPACT_MARKER}\n{tool_line}artifact_path: {artifact_line}\ntotal_chars: {}\ntruncated_chars: {truncated_chars}\nretrieval_hint: use read_file on artifact_path if needed\nexcerpt:\n{excerpt}\n</Tool Result Compact>",
        content.len()
    )
}

fn take_chars(content: &str, count: usize) -> String {
    content.chars().take(count).collect()
}

fn take_tail_chars(content: &str, count: usize) -> String {
    if count == 0 {
        return String::new();
    }
    let chars = content.chars().collect::<Vec<_>>();
    let start = chars.len().saturating_sub(count);
    chars[start..].iter().collect()
}