Skip to main content

codex/commands/
app_server.rs

1use 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    /// Generates app-server bindings via `codex app-server generate-ts` or `generate-json-schema`.
13    ///
14    /// Ensures the output directory exists, mirrors stdout/stderr according to the builder
15    /// (`mirror_stdout` / `quiet`), and returns captured output plus the exit status. Non-zero
16    /// exits bubble up as [`CodexError::NonZeroExit`] with stderr attached. Use
17    /// [`AppServerCodegenRequest::prettier`] to format TypeScript output with a specific
18    /// Prettier binary and request-level overrides for config/profile toggles.
19    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}