vtcode-acp 0.98.4

ACP bridge and client implementation for VT Code
use agent_client_protocol as acp;
use serde_json::{Value, json};
use std::path::PathBuf;

pub const TOOL_FAILURE_PREFIX: &str = "Tool execution failed";
pub const TOOL_SUCCESS_LABEL: &str = "success";
pub const TOOL_ERROR_LABEL: &str = "error";
pub const TOOL_RESPONSE_KEY_STATUS: &str = "status";
pub const TOOL_RESPONSE_KEY_TOOL: &str = "tool";
pub const TOOL_RESPONSE_KEY_PATH: &str = "path";
pub const TOOL_RESPONSE_KEY_CONTENT: &str = "content";
pub const TOOL_RESPONSE_KEY_TRUNCATED: &str = "truncated";
pub const TOOL_RESPONSE_KEY_MESSAGE: &str = "message";

pub const TOOL_EXECUTION_CANCELLED_MESSAGE: &str =
    "Tool execution cancelled at the client's request";
pub const TOOL_PERMISSION_ALLOW_OPTION_ID: &str = "allow-once";
pub const TOOL_PERMISSION_ALLOW_ALWAYS_OPTION_ID: &str = "allow-always";
pub const TOOL_PERMISSION_DENY_OPTION_ID: &str = "reject-once";
pub const TOOL_PERMISSION_DENY_ALWAYS_OPTION_ID: &str = "reject-always";
pub const TOOL_PERMISSION_ALLOW_PREFIX: &str = "Allow";
pub const TOOL_PERMISSION_DENY_PREFIX: &str = "Deny";
pub const TOOL_PERMISSION_DENIED_MESSAGE: &str =
    "Tool execution cancelled: permission denied by the user";
pub const TOOL_PERMISSION_CANCELLED_MESSAGE: &str =
    "Tool execution cancelled: permission request interrupted";
pub const TOOL_PERMISSION_REQUEST_FAILURE_LOG: &str =
    "Failed to request ACP tool permission, cancelling the tool invocation";
pub const TOOL_PERMISSION_UNKNOWN_OPTION_LOG: &str =
    "Received unsupported ACP permission option selection";
pub const TOOL_PERMISSION_REQUEST_FAILURE_MESSAGE: &str =
    "Tool execution cancelled: permission request failed";

pub struct ToolExecutionReport {
    pub status: acp::ToolCallStatus,
    pub llm_response: String,
    pub content: Vec<acp::ToolCallContent>,
    pub locations: Vec<acp::ToolCallLocation>,
    pub raw_output: Option<Value>,
}

impl ToolExecutionReport {
    pub fn success(
        content: Vec<acp::ToolCallContent>,
        locations: Vec<acp::ToolCallLocation>,
        payload: Value,
    ) -> Self {
        Self {
            status: acp::ToolCallStatus::Completed,
            llm_response: payload.to_string(),
            content,
            locations,
            raw_output: Some(payload),
        }
    }

    pub fn failure(tool_name: &str, message: &str) -> Self {
        let payload = json!({
            TOOL_RESPONSE_KEY_STATUS: TOOL_ERROR_LABEL,
            TOOL_RESPONSE_KEY_TOOL: tool_name,
            TOOL_RESPONSE_KEY_MESSAGE: message,
        });
        Self {
            status: acp::ToolCallStatus::Failed,
            llm_response: payload.to_string(),
            content: vec![acp::ToolCallContent::from(format!(
                "{TOOL_FAILURE_PREFIX}: {message}"
            ))],
            locations: Vec::new(),
            raw_output: Some(payload),
        }
    }

    pub fn cancelled(tool_name: &str) -> Self {
        Self::failure(tool_name, TOOL_EXECUTION_CANCELLED_MESSAGE)
    }
}

pub fn create_diff_content(
    path: &str,
    old_text: Option<&str>,
    new_text: &str,
) -> acp::ToolCallContent {
    acp::ToolCallContent::Diff(
        acp::Diff::new(PathBuf::from(path), new_text.to_string())
            .old_text(old_text.map(|s| s.to_string())),
    )
}