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(["--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 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}