Skip to main content

acp_cli/client/
mod.rs

1pub mod permissions;
2
3use agent_client_protocol as acp;
4use tokio::sync::{mpsc, oneshot};
5
6use crate::bridge::events::{
7    BridgeEvent, PermissionKind, PermissionOption, PermissionOutcome, ToolCallInfo,
8};
9
10/// ACP Client implementation that bridges agent protocol events
11/// into the internal BridgeEvent channel.
12///
13/// Handles permission requests by forwarding them through a oneshot channel
14/// to the main event loop, and converts session notifications (text chunks,
15/// tool calls) into corresponding BridgeEvent variants.
16pub struct BridgedAcpClient {
17    pub evt_tx: mpsc::UnboundedSender<BridgeEvent>,
18}
19
20#[async_trait::async_trait(?Send)]
21impl acp::Client for BridgedAcpClient {
22    async fn request_permission(
23        &self,
24        args: acp::RequestPermissionRequest,
25    ) -> acp::Result<acp::RequestPermissionResponse> {
26        // Extract tool info from the ToolCallUpdate
27        let tool = ToolCallInfo {
28            name: args.tool_call.fields.title.clone().unwrap_or_default(),
29            description: None,
30        };
31
32        // Convert ACP PermissionOptions to our bridge PermissionOptions
33        let options: Vec<PermissionOption> = args
34            .options
35            .iter()
36            .map(|o| PermissionOption {
37                option_id: o.option_id.0.to_string(),
38                name: o.name.clone(),
39                kind: match o.kind {
40                    acp::PermissionOptionKind::AllowOnce
41                    | acp::PermissionOptionKind::AllowAlways => PermissionKind::Allow,
42                    _ => PermissionKind::Deny,
43                },
44            })
45            .collect();
46
47        // Create a oneshot channel for the permission reply
48        let (reply_tx, reply_rx) = oneshot::channel();
49
50        // Send the permission request to the main event loop
51        let _ = self.evt_tx.send(BridgeEvent::PermissionRequest {
52            tool,
53            options,
54            reply: reply_tx,
55        });
56
57        // Wait for the decision from the main thread
58        let outcome = reply_rx.await.unwrap_or(PermissionOutcome::Cancelled);
59
60        // Convert our PermissionOutcome back to ACP's RequestPermissionOutcome
61        let acp_outcome = match outcome {
62            PermissionOutcome::Selected { option_id } => acp::RequestPermissionOutcome::Selected(
63                acp::SelectedPermissionOutcome::new(option_id),
64            ),
65            PermissionOutcome::Cancelled => acp::RequestPermissionOutcome::Cancelled,
66        };
67
68        Ok(acp::RequestPermissionResponse::new(acp_outcome))
69    }
70
71    async fn session_notification(&self, args: acp::SessionNotification) -> acp::Result<()> {
72        match args.update {
73            acp::SessionUpdate::AgentMessageChunk(chunk) => {
74                if let acp::ContentBlock::Text(text_content) = chunk.content {
75                    let _ = self.evt_tx.send(BridgeEvent::TextChunk {
76                        text: text_content.text,
77                    });
78                }
79            }
80            acp::SessionUpdate::ToolCall(tool_call) => {
81                let _ = self.evt_tx.send(BridgeEvent::ToolUse {
82                    name: tool_call.title.clone(),
83                });
84            }
85            _ => {
86                // Ignore other session update variants
87            }
88        }
89        Ok(())
90    }
91}