codex_wrapper/command/
review.rs1use crate::Codex;
2use crate::command::CodexCommand;
3#[cfg(feature = "json")]
4use crate::error::Error;
5use crate::error::Result;
6use crate::exec::{self, CommandOutput};
7#[cfg(feature = "json")]
8use crate::types::JsonLineEvent;
9
10#[derive(Debug, Clone)]
11pub struct ReviewCommand {
12 prompt: Option<String>,
13 config_overrides: Vec<String>,
14 enabled_features: Vec<String>,
15 disabled_features: Vec<String>,
16 uncommitted: bool,
17 base: Option<String>,
18 commit: Option<String>,
19 model: Option<String>,
20 title: Option<String>,
21 full_auto: bool,
22 dangerously_bypass_approvals_and_sandbox: bool,
23 skip_git_repo_check: bool,
24 ephemeral: bool,
25 json: bool,
26 output_last_message: Option<String>,
27 retry_policy: Option<crate::retry::RetryPolicy>,
28}
29
30impl ReviewCommand {
31 #[must_use]
32 pub fn new() -> Self {
33 Self {
34 prompt: None,
35 config_overrides: Vec::new(),
36 enabled_features: Vec::new(),
37 disabled_features: Vec::new(),
38 uncommitted: false,
39 base: None,
40 commit: None,
41 model: None,
42 title: None,
43 full_auto: false,
44 dangerously_bypass_approvals_and_sandbox: false,
45 skip_git_repo_check: false,
46 ephemeral: false,
47 json: false,
48 output_last_message: None,
49 retry_policy: None,
50 }
51 }
52
53 #[must_use]
54 pub fn prompt(mut self, prompt: impl Into<String>) -> Self {
55 self.prompt = Some(prompt.into());
56 self
57 }
58
59 #[must_use]
60 pub fn config(mut self, key_value: impl Into<String>) -> Self {
61 self.config_overrides.push(key_value.into());
62 self
63 }
64
65 #[must_use]
66 pub fn enable(mut self, feature: impl Into<String>) -> Self {
67 self.enabled_features.push(feature.into());
68 self
69 }
70
71 #[must_use]
72 pub fn disable(mut self, feature: impl Into<String>) -> Self {
73 self.disabled_features.push(feature.into());
74 self
75 }
76
77 #[must_use]
78 pub fn uncommitted(mut self) -> Self {
79 self.uncommitted = true;
80 self
81 }
82
83 #[must_use]
84 pub fn base(mut self, branch: impl Into<String>) -> Self {
85 self.base = Some(branch.into());
86 self
87 }
88
89 #[must_use]
90 pub fn commit(mut self, sha: impl Into<String>) -> Self {
91 self.commit = Some(sha.into());
92 self
93 }
94
95 #[must_use]
96 pub fn model(mut self, model: impl Into<String>) -> Self {
97 self.model = Some(model.into());
98 self
99 }
100
101 #[must_use]
102 pub fn title(mut self, title: impl Into<String>) -> Self {
103 self.title = Some(title.into());
104 self
105 }
106
107 #[must_use]
108 pub fn full_auto(mut self) -> Self {
109 self.full_auto = true;
110 self
111 }
112
113 #[must_use]
114 pub fn dangerously_bypass_approvals_and_sandbox(mut self) -> Self {
115 self.dangerously_bypass_approvals_and_sandbox = true;
116 self
117 }
118
119 #[must_use]
120 pub fn skip_git_repo_check(mut self) -> Self {
121 self.skip_git_repo_check = true;
122 self
123 }
124
125 #[must_use]
126 pub fn ephemeral(mut self) -> Self {
127 self.ephemeral = true;
128 self
129 }
130
131 #[must_use]
132 pub fn json(mut self) -> Self {
133 self.json = true;
134 self
135 }
136
137 #[must_use]
138 pub fn output_last_message(mut self, path: impl Into<String>) -> Self {
139 self.output_last_message = Some(path.into());
140 self
141 }
142
143 #[must_use]
144 pub fn retry(mut self, policy: crate::retry::RetryPolicy) -> Self {
145 self.retry_policy = Some(policy);
146 self
147 }
148
149 #[cfg(feature = "json")]
150 pub async fn execute_json_lines(&self, codex: &Codex) -> Result<Vec<JsonLineEvent>> {
151 let mut args = self.args();
152 if !self.json {
153 args.push("--json".into());
154 }
155
156 let output = exec::run_codex_with_retry(codex, args, self.retry_policy.as_ref()).await?;
157 output
158 .stdout
159 .lines()
160 .filter(|line| line.trim_start().starts_with('{'))
161 .map(|line| {
162 serde_json::from_str(line).map_err(|source| Error::Json {
163 message: format!("failed to parse JSONL event: {line}"),
164 source,
165 })
166 })
167 .collect()
168 }
169}
170
171impl Default for ReviewCommand {
172 fn default() -> Self {
173 Self::new()
174 }
175}
176
177impl CodexCommand for ReviewCommand {
178 type Output = CommandOutput;
179
180 fn args(&self) -> Vec<String> {
181 let mut args = vec!["exec".into(), "review".into()];
182 for value in &self.config_overrides {
183 args.push("-c".into());
184 args.push(value.clone());
185 }
186 for value in &self.enabled_features {
187 args.push("--enable".into());
188 args.push(value.clone());
189 }
190 for value in &self.disabled_features {
191 args.push("--disable".into());
192 args.push(value.clone());
193 }
194 if self.uncommitted {
195 args.push("--uncommitted".into());
196 }
197 if let Some(base) = &self.base {
198 args.push("--base".into());
199 args.push(base.clone());
200 }
201 if let Some(commit) = &self.commit {
202 args.push("--commit".into());
203 args.push(commit.clone());
204 }
205 if let Some(model) = &self.model {
206 args.push("--model".into());
207 args.push(model.clone());
208 }
209 if let Some(title) = &self.title {
210 args.push("--title".into());
211 args.push(title.clone());
212 }
213 if self.full_auto {
214 args.push("--full-auto".into());
215 }
216 if self.dangerously_bypass_approvals_and_sandbox {
217 args.push("--dangerously-bypass-approvals-and-sandbox".into());
218 }
219 if self.skip_git_repo_check {
220 args.push("--skip-git-repo-check".into());
221 }
222 if self.ephemeral {
223 args.push("--ephemeral".into());
224 }
225 if self.json {
226 args.push("--json".into());
227 }
228 if let Some(path) = &self.output_last_message {
229 args.push("--output-last-message".into());
230 args.push(path.clone());
231 }
232 if let Some(prompt) = &self.prompt {
233 args.push(prompt.clone());
234 }
235 args
236 }
237
238 async fn execute(&self, codex: &Codex) -> Result<CommandOutput> {
239 exec::run_codex_with_retry(codex, self.args(), self.retry_policy.as_ref()).await
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn review_args() {
249 let args = ReviewCommand::new()
250 .uncommitted()
251 .model("gpt-5")
252 .json()
253 .prompt("focus on correctness")
254 .args();
255
256 assert_eq!(
257 args,
258 vec![
259 "exec",
260 "review",
261 "--uncommitted",
262 "--model",
263 "gpt-5",
264 "--json",
265 "focus on correctness",
266 ]
267 );
268 }
269}