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 let name = self.name();
55
56 cx.spawn(async move |cx| {
57 let command = this.command(&project, cx).await?;
58
59 let mut child = util::command::new_smol_command(&command.path)
60 .args(command.args.iter())
61 .current_dir(root_dir)
62 .stdin(std::process::Stdio::piped())
63 .stdout(std::process::Stdio::piped())
64 .stderr(std::process::Stdio::inherit())
65 .kill_on_drop(true)
66 .spawn()?;
67
68 let stdin = child.stdin.take().unwrap();
69 let stdout = child.stdout.take().unwrap();
70
71 let foreground_executor = cx.foreground_executor().clone();
72
73 let thread_rc = Rc::new(RefCell::new(WeakEntity::new_invalid()));
74
75 let (connection, io_fut) = acp_old::AgentConnection::connect_to_agent(
76 OldAcpClientDelegate::new(thread_rc.clone(), cx.clone()),
77 stdin,
78 stdout,
79 move |fut| foreground_executor.spawn(fut).detach(),
80 );
81
82 let io_task = cx.background_spawn(async move {
83 io_fut.await.log_err();
84 });
85
86 let child_status = cx.background_spawn(async move {
87 let result = match child.status().await {
88 Err(e) => Err(anyhow!(e)),
89 Ok(result) if result.success() => Ok(()),
90 Ok(result) => {
91 if let Some(AgentServerVersion::Unsupported {
92 error_message,
93 upgrade_message,
94 upgrade_command,
95 }) = this.version(&command).await.log_err()
96 {
97 Err(anyhow!(LoadError::Unsupported {
98 error_message,
99 upgrade_message,
100 upgrade_command
101 }))
102 } else {
103 Err(anyhow!(LoadError::Exited(result.code().unwrap_or(-127))))
104 }
105 }
106 };
107 drop(io_task);
108 result
109 });
110
111 let connection: Rc<dyn AgentConnection> = Rc::new(OldAcpAgentConnection {
112 name,
113 connection,
114 child_status,
115 });
116
117 Ok(connection)
118 })
119 }
120}