vtcode-llm 0.181.1

LLM provider abstraction, client implementations, and streaming for VT Code
Documentation
use super::*;

pub(super) fn parse_observed_tool_call(update: &Value) -> Option<CopilotObservedToolCall> {
    let tool_call_id = update.get("toolCallId")?.as_str()?.to_string();
    let tool_name = derived_copilot_tool_name(
        update.get("title").and_then(Value::as_str),
        update.get("kind").and_then(Value::as_str),
    );
    let status = parse_observed_tool_status(
        update.get("status").and_then(Value::as_str),
        update.get("sessionUpdate").and_then(Value::as_str),
    );
    let arguments = update.get("rawInput").cloned();
    let output = extract_observed_tool_output(update);
    let terminal_id = extract_tool_call_terminal_id(update.get("content"));

    Some(CopilotObservedToolCall {
        tool_call_id,
        tool_name,
        status,
        arguments,
        output,
        terminal_id,
    })
}

fn parse_observed_tool_status(status: Option<&str>, session_update: Option<&str>) -> CopilotObservedToolCallStatus {
    match status.unwrap_or_else(|| {
        if session_update == Some("tool_call") {
            "pending"
        } else {
            "in_progress"
        }
    }) {
        "pending" => CopilotObservedToolCallStatus::Pending,
        "in_progress" => CopilotObservedToolCallStatus::InProgress,
        "completed" => CopilotObservedToolCallStatus::Completed,
        "failed" => CopilotObservedToolCallStatus::Failed,
        _ => CopilotObservedToolCallStatus::InProgress,
    }
}

fn extract_observed_tool_output(update: &Value) -> Option<String> {
    update
        .get("rawOutput")
        .and_then(extract_observed_tool_raw_output)
        .or_else(|| extract_tool_call_content_text(update.get("content")))
}

fn extract_observed_tool_raw_output(raw_output: &Value) -> Option<String> {
    match raw_output {
        Value::String(text) => Some(text.clone()).filter(|text| !text.trim().is_empty()),
        Value::Object(object) => object
            .get("content")
            .and_then(Value::as_str)
            .filter(|text| !text.trim().is_empty())
            .map(ToString::to_string)
            .or_else(|| {
                object
                    .get("detailedContent")
                    .and_then(Value::as_str)
                    .filter(|text| !text.trim().is_empty())
                    .map(ToString::to_string)
            })
            .or_else(|| Some(json_to_string_pretty(raw_output))),
        _ => Some(json_to_string_pretty(raw_output)),
    }
}

fn extract_tool_call_content_text(content: Option<&Value>) -> Option<String> {
    content.and_then(Value::as_array).into_iter().flatten().find_map(|item| {
        item.get("content").and_then(|content| {
            content
                .get("type")
                .and_then(Value::as_str)
                .filter(|value| *value == "text")
                .and_then(|_| content.get("text"))
                .and_then(Value::as_str)
                .map(ToString::to_string)
        })
    })
}

fn extract_tool_call_terminal_id(content: Option<&Value>) -> Option<String> {
    content.and_then(Value::as_array).into_iter().flatten().find_map(|item| {
        item.get("content").and_then(|content| {
            content
                .get("type")
                .and_then(Value::as_str)
                .filter(|value| *value == "terminal")
                .and_then(|_| content.get("terminalId"))
                .and_then(Value::as_str)
                .map(ToString::to_string)
        })
    })
}