1use std::path::PathBuf;
7use std::str::FromStr;
8use std::sync::Arc;
9
10use agent_client_protocol::schema::{
11 InitializeRequest, ProtocolVersion, RequestPermissionOutcome, RequestPermissionRequest,
12 RequestPermissionResponse, SelectedPermissionOutcome,
13};
14use agent_client_protocol::{Agent, Client, ConnectionTo};
15use agent_client_protocol_tokio::AcpAgent;
16use tracing::{debug, info, warn};
17
18use crate::error::{AcpError, Result};
19use crate::permissions::{
20 PermissionDecision, PermissionOption, PermissionPolicy, PermissionRequest,
21};
22
23#[derive(Debug, Clone)]
25pub struct AcpAgentConfig {
26 pub command: String,
28 pub working_dir: PathBuf,
30 pub auto_approve: bool,
33}
34
35impl AcpAgentConfig {
36 pub fn new(command: impl Into<String>) -> Self {
38 Self {
39 command: command.into(),
40 working_dir: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
41 auto_approve: true,
42 }
43 }
44
45 pub fn working_dir(mut self, path: impl Into<PathBuf>) -> Self {
47 self.working_dir = path.into();
48 self
49 }
50
51 pub fn auto_approve(mut self, approve: bool) -> Self {
53 self.auto_approve = approve;
54 self
55 }
56}
57
58pub async fn prompt_agent(config: &AcpAgentConfig, prompt: &str) -> Result<String> {
63 let policy =
64 if config.auto_approve { PermissionPolicy::AutoApprove } else { PermissionPolicy::DenyAll };
65 prompt_agent_with_policy(config, prompt, Arc::new(policy)).await
66}
67
68pub async fn prompt_agent_with_policy(
92 config: &AcpAgentConfig,
93 prompt: &str,
94 policy: Arc<PermissionPolicy>,
95) -> Result<String> {
96 info!(command = %config.command, cwd = %config.working_dir.display(), "spawning ACP agent");
97
98 let agent = AcpAgent::from_str(&config.command).map_err(|e| {
99 AcpError::InvalidConfig(format!("invalid command '{}': {e}", config.command))
100 })?;
101
102 let prompt_text = prompt.to_string();
103 let working_dir = config.working_dir.clone();
104 let policy_clone = policy.clone();
105
106 let result: std::result::Result<String, agent_client_protocol::Error> = Client
107 .builder()
108 .on_receive_request(
109 async move |request: RequestPermissionRequest, responder, _cx: ConnectionTo<Agent>| {
110 let title = request
112 .options
113 .first()
114 .map(|o| o.name.to_string())
115 .unwrap_or_else(|| "Unknown operation".to_string());
116
117 let perm_request = PermissionRequest {
118 title: title.clone(),
119 options: request
120 .options
121 .iter()
122 .map(|o| PermissionOption {
123 id: o.option_id.to_string(),
124 name: o.name.to_string(),
125 })
126 .collect(),
127 };
128
129 let decision = policy_clone.decide(&perm_request);
131
132 match &decision {
133 PermissionDecision::Allow(option_id) => {
134 debug!(title = %title, decision = %decision, "ACP permission granted");
135 responder.respond(RequestPermissionResponse::new(
136 RequestPermissionOutcome::Selected(SelectedPermissionOutcome::new(
137 option_id.clone(),
138 )),
139 ))
140 }
141 PermissionDecision::Deny => {
142 warn!(title = %title, "ACP permission DENIED by policy");
143 responder.respond(RequestPermissionResponse::new(
144 RequestPermissionOutcome::Cancelled,
145 ))
146 }
147 }
148 },
149 agent_client_protocol::on_receive_request!(),
150 )
151 .connect_with(agent, |connection: ConnectionTo<Agent>| async move {
152 connection
154 .send_request(InitializeRequest::new(ProtocolVersion::V1))
155 .block_task()
156 .await?;
157
158 let response_text = connection
160 .build_session(&working_dir)
161 .block_task()
162 .run_until(async |mut session| {
163 session.send_prompt(&prompt_text)?;
164 let text = session.read_to_string().await?;
165 Ok(text)
166 })
167 .await?;
168
169 Ok(response_text)
170 })
171 .await;
172
173 result.map_err(|e| AcpError::Protocol(e.to_string()))
174}