vtcode 0.106.0

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
use vtcode_commons::diff_paths::{
    is_diff_addition_line, is_diff_deletion_line, is_diff_header_line,
};
use vtcode_core::llm::provider as uni;
use vtcode_core::llm::providers::ReasoningSegment;

pub(super) fn reasoning_duplicates_content(reasoning: &str, content: &str) -> bool {
    let r = reasoning.trim();
    let c = content.trim();
    if r.is_empty() || c.is_empty() {
        return false;
    }
    r == c || r.contains(c) || c.contains(r)
}

pub(super) fn build_combined_reasoning(
    reasoning: &[ReasoningSegment],
    detail_reasoning: Option<&str>,
) -> Option<String> {
    let capacity = reasoning
        .iter()
        .map(|segment| segment.text.len())
        .sum::<usize>()
        + reasoning.len().saturating_sub(1);
    let mut combined_reasoning = String::with_capacity(capacity);

    for segment in reasoning {
        if !combined_reasoning.is_empty() {
            combined_reasoning.push('\n');
        }
        combined_reasoning.push_str(&segment.text);
    }

    if combined_reasoning.trim().is_empty()
        && let Some(detail_reasoning) = detail_reasoning
    {
        return Some(detail_reasoning.to_string());
    }

    if combined_reasoning.is_empty() {
        None
    } else {
        Some(combined_reasoning)
    }
}

pub(super) fn parse_reasoning_detail_value(detail: &str) -> serde_json::Value {
    let trimmed = detail.trim();
    if (trimmed.starts_with('{') || trimmed.starts_with('['))
        && let Ok(parsed) = serde_json::from_str::<serde_json::Value>(trimmed)
    {
        return parsed;
    }
    serde_json::Value::String(detail.to_string())
}

pub(super) fn push_assistant_message(history: &mut Vec<uni::Message>, msg: uni::Message) {
    if let Some(last) = history.last_mut()
        && last.role == uni::MessageRole::Assistant
        && last.tool_calls.is_none()
        && last.phase == msg.phase
    {
        last.content = msg.content;
        last.reasoning = msg.reasoning;
        last.reasoning_details = msg.reasoning_details;
    } else {
        history.push(msg);
    }
}

pub(super) fn should_suppress_redundant_diff_recap(
    history: &[uni::Message],
    assistant_text: &str,
) -> bool {
    if assistant_text.trim().is_empty() {
        return false;
    }
    if !is_redundant_diff_recap_text(assistant_text) {
        return false;
    }
    if !has_recent_git_diff_tool_output(history) {
        return false;
    }
    if !last_user_requested_diff_view(history) {
        return false;
    }
    if last_user_requested_diff_analysis(history) {
        return false;
    }
    true
}

fn is_redundant_diff_recap_text(text: &str) -> bool {
    let trimmed = text.trim();
    let lower = trimmed.to_ascii_lowercase();
    lower.starts_with("diff for ")
        || lower.starts_with("the diff shows")
        || lower.starts_with("changes in ")
        || lower.starts_with("```diff")
        || lower.starts_with("diff preview changes")
        || lower.contains("\n**diff preview changes**")
        || (trimmed.contains("```") && is_diff_like_fenced_recap(trimmed))
}

fn is_diff_like_fenced_recap(text: &str) -> bool {
    let mut has_fence = false;
    let mut has_diff_marker = false;
    for line in text.lines() {
        let trimmed = line.trim_start();
        if trimmed.starts_with("```") {
            has_fence = true;
            continue;
        }
        if is_diff_header_line(trimmed)
            || is_diff_addition_line(trimmed)
            || is_diff_deletion_line(trimmed)
        {
            has_diff_marker = true;
        }
    }
    has_fence && has_diff_marker
}

fn has_recent_git_diff_tool_output(history: &[uni::Message]) -> bool {
    history
        .iter()
        .rev()
        .take(12)
        .any(message_is_git_diff_tool_output)
}

fn message_is_git_diff_tool_output(message: &uni::Message) -> bool {
    if message.role != uni::MessageRole::Tool {
        return false;
    }

    let content = message.content.as_text();
    if let Ok(value) = serde_json::from_str::<serde_json::Value>(content.as_ref()) {
        if value
            .get("content_type")
            .and_then(serde_json::Value::as_str)
            .is_some_and(|content_type| content_type == "git_diff")
        {
            return true;
        }
        if value
            .get("command")
            .and_then(serde_json::Value::as_str)
            .is_some_and(|command| command.trim_start().starts_with("git diff"))
        {
            return true;
        }
        if value
            .get("output")
            .and_then(serde_json::Value::as_str)
            .is_some_and(|output| output.contains("diff --git "))
        {
            return true;
        }
    }

    content.contains("\"content_type\":\"git_diff\"") || content.contains("diff --git ")
}

fn last_user_message_text(history: &[uni::Message]) -> Option<String> {
    history
        .iter()
        .rev()
        .find(|message| message.role == uni::MessageRole::User)
        .map(|message| message.content.as_text().to_ascii_lowercase())
}

fn last_user_requested_diff_view(history: &[uni::Message]) -> bool {
    let Some(text) = last_user_message_text(history) else {
        return false;
    };
    [
        "show diff",
        "git diff",
        "view diff",
        "show changes",
        "what changed",
    ]
    .iter()
    .any(|needle| text.contains(needle))
}

fn last_user_requested_diff_analysis(history: &[uni::Message]) -> bool {
    let Some(text) = last_user_message_text(history) else {
        return false;
    };
    ["analy", "explain", "summar", "review", "why", "interpret"]
        .iter()
        .any(|needle| text.contains(needle))
}