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(["--approval-policy".to_owned(), approval.as_str().to_owned()]);
134        }
135        if self.full_auto {
136            args.push("--full-auto".to_owned());
137        }
138        if let Some(ref profile) = self.profile {
139            args.extend(["--profile".to_owned(), profile.clone()]);
140        }
141        for (key, value) in &self.config_overrides {
142            args.extend(["-c".to_owned(), format!("{key}={value}")]);
143        }
144        if self.ephemeral {
145            args.push("--ephemeral".to_owned());
146        }
147        if let Some(ref schema) = self.output_schema {
148            args.extend(["--output-schema".to_owned(), schema.clone()]);
149        }
150        for image in &self.images {
151            args.extend(["--image".to_owned(), image.display().to_string()]);
152        }
153
154        args
155    }
156}
157
158impl ResumeOptions {
159    /// Convert these options into CLI arguments for the `codex` binary.
160    pub fn to_cli_args(&self) -> Vec<String> {
161        let mut args = Vec::new();
162
163        if let Some(ref id) = self.session_id {
164            args.extend(["--resume".to_owned(), id.clone()]);
165        }
166        if self.last {
167            args.push("--last".to_owned());
168        }
169        if let Some(ref model) = self.model {
170            args.extend(["-m".to_owned(), model.clone()]);
171        }
172        if self.full_auto {
173            args.push("--full-auto".to_owned());
174        }
175
176        args
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn empty_options_produce_no_args() {
186        let opts = ExecOptions::default();
187        assert!(opts.to_cli_args().is_empty());
188    }
189
190    #[test]
191    fn model_and_full_auto() {
192        let opts = ExecOptions {
193            model: Some("o4-mini".to_owned()),
194            full_auto: true,
195            ..Default::default()
196        };
197        let args = opts.to_cli_args();
198        assert_eq!(args, vec!["-m", "o4-mini", "--full-auto"]);
199    }
200
201    #[test]
202    fn sandbox_and_approval() {
203        let opts = ExecOptions {
204            sandbox: Some(SandboxMode::ReadOnly),
205            approval: Some(ApprovalPolicy::OnFailure),
206            ..Default::default()
207        };
208        let args = opts.to_cli_args();
209        assert!(args.contains(&"--sandbox".to_owned()));
210        assert!(args.contains(&"read-only".to_owned()));
211        assert!(args.contains(&"--approval-policy".to_owned()));
212        assert!(args.contains(&"on-failure".to_owned()));
213    }
214
215    #[test]
216    fn config_overrides() {
217        let opts = ExecOptions {
218            config_overrides: vec![
219                ("key1".to_owned(), "val1".to_owned()),
220                ("key2".to_owned(), "val2".to_owned()),
221            ],
222            ..Default::default()
223        };
224        let args = opts.to_cli_args();
225        assert_eq!(args, vec!["-c", "key1=val1", "-c", "key2=val2"]);
226    }
227
228    #[test]
229    fn images() {
230        let opts = ExecOptions {
231            images: vec![PathBuf::from("a.png"), PathBuf::from("b.jpg")],
232            ..Default::default()
233        };
234        let args = opts.to_cli_args();
235        assert_eq!(args, vec!["--image", "a.png", "--image", "b.jpg"]);
236    }
237
238    #[test]
239    fn resume_options() {
240        let opts = ResumeOptions {
241            session_id: Some("sess_123".to_owned()),
242            model: Some("o4-mini".to_owned()),
243            full_auto: true,
244            ..Default::default()
245        };
246        let args = opts.to_cli_args();
247        assert_eq!(
248            args,
249            vec!["--resume", "sess_123", "-m", "o4-mini", "--full-auto"]
250        );
251    }
252
253    #[test]
254    fn resume_last() {
255        let opts = ResumeOptions {
256            last: true,
257            ..Default::default()
258        };
259        let args = opts.to_cli_args();
260        assert_eq!(args, vec!["--last"]);
261    }
262
263    #[test]
264    fn profile_and_ephemeral() {
265        let opts = ExecOptions {
266            profile: Some("custom".to_owned()),
267            ephemeral: true,
268            ..Default::default()
269        };
270        let args = opts.to_cli_args();
271        assert_eq!(args, vec!["--profile", "custom", "--ephemeral"]);
272    }
273
274    #[test]
275    fn output_schema() {
276        let opts = ExecOptions {
277            output_schema: Some(r#"{"type":"object"}"#.to_owned()),
278            ..Default::default()
279        };
280        let args = opts.to_cli_args();
281        assert_eq!(args, vec!["--output-schema", r#"{"type":"object"}"#]);
282    }
283}