vtcode-acp 0.98.6

ACP bridge and client implementation for VT Code
use agent_client_protocol as acp;
use vtcode_core::config::types::ReasoningEffortLevel;
use vtcode_core::core::interfaces::SessionMode;

pub(crate) const SESSION_CONFIG_MODE_ID: &str = "mode";
pub(crate) const SESSION_CONFIG_THOUGHT_LEVEL_ID: &str = "thought_level";

pub(crate) fn session_mode_description(mode: SessionMode) -> &'static str {
    match mode {
        SessionMode::Ask => "Answer questions with read-only workspace inspection",
        SessionMode::Architect => {
            "Design and plan software systems with read-only workspace inspection"
        }
        SessionMode::Code => "Write and modify code with full tool access",
    }
}

pub(crate) fn session_mode_prompt(mode: SessionMode) -> Option<&'static str> {
    match mode {
        SessionMode::Ask => Some(
            "You are in Ask mode. Answer questions directly and use only read-only workspace inspection tools when needed. Do not make code changes or run implementation tools. If the user wants implementation, tell them to switch to Code mode.",
        ),
        SessionMode::Architect => Some(
            "You are in Architect mode. Focus on design, planning, and read-only workspace inspection. Do not make code changes or run implementation tools. If implementation is requested, provide a plan or ask the user to switch to Code mode.",
        ),
        SessionMode::Code => None,
    }
}

pub(crate) fn session_mode_allows_local_tools(mode: SessionMode) -> bool {
    matches!(mode, SessionMode::Code)
}

pub(crate) fn acp_session_modes() -> Vec<acp::SessionMode> {
    vec![
        acp::SessionMode::new(SessionMode::Ask.as_str(), "Ask")
            .description(session_mode_description(SessionMode::Ask)),
        acp::SessionMode::new(SessionMode::Architect.as_str(), "Architect")
            .description(session_mode_description(SessionMode::Architect)),
        acp::SessionMode::new(SessionMode::Code.as_str(), "Code")
            .description(session_mode_description(SessionMode::Code)),
    ]
}

pub(crate) fn session_mode_id(mode: SessionMode) -> acp::SessionModeId {
    acp::SessionModeId::new(mode.as_str())
}

fn session_mode_name(mode: SessionMode) -> &'static str {
    match mode {
        SessionMode::Ask => "Ask",
        SessionMode::Architect => "Architect",
        SessionMode::Code => "Code",
    }
}

fn reasoning_effort_name(level: ReasoningEffortLevel) -> &'static str {
    match level {
        ReasoningEffortLevel::None => "None",
        ReasoningEffortLevel::Minimal => "Minimal",
        ReasoningEffortLevel::Low => "Low",
        ReasoningEffortLevel::Medium => "Medium",
        ReasoningEffortLevel::High => "High",
        ReasoningEffortLevel::XHigh => "Extra High",
    }
}

pub(crate) fn session_config_options(
    current_mode: SessionMode,
    reasoning_effort: ReasoningEffortLevel,
    include_thought_level: bool,
) -> Vec<acp::SessionConfigOption> {
    let mode_options = [SessionMode::Ask, SessionMode::Architect, SessionMode::Code]
        .into_iter()
        .map(|mode| acp::SessionConfigSelectOption::new(mode.as_str(), session_mode_name(mode)))
        .collect::<Vec<_>>();

    let thought_level_options = ReasoningEffortLevel::allowed_values()
        .iter()
        .filter_map(|value| {
            ReasoningEffortLevel::parse(value).map(|level| {
                acp::SessionConfigSelectOption::new(level.as_str(), reasoning_effort_name(level))
            })
        })
        .collect::<Vec<_>>();

    let mut config_options = Vec::with_capacity(2);
    config_options.push(
        acp::SessionConfigOption::select(
            SESSION_CONFIG_MODE_ID,
            "Mode",
            current_mode.as_str(),
            mode_options,
        )
        .description("Controls whether VT Code answers, plans, or edits.")
        .category(acp::SessionConfigOptionCategory::Mode),
    );
    if include_thought_level {
        config_options.push(
            acp::SessionConfigOption::select(
                SESSION_CONFIG_THOUGHT_LEVEL_ID,
                "Thought level",
                reasoning_effort.as_str(),
                thought_level_options,
            )
            .description("Controls how much reasoning effort VT Code requests from the model.")
            .category(acp::SessionConfigOptionCategory::ThoughtLevel),
        );
    }

    config_options
}

pub(crate) fn text_chunk(text: impl Into<String>) -> acp::ContentChunk {
    acp::ContentChunk::new(acp::ContentBlock::from(text.into()))
}

pub(crate) fn agent_implementation_info(title_override: Option<String>) -> acp::Implementation {
    acp::Implementation::new("vtcode", env!("CARGO_PKG_VERSION"))
        .title(title_override.or_else(|| Some("VT Code".to_string())))
}

pub(crate) fn build_available_commands() -> Vec<acp::AvailableCommand> {
    vec![
        acp::AvailableCommand::new("init", "Create vtcode.toml and index the workspace").input(
            acp::AvailableCommandInput::Unstructured(acp::UnstructuredCommandInput::new(
                "Optional: --force flag",
            )),
        ),
        acp::AvailableCommand::new("config", "Browse vtcode.toml settings sections"),
        acp::AvailableCommand::new("status", "Show model, provider, workspace, and tool status"),
        acp::AvailableCommand::new("doctor", "Run installation and configuration diagnostics"),
        acp::AvailableCommand::new(
            "plan",
            "Toggle between Code and Architect modes for read-only planning",
        )
        .input(acp::AvailableCommandInput::Unstructured(
            acp::UnstructuredCommandInput::new("Optional: on | off"),
        )),
        acp::AvailableCommand::new("mode", "Cycle through Ask -> Architect -> Code modes"),
        acp::AvailableCommand::new("help", "Show slash command help"),
        acp::AvailableCommand::new("reset", "Reset conversation context"),
        acp::AvailableCommand::new("tools", "List tools and their descriptions"),
        acp::AvailableCommand::new("exit", "Close the VT Code session"),
    ]
}