Skip to main content

apiari_codex_sdk/
options.rs

1//! Execution options and CLI argument building.
2//!
3//! [`ExecOptions`] and [`ResumeOptions`] hold all the knobs for launching a
4//! codex execution. Their [`to_cli_args`](ExecOptions::to_cli_args) methods
5//! convert options into `codex` CLI flags.
6
7use std::path::PathBuf;
8
9/// Options for `codex exec`.
10///
11/// These map onto `codex` CLI flags. Only fields that are set will produce
12/// flags; `None` / empty-vec fields are omitted.
13#[derive(Debug, Clone, Default)]
14pub struct ExecOptions {
15    /// Model to use (e.g. `"o4-mini"`, `"codex-mini"`).
16    pub model: Option<String>,
17
18    /// Sandbox mode controlling file system access.
19    pub sandbox: Option<SandboxMode>,
20
21    /// Approval policy for tool execution.
22    pub approval: Option<ApprovalPolicy>,
23
24    /// Enable fully autonomous mode (auto-approve everything).
25    pub full_auto: bool,
26
27    /// Named profile to load.
28    pub profile: Option<String>,
29
30    /// Config key=value overrides (passed as `-c key=value`).
31    pub config_overrides: Vec<(String, String)>,
32
33    /// Working directory for the execution.
34    pub working_dir: Option<PathBuf>,
35
36    /// Run in ephemeral mode (no session persistence).
37    pub ephemeral: bool,
38
39    /// JSON schema for structured output validation.
40    pub output_schema: Option<String>,
41
42    /// Image file paths to include as context.
43    pub images: Vec<PathBuf>,
44
45    /// Additional environment variables to set.
46    pub env_vars: Vec<(String, String)>,
47}
48
49/// Options for resuming a previous `codex exec` session.
50#[derive(Debug, Clone, Default)]
51pub struct ResumeOptions {
52    /// Resume a specific session by ID.
53    pub session_id: Option<String>,
54
55    /// Resume the most recent session.
56    pub last: bool,
57
58    /// Model to use for the resumed session.
59    pub model: Option<String>,
60
61    /// Enable fully autonomous mode.
62    pub full_auto: bool,
63
64    /// Working directory for the execution.
65    pub working_dir: Option<PathBuf>,
66
67    /// Additional environment variables to set.
68    pub env_vars: Vec<(String, String)>,
69}
70
71/// Sandbox modes controlling file system access.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum SandboxMode {
74    /// Read-only access to the file system.
75    ReadOnly,
76    /// Can write within the workspace directory.
77    WorkspaceWrite,
78    /// Full file system access (dangerous).
79    DangerFullAccess,
80}
81
82impl SandboxMode {
83    /// Return the CLI flag value for this mode.
84    pub fn as_str(&self) -> &'static str {
85        match self {
86            Self::ReadOnly => "read-only",
87            Self::WorkspaceWrite => "workspace-write",
88            Self::DangerFullAccess => "danger-full-access",
89        }
90    }
91}
92
93/// Approval policies for tool execution.
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum ApprovalPolicy {
96    /// Require approval for all tool use.
97    Untrusted,
98    /// Only require approval on failure.
99    OnFailure,
100    /// Only require approval when explicitly requested.
101    OnRequest,
102    /// Never require approval.
103    Never,
104}
105
106impl ApprovalPolicy {
107    /// Return the CLI flag value for this policy.
108    pub fn as_str(&self) -> &'static str {
109        match self {
110            Self::Untrusted => "untrusted",
111            Self::OnFailure => "on-failure",
112            Self::OnRequest => "on-request",
113            Self::Never => "never",
114        }
115    }
116}
117
118impl ExecOptions {
119    /// Convert these options into CLI arguments for the `codex` binary.
120    ///
121    /// This does **not** include `exec` or `--json` — those are added by
122    /// [`ReadOnlyTransport::spawn`](crate::transport::ReadOnlyTransport::spawn).
123    pub fn to_cli_args(&self) -> Vec<String> {
124        let mut args = Vec::new();
125
126        if let Some(ref model) = self.model {
127            args.extend(["-m".to_owned(), model.clone()]);
128        }
129        if let Some(sandbox) = self.sandbox {
130            args.extend(["--sandbox".to_owned(), sandbox.as_str().to_owned()]);
131        }
132        if let Some(approval) = self.approval {
133            args.extend([
134                "--approval-policy".to_owned(),
135                approval.as_str().to_owned(),
136            ]);
137        }
138        if self.full_auto {
139            args.push("--full-auto".to_owned());
140        }
141        if let Some(ref profile) = self.profile {
142            args.extend(["--profile".to_owned(), profile.clone()]);
143        }
144        for (key, value) in &self.config_overrides {
145            args.extend(["-c".to_owned(), format!("{key}={value}")]);
146        }
147        if self.ephemeral {
148            args.push("--ephemeral".to_owned());
149        }
150        if let Some(ref schema) = self.output_schema {
151            args.extend(["--output-schema".to_owned(), schema.clone()]);
152        }
153        for image in &self.images {
154            args.extend(["--image".to_owned(), image.display().to_string()]);
155        }
156
157        args
158    }
159}
160
161impl ResumeOptions {
162    /// Convert these options into CLI arguments for the `codex` binary.
163    pub fn to_cli_args(&self) -> Vec<String> {
164        let mut args = Vec::new();
165
166        if let Some(ref id) = self.session_id {
167            args.extend(["--resume".to_owned(), id.clone()]);
168        }
169        if self.last {
170            args.push("--last".to_owned());
171        }
172        if let Some(ref model) = self.model {
173            args.extend(["-m".to_owned(), model.clone()]);
174        }
175        if self.full_auto {
176            args.push("--full-auto".to_owned());
177        }
178
179        args
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn empty_options_produce_no_args() {
189        let opts = ExecOptions::default();
190        assert!(opts.to_cli_args().is_empty());
191    }
192
193    #[test]
194    fn model_and_full_auto() {
195        let opts = ExecOptions {
196            model: Some("o4-mini".to_owned()),
197            full_auto: true,
198            ..Default::default()
199        };
200        let args = opts.to_cli_args();
201        assert_eq!(args, vec!["-m", "o4-mini", "--full-auto"]);
202    }
203
204    #[test]
205    fn sandbox_and_approval() {
206        let opts = ExecOptions {
207            sandbox: Some(SandboxMode::ReadOnly),
208            approval: Some(ApprovalPolicy::OnFailure),
209            ..Default::default()
210        };
211        let args = opts.to_cli_args();
212        assert!(args.contains(&"--sandbox".to_owned()));
213        assert!(args.contains(&"read-only".to_owned()));
214        assert!(args.contains(&"--approval-policy".to_owned()));
215        assert!(args.contains(&"on-failure".to_owned()));
216    }
217
218    #[test]
219    fn config_overrides() {
220        let opts = ExecOptions {
221            config_overrides: vec![
222                ("key1".to_owned(), "val1".to_owned()),
223                ("key2".to_owned(), "val2".to_owned()),
224            ],
225            ..Default::default()
226        };
227        let args = opts.to_cli_args();
228        assert_eq!(args, vec!["-c", "key1=val1", "-c", "key2=val2"]);
229    }
230
231    #[test]
232    fn images() {
233        let opts = ExecOptions {
234            images: vec![PathBuf::from("a.png"), PathBuf::from("b.jpg")],
235            ..Default::default()
236        };
237        let args = opts.to_cli_args();
238        assert_eq!(args, vec!["--image", "a.png", "--image", "b.jpg"]);
239    }
240
241    #[test]
242    fn resume_options() {
243        let opts = ResumeOptions {
244            session_id: Some("sess_123".to_owned()),
245            model: Some("o4-mini".to_owned()),
246            full_auto: true,
247            ..Default::default()
248        };
249        let args = opts.to_cli_args();
250        assert_eq!(
251            args,
252            vec!["--resume", "sess_123", "-m", "o4-mini", "--full-auto"]
253        );
254    }
255
256    #[test]
257    fn resume_last() {
258        let opts = ResumeOptions {
259            last: true,
260            ..Default::default()
261        };
262        let args = opts.to_cli_args();
263        assert_eq!(args, vec!["--last"]);
264    }
265
266    #[test]
267    fn profile_and_ephemeral() {
268        let opts = ExecOptions {
269            profile: Some("custom".to_owned()),
270            ephemeral: true,
271            ..Default::default()
272        };
273        let args = opts.to_cli_args();
274        assert_eq!(args, vec!["--profile", "custom", "--ephemeral"]);
275    }
276
277    #[test]
278    fn output_schema() {
279        let opts = ExecOptions {
280            output_schema: Some(r#"{"type":"object"}"#.to_owned()),
281            ..Default::default()
282        };
283        let args = opts.to_cli_args();
284        assert_eq!(args, vec!["--output-schema", r#"{"type":"object"}"#]);
285    }
286}