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