1use log::debug;
7use std::path::PathBuf;
8use std::process::Stdio;
9
10#[derive(Debug, Clone)]
17pub struct AppServerBuilder {
18 command: PathBuf,
19 working_directory: Option<PathBuf>,
20}
21
22impl Default for AppServerBuilder {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl AppServerBuilder {
29 pub fn new() -> Self {
31 Self {
32 command: PathBuf::from("codex"),
33 working_directory: None,
34 }
35 }
36
37 pub fn command<P: Into<PathBuf>>(mut self, path: P) -> Self {
39 self.command = path.into();
40 self
41 }
42
43 pub fn working_directory<P: Into<PathBuf>>(mut self, dir: P) -> Self {
45 self.working_directory = Some(dir.into());
46 self
47 }
48
49 fn build_args(&self) -> Vec<String> {
51 vec![
52 "app-server".to_string(),
53 "--listen".to_string(),
54 "stdio://".to_string(),
55 ]
56 }
57
58 #[cfg(feature = "async-client")]
60 pub async fn spawn(self) -> crate::error::Result<tokio::process::Child> {
61 let args = self.build_args();
62
63 debug!(
64 "[CLI] Spawning async app-server: {} {}",
65 self.command.display(),
66 args.join(" ")
67 );
68
69 let mut cmd = tokio::process::Command::new(&self.command);
70 cmd.args(&args)
71 .stdin(Stdio::piped())
72 .stdout(Stdio::piped())
73 .stderr(Stdio::piped());
74
75 if let Some(ref dir) = self.working_directory {
76 cmd.current_dir(dir);
77 }
78
79 cmd.spawn().map_err(crate::error::Error::Io)
80 }
81
82 pub fn spawn_sync(self) -> std::io::Result<std::process::Child> {
84 let args = self.build_args();
85
86 debug!(
87 "[CLI] Spawning sync app-server: {} {}",
88 self.command.display(),
89 args.join(" ")
90 );
91
92 let mut cmd = std::process::Command::new(&self.command);
93 cmd.args(&args)
94 .stdin(Stdio::piped())
95 .stdout(Stdio::piped())
96 .stderr(Stdio::piped());
97
98 if let Some(ref dir) = self.working_directory {
99 cmd.current_dir(dir);
100 }
101
102 cmd.spawn()
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_default_args() {
112 let builder = AppServerBuilder::new();
113 let args = builder.build_args();
114
115 assert_eq!(args, vec!["app-server", "--listen", "stdio://"]);
116 }
117
118 #[test]
119 fn test_custom_command() {
120 let builder = AppServerBuilder::new().command("/usr/local/bin/codex");
121 assert_eq!(builder.command, PathBuf::from("/usr/local/bin/codex"));
122 }
123
124 #[test]
125 fn test_working_directory() {
126 let builder = AppServerBuilder::new().working_directory("/tmp/work");
127 assert_eq!(builder.working_directory, Some(PathBuf::from("/tmp/work")));
128 }
129}