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
49#[derive(Debug, Clone, Default)]
51pub struct ResumeOptions {
52 pub session_id: Option<String>,
54
55 pub last: bool,
57
58 pub model: Option<String>,
60
61 pub full_auto: bool,
63
64 pub working_dir: Option<PathBuf>,
66
67 pub env_vars: Vec<(String, String)>,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum SandboxMode {
74 ReadOnly,
76 WorkspaceWrite,
78 DangerFullAccess,
80}
81
82impl SandboxMode {
83 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum ApprovalPolicy {
96 Untrusted,
98 OnFailure,
100 OnRequest,
102 Never,
104}
105
106impl ApprovalPolicy {
107 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 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 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}