1use crate::{AgentServer, AgentServerCommand, AgentServerVersion};
2use acp_thread::{AgentConnection, LoadError, OldAcpAgentConnection, OldAcpClientDelegate};
3use agentic_coding_protocol as acp_old;
4use anyhow::{Result, anyhow};
5use gpui::{App, AsyncApp, Entity, Task, WeakEntity, prelude::*};
6use project::Project;
7use std::{cell::RefCell, path::Path, rc::Rc};
8use util::ResultExt;
9
10pub trait StdioAgentServer: Send + Clone {
11 fn logo(&self) -> ui::IconName;
12 fn name(&self) -> &'static str;
13 fn empty_state_headline(&self) -> &'static str;
14 fn empty_state_message(&self) -> &'static str;
15
16 fn command(
17 &self,
18 project: &Entity<Project>,
19 cx: &mut AsyncApp,
20 ) -> impl Future<Output = Result<AgentServerCommand>>;
21
22 fn version(
23 &self,
24 command: &AgentServerCommand,
25 ) -> impl Future<Output = Result<AgentServerVersion>> + Send;
26}
27
28impl<T: StdioAgentServer + 'static> AgentServer for T {
29 fn name(&self) -> &'static str {
30 self.name()
31 }
32
33 fn empty_state_headline(&self) -> &'static str {
34 self.empty_state_headline()
35 }
36
37 fn empty_state_message(&self) -> &'static str {
38 self.empty_state_message()
39 }
40
41 fn logo(&self) -> ui::IconName {
42 self.logo()
43 }
44
45 fn connect(
46 &self,
47 root_dir: &Path,
48 project: &Entity<Project>,
49 cx: &mut App,
50 ) -> Task<Result<Rc<dyn AgentConnection>>> {
51 let root_dir = root_dir.to_path_buf();
52 let project = project.clone();
53 let this = self.clone();
54
55 cx.spawn(async move |cx| {
56 let command = this.command(&project, cx).await?;
57
58 let mut child = util::command::new_smol_command(&command.path)
59 .args(command.args.iter())
60 .current_dir(root_dir)
61 .stdin(std::process::Stdio::piped())
62 .stdout(std::process::Stdio::piped())
63 .stderr(std::process::Stdio::inherit())
64 .kill_on_drop(true)
65 .spawn()?;
66
67 let stdin = child.stdin.take().unwrap();
68 let stdout = child.stdout.take().unwrap();
69
70 let foreground_executor = cx.foreground_executor().clone();
71
72 let thread_rc = Rc::new(RefCell::new(WeakEntity::new_invalid()));
73
74 let (connection, io_fut) = acp_old::AgentConnection::connect_to_agent(
75 OldAcpClientDelegate::new(thread_rc.clone(), cx.clone()),
76 stdin,
77 stdout,
78 move |fut| foreground_executor.spawn(fut).detach(),
79 );
80
81 let io_task = cx.background_spawn(async move {
82 io_fut.await.log_err();
83 });
84
85 let child_status = cx.background_spawn(async move {
86 let result = match child.status().await {
87 Err(e) => Err(anyhow!(e)),
88 Ok(result) if result.success() => Ok(()),
89 Ok(result) => {
90 if let Some(AgentServerVersion::Unsupported {
91 error_message,
92 upgrade_message,
93 upgrade_command,
94 }) = this.version(&command).await.log_err()
95 {
96 Err(anyhow!(LoadError::Unsupported {
97 error_message,
98 upgrade_message,
99 upgrade_command
100 }))
101 } else {
102 Err(anyhow!(LoadError::Exited(result.code().unwrap_or(-127))))
103 }
104 }
105 };
106 drop(io_task);
107 result
108 });
109
110 let connection: Rc<dyn AgentConnection> = Rc::new(OldAcpAgentConnection {
111 connection,
112 child_status,
113 });
114
115 Ok(connection)
116 })
117 }
118}