1use std::{path::PathBuf, time::Duration};
2
3#[cfg(test)]
4use std::ffi::OsString;
5
6use crate::home::CommandEnvironment;
7use tokio::process::Command;
8
9mod cli_overrides;
10mod types;
11
12pub use types::{
13 ApprovalPolicy, CliOverrides, CliOverridesPatch, ColorMode, ConfigOverride, FeatureToggles,
14 FlagState, LocalProvider, ModelVerbosity, ReasoningEffort, ReasoningOverrides,
15 ReasoningSummary, ReasoningSummaryFormat, SafetyOverride, SandboxMode,
16};
17
18pub(super) type ResolvedCliOverrides = cli_overrides::ResolvedCliOverrides;
19
20#[cfg(test)]
21pub(super) const DEFAULT_REASONING_CONFIG_GPT5: &[(&str, &str)] =
22 cli_overrides::DEFAULT_REASONING_CONFIG_GPT5;
23#[cfg(test)]
24pub(super) const DEFAULT_REASONING_CONFIG_GPT5_CODEX: &[(&str, &str)] =
25 cli_overrides::DEFAULT_REASONING_CONFIG_GPT5_CODEX;
26#[cfg(test)]
27pub(super) const DEFAULT_REASONING_CONFIG_GPT5_1: &[(&str, &str)] =
28 cli_overrides::DEFAULT_REASONING_CONFIG_GPT5_1;
29
30#[cfg(test)]
31pub(super) fn reasoning_config_for(
32 model: Option<&str>,
33) -> Option<&'static [(&'static str, &'static str)]> {
34 cli_overrides::reasoning_config_for(model)
35}
36
37pub(super) fn resolve_cli_overrides(
38 builder: &CliOverrides,
39 patch: &CliOverridesPatch,
40 model: Option<&str>,
41) -> ResolvedCliOverrides {
42 cli_overrides::resolve_cli_overrides(builder, patch, model)
43}
44
45#[cfg(test)]
46pub(super) fn cli_override_args(
47 resolved: &ResolvedCliOverrides,
48 include_search: bool,
49) -> Vec<OsString> {
50 cli_overrides::cli_override_args(resolved, include_search)
51}
52
53pub(super) fn apply_cli_overrides(
54 command: &mut Command,
55 resolved: &ResolvedCliOverrides,
56 include_search: bool,
57) {
58 cli_overrides::apply_cli_overrides(command, resolved, include_search);
59}
60
61#[derive(Clone, Debug)]
66pub struct CodexClientBuilder {
67 pub(super) binary: PathBuf,
68 pub(super) codex_home: Option<PathBuf>,
69 pub(super) create_home_dirs: bool,
70 pub(super) model: Option<String>,
71 pub(super) timeout: Duration,
72 pub(super) color_mode: ColorMode,
73 pub(super) working_dir: Option<PathBuf>,
74 pub(super) add_dirs: Vec<PathBuf>,
75 pub(super) images: Vec<PathBuf>,
76 pub(super) json_output: bool,
77 pub(super) output_schema: bool,
78 pub(super) quiet: bool,
79 pub(super) mirror_stdout: bool,
80 pub(super) json_event_log: Option<PathBuf>,
81 pub(super) cli_overrides: CliOverrides,
82 pub(super) capability_overrides: crate::CapabilityOverrides,
83 pub(super) capability_cache_policy: crate::CapabilityCachePolicy,
84}
85
86impl CodexClientBuilder {
87 pub fn new() -> Self {
89 Self::default()
90 }
91
92 pub fn binary(mut self, binary: impl Into<PathBuf>) -> Self {
98 self.binary = binary.into();
99 self
100 }
101
102 pub fn codex_home(mut self, home: impl Into<PathBuf>) -> Self {
105 self.codex_home = Some(home.into());
106 self
107 }
108
109 pub fn create_home_dirs(mut self, enable: bool) -> Self {
112 self.create_home_dirs = enable;
113 self
114 }
115
116 pub fn model(mut self, model: impl Into<String>) -> Self {
118 let model = model.into();
119 self.model = (!model.trim().is_empty()).then_some(model);
120 self
121 }
122
123 pub fn timeout(mut self, timeout: Duration) -> Self {
125 self.timeout = timeout;
126 self
127 }
128
129 pub fn color_mode(mut self, color_mode: ColorMode) -> Self {
131 self.color_mode = color_mode;
132 self
133 }
134
135 pub fn working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
137 self.working_dir = Some(dir.into());
138 self
139 }
140
141 pub fn add_dir(mut self, path: impl Into<PathBuf>) -> Self {
145 self.add_dirs.push(path.into());
146 self
147 }
148
149 pub fn add_dirs<I, P>(mut self, dirs: I) -> Self
151 where
152 I: IntoIterator<Item = P>,
153 P: Into<PathBuf>,
154 {
155 self.add_dirs = dirs.into_iter().map(Into::into).collect();
156 self
157 }
158
159 pub fn image(mut self, path: impl Into<PathBuf>) -> Self {
161 self.images.push(path.into());
162 self
163 }
164
165 pub fn images<I, P>(mut self, images: I) -> Self
167 where
168 I: IntoIterator<Item = P>,
169 P: Into<PathBuf>,
170 {
171 self.images = images.into_iter().map(Into::into).collect();
172 self
173 }
174
175 pub fn json(mut self, enable: bool) -> Self {
182 self.json_output = enable;
183 self
184 }
185
186 pub fn output_schema(mut self, enable: bool) -> Self {
190 self.output_schema = enable;
191 self
192 }
193
194 pub fn quiet(mut self, enable: bool) -> Self {
196 self.quiet = enable;
197 self
198 }
199
200 pub fn mirror_stdout(mut self, enable: bool) -> Self {
204 self.mirror_stdout = enable;
205 self
206 }
207
208 pub fn json_event_log(mut self, path: impl Into<PathBuf>) -> Self {
212 self.json_event_log = Some(path.into());
213 self
214 }
215
216 pub fn config_override(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
218 self.cli_overrides
219 .config_overrides
220 .push(ConfigOverride::new(key, value));
221 self
222 }
223
224 pub fn config_override_raw(mut self, raw: impl Into<String>) -> Self {
226 self.cli_overrides
227 .config_overrides
228 .push(ConfigOverride::from_raw(raw));
229 self
230 }
231
232 pub fn config_overrides<I, K, V>(mut self, overrides: I) -> Self
234 where
235 I: IntoIterator<Item = (K, V)>,
236 K: Into<String>,
237 V: Into<String>,
238 {
239 self.cli_overrides.config_overrides = overrides
240 .into_iter()
241 .map(|(key, value)| ConfigOverride::new(key, value))
242 .collect();
243 self
244 }
245
246 pub fn profile(mut self, profile: impl Into<String>) -> Self {
248 let profile = profile.into();
249 self.cli_overrides.profile = (!profile.trim().is_empty()).then_some(profile);
250 self
251 }
252
253 pub fn reasoning_effort(mut self, effort: ReasoningEffort) -> Self {
255 self.cli_overrides.reasoning.effort = Some(effort);
256 self
257 }
258
259 pub fn reasoning_summary(mut self, summary: ReasoningSummary) -> Self {
261 self.cli_overrides.reasoning.summary = Some(summary);
262 self
263 }
264
265 pub fn reasoning_verbosity(mut self, verbosity: ModelVerbosity) -> Self {
267 self.cli_overrides.reasoning.verbosity = Some(verbosity);
268 self
269 }
270
271 pub fn reasoning_summary_format(mut self, format: ReasoningSummaryFormat) -> Self {
273 self.cli_overrides.reasoning.summary_format = Some(format);
274 self
275 }
276
277 pub fn supports_reasoning_summaries(mut self, enable: bool) -> Self {
279 self.cli_overrides.reasoning.supports_summaries = Some(enable);
280 self
281 }
282
283 pub fn auto_reasoning_defaults(mut self, enable: bool) -> Self {
285 self.cli_overrides.auto_reasoning_defaults = enable;
286 self
287 }
288
289 pub fn approval_policy(mut self, policy: ApprovalPolicy) -> Self {
291 self.cli_overrides.approval_policy = Some(policy);
292 self
293 }
294
295 pub fn sandbox_mode(mut self, mode: SandboxMode) -> Self {
297 self.cli_overrides.sandbox_mode = Some(mode);
298 self
299 }
300
301 pub fn full_auto(mut self, enable: bool) -> Self {
303 self.cli_overrides.safety_override = if enable {
304 SafetyOverride::FullAuto
305 } else {
306 SafetyOverride::Inherit
307 };
308 self
309 }
310
311 pub fn dangerously_bypass_approvals_and_sandbox(mut self, enable: bool) -> Self {
313 self.cli_overrides.safety_override = if enable {
314 SafetyOverride::DangerouslyBypass
315 } else {
316 SafetyOverride::Inherit
317 };
318 self
319 }
320
321 pub fn cd(mut self, dir: impl Into<PathBuf>) -> Self {
323 self.cli_overrides.cd = Some(dir.into());
324 self
325 }
326
327 pub fn remote(mut self, remote: impl Into<String>) -> Self {
329 let remote = remote.into();
330 self.cli_overrides.remote = (!remote.trim().is_empty()).then_some(remote);
331 self
332 }
333
334 pub fn remote_auth_token_env(mut self, env_var: impl Into<String>) -> Self {
336 let env_var = env_var.into();
337 self.cli_overrides.remote_auth_token_env = (!env_var.trim().is_empty()).then_some(env_var);
338 self
339 }
340
341 pub fn local_provider(mut self, provider: LocalProvider) -> Self {
343 self.cli_overrides.local_provider = Some(provider);
344 self
345 }
346
347 pub fn oss(mut self, enable: bool) -> Self {
349 self.cli_overrides.oss = if enable {
350 FlagState::Enable
351 } else {
352 FlagState::Disable
353 };
354 self
355 }
356
357 pub fn enable_feature(mut self, name: impl Into<String>) -> Self {
359 self.cli_overrides.feature_toggles.enable.push(name.into());
360 self
361 }
362
363 pub fn disable_feature(mut self, name: impl Into<String>) -> Self {
365 self.cli_overrides.feature_toggles.disable.push(name.into());
366 self
367 }
368
369 pub fn search(mut self, enable: bool) -> Self {
371 self.cli_overrides.search = if enable {
372 FlagState::Enable
373 } else {
374 FlagState::Disable
375 };
376 self
377 }
378
379 pub fn capability_overrides(mut self, overrides: crate::CapabilityOverrides) -> Self {
381 self.capability_overrides = overrides;
382 self
383 }
384
385 pub fn capability_feature_overrides(
387 mut self,
388 overrides: crate::CapabilityFeatureOverrides,
389 ) -> Self {
390 self.capability_overrides.features = overrides;
391 self
392 }
393
394 pub fn capability_feature_hints(mut self, features: crate::CodexFeatureFlags) -> Self {
396 self.capability_overrides.features = crate::CapabilityFeatureOverrides::enabling(features);
397 self
398 }
399
400 pub fn capability_snapshot(mut self, snapshot: crate::CodexCapabilities) -> Self {
404 self.capability_overrides.snapshot = Some(snapshot);
405 self
406 }
407
408 pub fn capability_version_override(mut self, version: crate::CodexVersionInfo) -> Self {
410 self.capability_overrides.version = Some(version);
411 self
412 }
413
414 pub fn capability_cache_policy(mut self, policy: crate::CapabilityCachePolicy) -> Self {
418 self.capability_cache_policy = policy;
419 self
420 }
421
422 pub fn bypass_capability_cache(mut self, bypass: bool) -> Self {
425 self.capability_cache_policy = if bypass {
426 crate::CapabilityCachePolicy::Bypass
427 } else {
428 crate::CapabilityCachePolicy::PreferCache
429 };
430 self
431 }
432
433 pub fn build(self) -> crate::CodexClient {
435 let command_env =
436 CommandEnvironment::new(self.binary, self.codex_home, self.create_home_dirs);
437 crate::CodexClient {
438 command_env,
439 model: self.model,
440 timeout: self.timeout,
441 color_mode: self.color_mode,
442 working_dir: self.working_dir,
443 add_dirs: self.add_dirs,
444 images: self.images,
445 json_output: self.json_output,
446 output_schema: self.output_schema,
447 quiet: self.quiet,
448 mirror_stdout: self.mirror_stdout,
449 json_event_log: self.json_event_log,
450 cli_overrides: self.cli_overrides,
451 capability_overrides: self.capability_overrides,
452 capability_cache_policy: self.capability_cache_policy,
453 }
454 }
455}
456
457impl Default for CodexClientBuilder {
458 fn default() -> Self {
459 Self {
460 binary: crate::defaults::default_binary_path(),
461 codex_home: None,
462 create_home_dirs: true,
463 model: None,
464 timeout: crate::defaults::DEFAULT_TIMEOUT,
465 color_mode: ColorMode::Never,
466 working_dir: None,
467 add_dirs: Vec::new(),
468 images: Vec::new(),
469 json_output: false,
470 output_schema: false,
471 quiet: false,
472 mirror_stdout: true,
473 json_event_log: None,
474 cli_overrides: CliOverrides::default(),
475 capability_overrides: crate::CapabilityOverrides::default(),
476 capability_cache_policy: crate::CapabilityCachePolicy::default(),
477 }
478 }
479}