aether-wisp 0.4.13

A terminal UI for AI coding agents via the Agent Client Protocol (ACP)
Documentation
use acp_utils::notifications::SubAgentProgressParams;
use agent_client_protocol::schema as acp;
use serde_json::{Value, json};
use wisp::components::tool_call_status_view::{ToolCallStatus, ToolCallStatusView};

pub struct ToolCallFactory {
    id: String,
    name: String,
    raw_input: Option<Value>,
}

pub enum SubAgentEvent {
    ToolCall { id: String, name: String, arguments: String },
    ToolCallUpdate { id: String, chunk: String },
    ToolResult { id: String, name: String, result: String, result_meta: Option<Value> },
    ToolError { id: String, name: String, error: String },
    Done,
}

impl ToolCallFactory {
    pub fn id(mut self, id: &str) -> Self {
        self.id = id.to_string();
        self
    }

    pub fn name(mut self, name: &str) -> Self {
        self.name = name.to_string();
        self
    }

    pub fn raw_input_json(mut self, raw_input: &str) -> Self {
        self.raw_input = Some(serde_json::from_str(raw_input).expect("valid tool call raw input fixture"));
        self
    }

    pub fn build(self) -> acp::ToolCall {
        let mut tool_call = acp::ToolCall::new(self.id, self.name);
        if let Some(raw_input) = self.raw_input {
            tool_call = tool_call.raw_input(raw_input);
        }
        tool_call
    }
}

impl Default for ToolCallFactory {
    fn default() -> Self {
        Self { id: "tool-1".to_string(), name: "Read".to_string(), raw_input: None }
    }
}

impl SubAgentEvent {
    fn into_json_value(self) -> Value {
        match self {
            Self::ToolCall { id, name, arguments } => json!({
                "ToolCall": {
                    "request": { "id": id, "name": name, "arguments": arguments },
                    "model_name": "m"
                }
            }),
            Self::ToolCallUpdate { id, chunk } => json!({
                "ToolCallUpdate": { "update": { "id": id, "chunk": chunk } }
            }),
            Self::ToolResult { id, name, result, result_meta } => {
                let mut result_value = json!({ "id": id, "name": name, "arguments": "{}", "result": result });
                if let Some(meta) = result_meta {
                    result_value["result_meta"] = meta;
                }
                json!({ "ToolResult": { "result": result_value, "model_name": "m" } })
            }
            Self::ToolError { id, name, error } => json!({
                "ToolError": {
                    "error": { "id": id, "name": name, "arguments": "{}", "error": error },
                    "model_name": "m"
                }
            }),
            Self::Done => json!("Done"),
        }
    }
}

pub fn tool_call_update(id: &str, status: acp::ToolCallStatus) -> acp::ToolCallUpdate {
    acp::ToolCallUpdate::new(id.to_string(), acp::ToolCallUpdateFields::new().status(status))
}

pub fn completed_tool_call_update(id: &str) -> acp::ToolCallUpdate {
    tool_call_update(id, acp::ToolCallStatus::Completed)
}

pub fn failed_tool_call_update(id: &str) -> acp::ToolCallUpdate {
    tool_call_update(id, acp::ToolCallStatus::Failed)
}

pub fn tool_call_status_view(status: &ToolCallStatus) -> ToolCallStatusView<'_> {
    ToolCallStatusView { name: "TestTool", arguments: "", display_value: None, diff_preview: None, status, tick: 0 }
}

pub fn sub_agent_tool_call(id: &str, name: &str) -> SubAgentEvent {
    SubAgentEvent::ToolCall { id: id.to_string(), name: name.to_string(), arguments: "{}".to_string() }
}

pub fn sub_agent_tool_call_with_args(id: &str, name: &str, arguments: impl Into<Value>) -> SubAgentEvent {
    SubAgentEvent::ToolCall { id: id.to_string(), name: name.to_string(), arguments: arguments.into().to_string() }
}

pub fn sub_agent_tool_update(id: &str, chunk: impl Into<Value>) -> SubAgentEvent {
    SubAgentEvent::ToolCallUpdate { id: id.to_string(), chunk: chunk.into().to_string() }
}

pub fn sub_agent_tool_result(id: &str, name: &str) -> SubAgentEvent {
    SubAgentEvent::ToolResult {
        id: id.to_string(),
        name: name.to_string(),
        result: "ok".to_string(),
        result_meta: None,
    }
}

pub fn sub_agent_tool_result_with_display_meta(id: &str, name: &str, title: &str, value: &str) -> SubAgentEvent {
    SubAgentEvent::ToolResult {
        id: id.to_string(),
        name: name.to_string(),
        result: "ok".to_string(),
        result_meta: Some(json!({ "display": { "title": title, "value": value } })),
    }
}

pub fn sub_agent_tool_error(id: &str, name: &str) -> SubAgentEvent {
    SubAgentEvent::ToolError { id: id.to_string(), name: name.to_string(), error: "not found".to_string() }
}

pub fn sub_agent_done() -> SubAgentEvent {
    SubAgentEvent::Done
}

pub fn sub_agent_progress(parent_tool_id: &str, agent_name: &str, event: SubAgentEvent) -> SubAgentProgressParams {
    sub_agent_progress_with_task_id(parent_tool_id, agent_name, agent_name, event)
}

pub fn sub_agent_progress_with_task_id(
    parent_tool_id: &str,
    task_id: &str,
    agent_name: &str,
    event: SubAgentEvent,
) -> SubAgentProgressParams {
    serde_json::from_value(json!({
        "parent_tool_id": parent_tool_id,
        "task_id": task_id,
        "agent_name": agent_name,
        "event": event.into_json_value(),
    }))
    .expect("valid sub-agent progress fixture")
}