codex/commands/
features.rs1use std::ffi::OsString;
2
3use tokio::{process::Command, time};
4
5use crate::{
6 builder::{apply_cli_overrides, resolve_cli_overrides},
7 process::{spawn_with_retry, tee_stream, ConsoleTarget},
8 ApplyDiffArtifacts, CodexClient, CodexError, FeaturesCommandRequest, FeaturesDisableRequest,
9 FeaturesEnableRequest, FeaturesListOutput, FeaturesListRequest,
10};
11
12impl CodexClient {
13 pub async fn features(
15 &self,
16 request: FeaturesCommandRequest,
17 ) -> Result<ApplyDiffArtifacts, CodexError> {
18 self.run_simple_command_with_overrides(vec![OsString::from("features")], request.overrides)
19 .await
20 }
21
22 pub async fn features_enable(
24 &self,
25 request: FeaturesEnableRequest,
26 ) -> Result<ApplyDiffArtifacts, CodexError> {
27 let FeaturesEnableRequest { feature, overrides } = request;
28 self.run_simple_command_with_overrides(
29 vec![
30 OsString::from("features"),
31 OsString::from("enable"),
32 OsString::from(feature),
33 ],
34 overrides,
35 )
36 .await
37 }
38
39 pub async fn features_disable(
41 &self,
42 request: FeaturesDisableRequest,
43 ) -> Result<ApplyDiffArtifacts, CodexError> {
44 let FeaturesDisableRequest { feature, overrides } = request;
45 self.run_simple_command_with_overrides(
46 vec![
47 OsString::from("features"),
48 OsString::from("disable"),
49 OsString::from(feature),
50 ],
51 overrides,
52 )
53 .await
54 }
55
56 pub async fn list_features(
63 &self,
64 request: FeaturesListRequest,
65 ) -> Result<FeaturesListOutput, CodexError> {
66 let FeaturesListRequest { json, overrides } = request;
67
68 let dir_ctx = self.directory_context()?;
69 let resolved_overrides =
70 resolve_cli_overrides(&self.cli_overrides, &overrides, self.model.as_deref());
71
72 let mut command = Command::new(self.command_env.binary_path());
73 command
74 .arg("features")
75 .arg("list")
76 .stdout(std::process::Stdio::piped())
77 .stderr(std::process::Stdio::piped())
78 .kill_on_drop(true)
79 .current_dir(dir_ctx.path());
80
81 apply_cli_overrides(&mut command, &resolved_overrides, true);
82
83 if json {
84 command.arg("--json");
85 }
86
87 self.command_env.apply(&mut command)?;
88
89 let mut child = spawn_with_retry(&mut command, self.command_env.binary_path())?;
90
91 let stdout = child.stdout.take().ok_or(CodexError::StdoutUnavailable)?;
92 let stderr = child.stderr.take().ok_or(CodexError::StderrUnavailable)?;
93
94 let stdout_task = tokio::spawn(tee_stream(
95 stdout,
96 ConsoleTarget::Stdout,
97 self.mirror_stdout,
98 ));
99 let stderr_task = tokio::spawn(tee_stream(stderr, ConsoleTarget::Stderr, !self.quiet));
100
101 let wait_task = async move {
102 let status = child
103 .wait()
104 .await
105 .map_err(|source| CodexError::Wait { source })?;
106 let stdout_bytes = stdout_task
107 .await
108 .map_err(CodexError::Join)?
109 .map_err(CodexError::CaptureIo)?;
110 let stderr_bytes = stderr_task
111 .await
112 .map_err(CodexError::Join)?
113 .map_err(CodexError::CaptureIo)?;
114 Ok::<_, CodexError>((status, stdout_bytes, stderr_bytes))
115 };
116
117 let (status, stdout_bytes, stderr_bytes) = if self.timeout.is_zero() {
118 wait_task.await?
119 } else {
120 match time::timeout(self.timeout, wait_task).await {
121 Ok(result) => result?,
122 Err(_) => {
123 return Err(CodexError::Timeout {
124 timeout: self.timeout,
125 });
126 }
127 }
128 };
129
130 if !status.success() {
131 return Err(CodexError::NonZeroExit {
132 status,
133 stderr: String::from_utf8(stderr_bytes)?,
134 });
135 }
136
137 let stdout_string = String::from_utf8(stdout_bytes)?;
138 let stderr_string = String::from_utf8(stderr_bytes)?;
139 let (features, format) = crate::version::parse_feature_list_output(&stdout_string, json)
140 .map_err(|reason| CodexError::FeatureListParse {
141 reason,
142 stdout: stdout_string.clone(),
143 })?;
144
145 Ok(FeaturesListOutput {
146 status,
147 stdout: stdout_string,
148 stderr: stderr_string,
149 features,
150 format,
151 })
152 }
153}