codex/commands/
app_server.rs1use std::fs as std_fs;
2
3use tokio::{process::Command, time};
4
5use crate::{
6 builder::{apply_cli_overrides, resolve_cli_overrides},
7 process::{spawn_with_retry, tee_stream, ConsoleTarget},
8 AppServerCodegenOutput, AppServerCodegenRequest, CodexClient, CodexError,
9};
10
11impl CodexClient {
12 pub async fn generate_app_server_bindings(
20 &self,
21 request: AppServerCodegenRequest,
22 ) -> Result<AppServerCodegenOutput, CodexError> {
23 let AppServerCodegenRequest {
24 target,
25 out_dir,
26 experimental,
27 overrides,
28 } = request;
29
30 std_fs::create_dir_all(&out_dir).map_err(|source| CodexError::PrepareOutputDirectory {
31 path: out_dir.clone(),
32 source,
33 })?;
34
35 let dir_ctx = self.directory_context()?;
36 let resolved_overrides =
37 resolve_cli_overrides(&self.cli_overrides, &overrides, self.model.as_deref());
38
39 let mut command = Command::new(self.command_env.binary_path());
40 command
41 .arg("app-server")
42 .arg(target.subcommand())
43 .arg("--out")
44 .arg(&out_dir)
45 .stdout(std::process::Stdio::piped())
46 .stderr(std::process::Stdio::piped())
47 .kill_on_drop(true)
48 .current_dir(dir_ctx.path());
49
50 apply_cli_overrides(&mut command, &resolved_overrides, true);
51
52 if experimental {
53 command.arg("--experimental");
54 }
55
56 if let Some(prettier) = target.prettier() {
57 command.arg("--prettier").arg(prettier);
58 }
59
60 self.command_env.apply(&mut command)?;
61
62 let mut child = spawn_with_retry(&mut command, self.command_env.binary_path())?;
63
64 let stdout = child.stdout.take().ok_or(CodexError::StdoutUnavailable)?;
65 let stderr = child.stderr.take().ok_or(CodexError::StderrUnavailable)?;
66
67 let stdout_task = tokio::spawn(tee_stream(
68 stdout,
69 ConsoleTarget::Stdout,
70 self.mirror_stdout,
71 ));
72 let stderr_task = tokio::spawn(tee_stream(stderr, ConsoleTarget::Stderr, !self.quiet));
73
74 let wait_task = async move {
75 let status = child
76 .wait()
77 .await
78 .map_err(|source| CodexError::Wait { source })?;
79 let stdout_bytes = stdout_task
80 .await
81 .map_err(CodexError::Join)?
82 .map_err(CodexError::CaptureIo)?;
83 let stderr_bytes = stderr_task
84 .await
85 .map_err(CodexError::Join)?
86 .map_err(CodexError::CaptureIo)?;
87 Ok::<_, CodexError>((status, stdout_bytes, stderr_bytes))
88 };
89
90 let (status, stdout_bytes, stderr_bytes) = if self.timeout.is_zero() {
91 wait_task.await?
92 } else {
93 match time::timeout(self.timeout, wait_task).await {
94 Ok(result) => result?,
95 Err(_) => {
96 return Err(CodexError::Timeout {
97 timeout: self.timeout,
98 });
99 }
100 }
101 };
102
103 if !status.success() {
104 return Err(CodexError::NonZeroExit {
105 status,
106 stderr: String::from_utf8(stderr_bytes)?,
107 });
108 }
109
110 Ok(AppServerCodegenOutput {
111 status,
112 stdout: String::from_utf8(stdout_bytes)?,
113 stderr: String::from_utf8(stderr_bytes)?,
114 out_dir,
115 })
116 }
117}