vtcode 0.99.1

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
use serde_json::{Map, Value};

use crate::agent::runloop::text_tools::parse_args::parse_scalar_value;

pub(super) fn parse_yaml_tool_call(text: &str) -> Option<(String, Value)> {
    for segment in text.split("```") {
        if segment.trim().is_empty() {
            continue;
        }
        if let Some(result) = parse_yaml_tool_block(segment) {
            return Some(result);
        }
    }
    parse_yaml_tool_block(text)
}

fn parse_yaml_tool_block(block: &str) -> Option<(String, Value)> {
    let mut lines = block.lines().map(|line| line.trim_end()).peekable();
    let mut name = None;
    const LANGUAGE_HINTS: &[&str] = &[
        "rust",
        "bash",
        "shell",
        "python",
        "json",
        "yaml",
        "toml",
        "javascript",
        "typescript",
        "markdown",
        "text",
        "swift",
        "go",
        "java",
        "cpp",
        "c",
        "php",
        "html",
        "css",
        "sql",
        "csharp",
    ];

    for line in lines.by_ref() {
        let trimmed = line.trim();
        if trimmed.is_empty() {
            continue;
        }
        if trimmed.starts_with('#') {
            continue;
        }
        if LANGUAGE_HINTS.contains(&trimmed.to_ascii_lowercase().as_str()) {
            continue;
        }
        name = Some(trimmed.trim_end_matches(':').to_string());
        break;
    }

    let name = name?;
    if name.is_empty() {
        return None;
    }

    let mut object = Map::new();
    let mut pending_key: Option<String> = None;
    let mut multiline_buffer: Vec<String> = Vec::new();
    let mut multiline_indent: Option<usize> = None;

    for line in lines {
        let raw = line;
        let trimmed = raw.trim();
        if trimmed.is_empty() {
            if pending_key.is_some() {
                multiline_buffer.push(String::new());
            }
            continue;
        }

        if pending_key.is_some() && (raw.starts_with(' ') || raw.starts_with('\t')) {
            let indent = raw.chars().take_while(|c| c.is_whitespace()).count();
            let content = if let Some(expected) = multiline_indent {
                if indent >= expected {
                    raw[expected..].to_string()
                } else {
                    raw.trim_start().to_string()
                }
            } else {
                multiline_indent = Some(indent);
                raw[indent..].to_string()
            };
            multiline_buffer.push(content);
            continue;
        }

        if let Some(key) = pending_key.take() {
            let joined = multiline_buffer.join("\n");
            object.insert(key, Value::String(joined));
            multiline_buffer.clear();
            multiline_indent = None;
        }

        if let Some((key_raw, value_raw)) = trimmed.split_once(':') {
            let key = key_raw.trim();
            let value = value_raw.trim();
            if key.is_empty() {
                continue;
            }
            if value == "|" {
                pending_key = Some(key.to_string());
                continue;
            }
            let parsed = parse_scalar_value(value);
            object.insert(key.to_string(), parsed);
        }
    }

    if let Some(key) = pending_key.take() {
        let joined = multiline_buffer.join("\n");
        object.insert(key, Value::String(joined));
    }

    if object.is_empty() {
        None
    } else {
        Some((name, Value::Object(object)))
    }
}