vv-agent 0.6.2

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

pub(super) fn build_extraction_prompt(messages: &[&Message]) -> String {
    let serialized_messages = messages
        .iter()
        .map(|message| message_to_text(message))
        .collect::<Vec<_>>();
    format!(
        "Analyze the following conversation messages and extract durable facts that should survive context compression.\n\n\
Categories:\n\
- user_intent: goals, constraints, preferences, explicit asks\n\
- decision: decisions or chosen approaches\n\
- file_change: files created/modified/deleted and why\n\
- error_fix: failures and their resolutions\n\
- key_fact: other important context that should not be forgotten\n\n\
Requirements:\n\
- Return JSON array only.\n\
- Keep each content field concise and deduplicatable.\n\
- Skip transient chatter and repeated information.\n\
- importance is 1-10 where 10 means critical.\n\n\
Output format:\n\
[{example}]\n\n\
Messages:\n{}",
        serde_json::to_string_pretty(&serialized_messages).unwrap_or_default(),
        example = r#"{"category":"...", "content":"...", "importance": 5}"#
    )
}

pub(super) fn should_skip_message(message: &Message) -> bool {
    message.role == MessageRole::System
        || (message.role == MessageRole::User
            && message.content.contains("<Compressed Agent Memory>"))
}

fn message_to_text(message: &Message) -> serde_json::Value {
    let role = match message.role {
        MessageRole::System => "system",
        MessageRole::User => "user",
        MessageRole::Assistant => "assistant",
        MessageRole::Tool => "tool",
    };
    let mut object = serde_json::Map::new();
    object.insert("role".to_string(), serde_json::json!(role));
    object.insert(
        "content".to_string(),
        serde_json::json!(compact_long_content(&message.content)),
    );
    if let Some(name) = &message.name {
        object.insert("name".to_string(), serde_json::json!(name));
    }
    if let Some(tool_call_id) = &message.tool_call_id {
        object.insert("tool_call_id".to_string(), serde_json::json!(tool_call_id));
    }
    if !message.tool_calls.is_empty() {
        object.insert(
            "tool_calls".to_string(),
            serde_json::to_value(&message.tool_calls).unwrap_or(serde_json::Value::Null),
        );
    }
    serde_json::Value::Object(object)
}

fn compact_long_content(content: &str) -> String {
    if content.len() <= 2_000 {
        return content.to_string();
    }
    let head = content.chars().take(1_200).collect::<String>();
    let tail = content
        .chars()
        .rev()
        .take(400)
        .collect::<Vec<_>>()
        .into_iter()
        .rev()
        .collect::<String>();
    format!("{head}\n...[truncated]...\n{tail}")
}