Skip to main content

codex/cli/
app_server.rs

1use crate::{CliOverridesPatch, ConfigOverride, FlagState};
2use std::{path::PathBuf, process::ExitStatus};
3
4/// Target for app-server code generation.
5#[derive(Clone, Debug, Eq, PartialEq)]
6pub enum AppServerCodegenTarget {
7    /// Emits TypeScript bindings for the app-server protocol. Optionally formats the output with Prettier.
8    TypeScript { prettier: Option<PathBuf> },
9    /// Emits a JSON schema bundle for the app-server protocol.
10    JsonSchema,
11}
12
13impl AppServerCodegenTarget {
14    pub(crate) fn subcommand(&self) -> &'static str {
15        match self {
16            AppServerCodegenTarget::TypeScript { .. } => "generate-ts",
17            AppServerCodegenTarget::JsonSchema => "generate-json-schema",
18        }
19    }
20
21    pub(crate) fn prettier(&self) -> Option<&PathBuf> {
22        match self {
23            AppServerCodegenTarget::TypeScript { prettier } => prettier.as_ref(),
24            AppServerCodegenTarget::JsonSchema => None,
25        }
26    }
27}
28
29/// Request for `codex app-server generate-ts` or `generate-json-schema`.
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub struct AppServerCodegenRequest {
32    /// Codegen target and optional Prettier path (TypeScript only).
33    pub target: AppServerCodegenTarget,
34    /// Output directory passed to `--out`; created if missing.
35    pub out_dir: PathBuf,
36    /// Passes `--experimental` to the app-server codegen subcommand when enabled.
37    pub experimental: bool,
38    /// Per-call CLI overrides layered on top of the builder.
39    pub overrides: CliOverridesPatch,
40}
41
42impl AppServerCodegenRequest {
43    /// Generates TypeScript bindings into `out_dir`.
44    pub fn typescript(out_dir: impl Into<PathBuf>) -> Self {
45        Self {
46            target: AppServerCodegenTarget::TypeScript { prettier: None },
47            out_dir: out_dir.into(),
48            experimental: false,
49            overrides: CliOverridesPatch::default(),
50        }
51    }
52
53    /// Generates a JSON schema bundle into `out_dir`.
54    pub fn json_schema(out_dir: impl Into<PathBuf>) -> Self {
55        Self {
56            target: AppServerCodegenTarget::JsonSchema,
57            out_dir: out_dir.into(),
58            experimental: false,
59            overrides: CliOverridesPatch::default(),
60        }
61    }
62
63    /// Controls whether `--experimental` is passed to the codegen subcommand.
64    pub fn experimental(mut self, enable: bool) -> Self {
65        self.experimental = enable;
66        self
67    }
68
69    /// Formats TypeScript output with the provided Prettier executable (no-op for JSON schema).
70    pub fn prettier(mut self, prettier: impl Into<PathBuf>) -> Self {
71        if let AppServerCodegenTarget::TypeScript { prettier: slot } = &mut self.target {
72            *slot = Some(prettier.into());
73        }
74        self
75    }
76
77    /// Replaces the default CLI overrides for this request.
78    pub fn with_overrides(mut self, overrides: CliOverridesPatch) -> Self {
79        self.overrides = overrides;
80        self
81    }
82
83    /// Adds a `--config key=value` override for this request.
84    pub fn config_override(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
85        self.overrides
86            .config_overrides
87            .push(ConfigOverride::new(key, value));
88        self
89    }
90
91    /// Adds a raw `--config key=value` override without validation.
92    pub fn config_override_raw(mut self, raw: impl Into<String>) -> Self {
93        self.overrides
94            .config_overrides
95            .push(ConfigOverride::from_raw(raw));
96        self
97    }
98
99    /// Sets the config profile (`--profile`) for this request.
100    pub fn profile(mut self, profile: impl Into<String>) -> Self {
101        let profile = profile.into();
102        self.overrides.profile = (!profile.trim().is_empty()).then_some(profile);
103        self
104    }
105
106    /// Requests the CLI `--oss` flag for this codegen call.
107    pub fn oss(mut self, enable: bool) -> Self {
108        self.overrides.oss = if enable {
109            FlagState::Enable
110        } else {
111            FlagState::Disable
112        };
113        self
114    }
115
116    /// Adds a `--enable <feature>` toggle for this codegen call.
117    pub fn enable_feature(mut self, name: impl Into<String>) -> Self {
118        self.overrides.feature_toggles.enable.push(name.into());
119        self
120    }
121
122    /// Adds a `--disable <feature>` toggle for this codegen call.
123    pub fn disable_feature(mut self, name: impl Into<String>) -> Self {
124        self.overrides.feature_toggles.disable.push(name.into());
125        self
126    }
127
128    /// Controls whether `--search` is passed through to Codex.
129    pub fn search(mut self, enable: bool) -> Self {
130        self.overrides.search = if enable {
131            FlagState::Enable
132        } else {
133            FlagState::Disable
134        };
135        self
136    }
137}
138
139/// Request for `codex app-server proxy`.
140#[derive(Clone, Debug, Eq, PartialEq)]
141pub struct AppServerProxyRequest {
142    /// Optional socket path passed via `--sock`.
143    pub socket_path: Option<PathBuf>,
144    /// Optional working directory override for the spawned process.
145    pub working_dir: Option<PathBuf>,
146    /// Per-call CLI overrides layered on top of the builder.
147    pub overrides: CliOverridesPatch,
148}
149
150impl AppServerProxyRequest {
151    /// Creates a request with no socket override.
152    pub fn new() -> Self {
153        Self {
154            socket_path: None,
155            working_dir: None,
156            overrides: CliOverridesPatch::default(),
157        }
158    }
159
160    /// Sets the optional socket path passed via `--sock`.
161    pub fn socket_path(mut self, socket_path: impl Into<PathBuf>) -> Self {
162        self.socket_path = Some(socket_path.into());
163        self
164    }
165
166    /// Sets the working directory used to resolve relative paths.
167    pub fn working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
168        self.working_dir = Some(dir.into());
169        self
170    }
171
172    /// Replaces the default CLI overrides for this request.
173    pub fn with_overrides(mut self, overrides: CliOverridesPatch) -> Self {
174        self.overrides = overrides;
175        self
176    }
177
178    /// Adds a `--config key=value` override for this request.
179    pub fn config_override(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
180        self.overrides
181            .config_overrides
182            .push(ConfigOverride::new(key, value));
183        self
184    }
185
186    /// Adds a raw `--config key=value` override without validation.
187    pub fn config_override_raw(mut self, raw: impl Into<String>) -> Self {
188        self.overrides
189            .config_overrides
190            .push(ConfigOverride::from_raw(raw));
191        self
192    }
193
194    /// Sets the config profile (`--profile`) for this request.
195    pub fn profile(mut self, profile: impl Into<String>) -> Self {
196        let profile = profile.into();
197        self.overrides.profile = (!profile.trim().is_empty()).then_some(profile);
198        self
199    }
200
201    /// Requests the CLI `--oss` flag for this call.
202    pub fn oss(mut self, enable: bool) -> Self {
203        self.overrides.oss = if enable {
204            FlagState::Enable
205        } else {
206            FlagState::Disable
207        };
208        self
209    }
210
211    /// Adds a `--enable <feature>` toggle for this call.
212    pub fn enable_feature(mut self, name: impl Into<String>) -> Self {
213        self.overrides.feature_toggles.enable.push(name.into());
214        self
215    }
216
217    /// Adds a `--disable <feature>` toggle for this call.
218    pub fn disable_feature(mut self, name: impl Into<String>) -> Self {
219        self.overrides.feature_toggles.disable.push(name.into());
220        self
221    }
222
223    /// Controls whether `--search` is passed through to Codex.
224    pub fn search(mut self, enable: bool) -> Self {
225        self.overrides.search = if enable {
226            FlagState::Enable
227        } else {
228            FlagState::Disable
229        };
230        self
231    }
232}
233
234impl Default for AppServerProxyRequest {
235    fn default() -> Self {
236        Self::new()
237    }
238}
239
240/// Request for `codex app-server`.
241#[derive(Clone, Debug, Eq, PartialEq)]
242pub struct AppServerRequest {
243    /// Optional address passed via `--listen`.
244    pub listen: Option<String>,
245    /// Optional websocket audience passed via `--ws-audience`.
246    pub ws_audience: Option<String>,
247    /// Optional websocket auth mode passed via `--ws-auth`.
248    pub ws_auth: Option<String>,
249    /// Optional websocket issuer passed via `--ws-issuer`.
250    pub ws_issuer: Option<String>,
251    /// Optional max clock skew seconds passed via `--ws-max-clock-skew-seconds`.
252    pub ws_max_clock_skew_seconds: Option<u64>,
253    /// Optional shared secret file passed via `--ws-shared-secret-file`.
254    pub ws_shared_secret_file: Option<PathBuf>,
255    /// Optional token file passed via `--ws-token-file`.
256    pub ws_token_file: Option<PathBuf>,
257    /// Optional token SHA-256 fingerprint passed via `--ws-token-sha256`.
258    pub ws_token_sha256: Option<String>,
259    /// Optional working directory override for the spawned process.
260    pub working_dir: Option<PathBuf>,
261    /// Per-call CLI overrides layered on top of the builder.
262    pub overrides: CliOverridesPatch,
263}
264
265impl AppServerRequest {
266    /// Creates a request with no listener or websocket overrides.
267    pub fn new() -> Self {
268        Self {
269            listen: None,
270            ws_audience: None,
271            ws_auth: None,
272            ws_issuer: None,
273            ws_max_clock_skew_seconds: None,
274            ws_shared_secret_file: None,
275            ws_token_file: None,
276            ws_token_sha256: None,
277            working_dir: None,
278            overrides: CliOverridesPatch::default(),
279        }
280    }
281
282    pub fn listen(mut self, listen: impl Into<String>) -> Self {
283        let listen = listen.into();
284        self.listen = (!listen.trim().is_empty()).then_some(listen);
285        self
286    }
287
288    pub fn ws_audience(mut self, value: impl Into<String>) -> Self {
289        let value = value.into();
290        self.ws_audience = (!value.trim().is_empty()).then_some(value);
291        self
292    }
293
294    pub fn ws_auth(mut self, value: impl Into<String>) -> Self {
295        let value = value.into();
296        self.ws_auth = (!value.trim().is_empty()).then_some(value);
297        self
298    }
299
300    pub fn ws_issuer(mut self, value: impl Into<String>) -> Self {
301        let value = value.into();
302        self.ws_issuer = (!value.trim().is_empty()).then_some(value);
303        self
304    }
305
306    pub fn ws_max_clock_skew_seconds(mut self, value: u64) -> Self {
307        self.ws_max_clock_skew_seconds = Some(value);
308        self
309    }
310
311    pub fn ws_shared_secret_file(mut self, path: impl Into<PathBuf>) -> Self {
312        self.ws_shared_secret_file = Some(path.into());
313        self
314    }
315
316    pub fn ws_token_file(mut self, path: impl Into<PathBuf>) -> Self {
317        self.ws_token_file = Some(path.into());
318        self
319    }
320
321    pub fn ws_token_sha256(mut self, value: impl Into<String>) -> Self {
322        let value = value.into();
323        self.ws_token_sha256 = (!value.trim().is_empty()).then_some(value);
324        self
325    }
326
327    pub fn working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
328        self.working_dir = Some(dir.into());
329        self
330    }
331
332    pub fn with_overrides(mut self, overrides: CliOverridesPatch) -> Self {
333        self.overrides = overrides;
334        self
335    }
336
337    pub fn config_override(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
338        self.overrides
339            .config_overrides
340            .push(ConfigOverride::new(key, value));
341        self
342    }
343
344    pub fn config_override_raw(mut self, raw: impl Into<String>) -> Self {
345        self.overrides
346            .config_overrides
347            .push(ConfigOverride::from_raw(raw));
348        self
349    }
350
351    pub fn profile(mut self, profile: impl Into<String>) -> Self {
352        let profile = profile.into();
353        self.overrides.profile = (!profile.trim().is_empty()).then_some(profile);
354        self
355    }
356
357    pub fn oss(mut self, enable: bool) -> Self {
358        self.overrides.oss = if enable {
359            FlagState::Enable
360        } else {
361            FlagState::Disable
362        };
363        self
364    }
365
366    pub fn enable_feature(mut self, name: impl Into<String>) -> Self {
367        self.overrides.feature_toggles.enable.push(name.into());
368        self
369    }
370
371    pub fn disable_feature(mut self, name: impl Into<String>) -> Self {
372        self.overrides.feature_toggles.disable.push(name.into());
373        self
374    }
375
376    pub fn search(mut self, enable: bool) -> Self {
377        self.overrides.search = if enable {
378            FlagState::Enable
379        } else {
380            FlagState::Disable
381        };
382        self
383    }
384}
385
386impl Default for AppServerRequest {
387    fn default() -> Self {
388        Self::new()
389    }
390}
391
392/// Captured output from app-server codegen commands.
393#[derive(Clone, Debug)]
394pub struct AppServerCodegenOutput {
395    /// Exit status returned by the subcommand.
396    pub status: ExitStatus,
397    /// Captured stdout (mirrored to the console when `mirror_stdout` is true).
398    pub stdout: String,
399    /// Captured stderr (mirrored unless `quiet` is set).
400    pub stderr: String,
401    /// Output directory passed to `--out`.
402    pub out_dir: PathBuf,
403}