vtcode 0.99.1

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
use std::time::Duration;

use hashbrown::HashMap;

use crate::agent::runloop::git::{FileStat, git_working_tree_numstat_snapshot};

const CODE_CHANGE_SNAPSHOT_TIMEOUT_MS: u64 = 120;

pub(super) struct TurnExecutionMetrics {
    pub attempts_made: usize,
    pub retry_count: usize,
    pub history_snapshot_bytes: usize,
    pub timeout_secs: u64,
    pub elapsed_ms: u128,
    pub outcome: &'static str,
}

pub(super) fn estimate_history_bytes(history: &[vtcode_core::llm::provider::Message]) -> usize {
    use vtcode_core::llm::provider::{ContentPart, MessageContent};

    history
        .iter()
        .map(|message| {
            let content_bytes = match &message.content {
                MessageContent::Text(text) => text.len(),
                MessageContent::Parts(parts) => parts
                    .iter()
                    .map(|part| match part {
                        ContentPart::Text { text } => text.len(),
                        ContentPart::Image { .. } | ContentPart::File { .. } => 128,
                    })
                    .sum::<usize>(),
            };
            let reasoning_bytes = message.reasoning.as_ref().map_or(0, String::len);
            let tool_call_id_bytes = message.tool_call_id.as_ref().map_or(0, String::len);
            let origin_tool_bytes = message.origin_tool.as_ref().map_or(0, String::len);
            content_bytes + reasoning_bytes + tool_call_id_bytes + origin_tool_bytes + 32
        })
        .sum()
}

pub(super) fn emit_turn_execution_metrics(turn_metrics: TurnExecutionMetrics) {
    tracing::info!(
        target: "vtcode.turn.metrics",
        metric = "turn_execution",
        attempts_made = turn_metrics.attempts_made,
        retry_count = turn_metrics.retry_count,
        history_snapshot_bytes = turn_metrics.history_snapshot_bytes,
        timeout_secs = turn_metrics.timeout_secs,
        elapsed_ms = turn_metrics.elapsed_ms,
        outcome = turn_metrics.outcome,
        "turn metric"
    );
}

pub(super) async fn capture_code_change_snapshot(
    workspace: &std::path::Path,
    phase: &str,
) -> Option<HashMap<std::path::PathBuf, FileStat>> {
    let workspace_path = workspace.to_path_buf();
    let phase_label = phase.to_string();
    match tokio::time::timeout(
        Duration::from_millis(CODE_CHANGE_SNAPSHOT_TIMEOUT_MS),
        tokio::task::spawn_blocking(move || git_working_tree_numstat_snapshot(&workspace_path)),
    )
    .await
    {
        Ok(Ok(Ok(snapshot))) => snapshot,
        Ok(Ok(Err(err))) => {
            tracing::warn!(
                "Failed to capture {} code-change snapshot: {}",
                phase_label,
                err
            );
            None
        }
        Ok(Err(err)) => {
            tracing::warn!(
                "Failed to capture {} code-change snapshot (join error): {}",
                phase_label,
                err
            );
            None
        }
        Err(_) => {
            tracing::debug!(
                "Skipping {} code-change snapshot after {}ms timeout",
                phase_label,
                CODE_CHANGE_SNAPSHOT_TIMEOUT_MS
            );
            None
        }
    }
}