1use std::path::PathBuf;
8
9#[derive(Debug, Clone, Default)]
14pub struct ExecOptions {
15 pub model: Option<String>,
17
18 pub sandbox: Option<SandboxMode>,
20
21 pub approval: Option<ApprovalPolicy>,
23
24 pub full_auto: bool,
26
27 pub profile: Option<String>,
29
30 pub config_overrides: Vec<(String, String)>,
32
33 pub working_dir: Option<PathBuf>,
35
36 pub ephemeral: bool,
38
39 pub output_schema: Option<String>,
41
42 pub images: Vec<PathBuf>,
44
45 pub env_vars: Vec<(String, String)>,
47
48 pub dangerously_bypass_sandbox: bool,
51}
52
53#[derive(Debug, Clone, Default)]
55pub struct ResumeOptions {
56 pub session_id: Option<String>,
58
59 pub last: bool,
61
62 pub model: Option<String>,
64
65 pub full_auto: bool,
67
68 pub working_dir: Option<PathBuf>,
70
71 pub env_vars: Vec<(String, String)>,
73
74 pub dangerously_bypass_sandbox: bool,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum SandboxMode {
82 ReadOnly,
84 WorkspaceWrite,
86 DangerFullAccess,
88}
89
90impl SandboxMode {
91 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum ApprovalPolicy {
104 Untrusted,
106 OnFailure,
108 OnRequest,
110 Never,
112}
113
114impl ApprovalPolicy {
115 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 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 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}