stdio_agent_server.rs

  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                thread: thread_rc,
114            });
115
116            Ok(connection)
117        })
118    }
119}