use std::{path::PathBuf, time::Duration};
#[cfg(test)]
use std::ffi::OsString;
use crate::home::CommandEnvironment;
use tokio::process::Command;
mod cli_overrides;
mod types;
pub use types::{
ApprovalPolicy, CliOverrides, CliOverridesPatch, ColorMode, ConfigOverride, FeatureToggles,
FlagState, LocalProvider, ModelVerbosity, ReasoningEffort, ReasoningOverrides,
ReasoningSummary, ReasoningSummaryFormat, SafetyOverride, SandboxMode,
};
pub(super) type ResolvedCliOverrides = cli_overrides::ResolvedCliOverrides;
#[cfg(test)]
pub(super) const DEFAULT_REASONING_CONFIG_GPT5: &[(&str, &str)] =
cli_overrides::DEFAULT_REASONING_CONFIG_GPT5;
#[cfg(test)]
pub(super) const DEFAULT_REASONING_CONFIG_GPT5_CODEX: &[(&str, &str)] =
cli_overrides::DEFAULT_REASONING_CONFIG_GPT5_CODEX;
#[cfg(test)]
pub(super) const DEFAULT_REASONING_CONFIG_GPT5_1: &[(&str, &str)] =
cli_overrides::DEFAULT_REASONING_CONFIG_GPT5_1;
#[cfg(test)]
pub(super) fn reasoning_config_for(
model: Option<&str>,
) -> Option<&'static [(&'static str, &'static str)]> {
cli_overrides::reasoning_config_for(model)
}
pub(super) fn resolve_cli_overrides(
builder: &CliOverrides,
patch: &CliOverridesPatch,
model: Option<&str>,
) -> ResolvedCliOverrides {
cli_overrides::resolve_cli_overrides(builder, patch, model)
}
#[cfg(test)]
pub(super) fn cli_override_args(
resolved: &ResolvedCliOverrides,
include_search: bool,
) -> Vec<OsString> {
cli_overrides::cli_override_args(resolved, include_search)
}
pub(super) fn apply_cli_overrides(
command: &mut Command,
resolved: &ResolvedCliOverrides,
include_search: bool,
) {
cli_overrides::apply_cli_overrides(command, resolved, include_search);
}
#[derive(Clone, Debug)]
pub struct CodexClientBuilder {
pub(super) binary: PathBuf,
pub(super) codex_home: Option<PathBuf>,
pub(super) create_home_dirs: bool,
pub(super) model: Option<String>,
pub(super) timeout: Duration,
pub(super) color_mode: ColorMode,
pub(super) working_dir: Option<PathBuf>,
pub(super) add_dirs: Vec<PathBuf>,
pub(super) images: Vec<PathBuf>,
pub(super) json_output: bool,
pub(super) output_schema: bool,
pub(super) quiet: bool,
pub(super) mirror_stdout: bool,
pub(super) json_event_log: Option<PathBuf>,
pub(super) cli_overrides: CliOverrides,
pub(super) capability_overrides: crate::CapabilityOverrides,
pub(super) capability_cache_policy: crate::CapabilityCachePolicy,
}
impl CodexClientBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn binary(mut self, binary: impl Into<PathBuf>) -> Self {
self.binary = binary.into();
self
}
pub fn codex_home(mut self, home: impl Into<PathBuf>) -> Self {
self.codex_home = Some(home.into());
self
}
pub fn create_home_dirs(mut self, enable: bool) -> Self {
self.create_home_dirs = enable;
self
}
pub fn model(mut self, model: impl Into<String>) -> Self {
let model = model.into();
self.model = (!model.trim().is_empty()).then_some(model);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn color_mode(mut self, color_mode: ColorMode) -> Self {
self.color_mode = color_mode;
self
}
pub fn working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.working_dir = Some(dir.into());
self
}
pub fn add_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.add_dirs.push(path.into());
self
}
pub fn add_dirs<I, P>(mut self, dirs: I) -> Self
where
I: IntoIterator<Item = P>,
P: Into<PathBuf>,
{
self.add_dirs = dirs.into_iter().map(Into::into).collect();
self
}
pub fn image(mut self, path: impl Into<PathBuf>) -> Self {
self.images.push(path.into());
self
}
pub fn images<I, P>(mut self, images: I) -> Self
where
I: IntoIterator<Item = P>,
P: Into<PathBuf>,
{
self.images = images.into_iter().map(Into::into).collect();
self
}
pub fn json(mut self, enable: bool) -> Self {
self.json_output = enable;
self
}
pub fn output_schema(mut self, enable: bool) -> Self {
self.output_schema = enable;
self
}
pub fn quiet(mut self, enable: bool) -> Self {
self.quiet = enable;
self
}
pub fn mirror_stdout(mut self, enable: bool) -> Self {
self.mirror_stdout = enable;
self
}
pub fn json_event_log(mut self, path: impl Into<PathBuf>) -> Self {
self.json_event_log = Some(path.into());
self
}
pub fn config_override(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.cli_overrides
.config_overrides
.push(ConfigOverride::new(key, value));
self
}
pub fn config_override_raw(mut self, raw: impl Into<String>) -> Self {
self.cli_overrides
.config_overrides
.push(ConfigOverride::from_raw(raw));
self
}
pub fn config_overrides<I, K, V>(mut self, overrides: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
self.cli_overrides.config_overrides = overrides
.into_iter()
.map(|(key, value)| ConfigOverride::new(key, value))
.collect();
self
}
pub fn profile(mut self, profile: impl Into<String>) -> Self {
let profile = profile.into();
self.cli_overrides.profile = (!profile.trim().is_empty()).then_some(profile);
self
}
pub fn reasoning_effort(mut self, effort: ReasoningEffort) -> Self {
self.cli_overrides.reasoning.effort = Some(effort);
self
}
pub fn reasoning_summary(mut self, summary: ReasoningSummary) -> Self {
self.cli_overrides.reasoning.summary = Some(summary);
self
}
pub fn reasoning_verbosity(mut self, verbosity: ModelVerbosity) -> Self {
self.cli_overrides.reasoning.verbosity = Some(verbosity);
self
}
pub fn reasoning_summary_format(mut self, format: ReasoningSummaryFormat) -> Self {
self.cli_overrides.reasoning.summary_format = Some(format);
self
}
pub fn supports_reasoning_summaries(mut self, enable: bool) -> Self {
self.cli_overrides.reasoning.supports_summaries = Some(enable);
self
}
pub fn auto_reasoning_defaults(mut self, enable: bool) -> Self {
self.cli_overrides.auto_reasoning_defaults = enable;
self
}
pub fn approval_policy(mut self, policy: ApprovalPolicy) -> Self {
self.cli_overrides.approval_policy = Some(policy);
self
}
pub fn sandbox_mode(mut self, mode: SandboxMode) -> Self {
self.cli_overrides.sandbox_mode = Some(mode);
self
}
pub fn full_auto(mut self, enable: bool) -> Self {
self.cli_overrides.safety_override = if enable {
SafetyOverride::FullAuto
} else {
SafetyOverride::Inherit
};
self
}
pub fn dangerously_bypass_approvals_and_sandbox(mut self, enable: bool) -> Self {
self.cli_overrides.safety_override = if enable {
SafetyOverride::DangerouslyBypass
} else {
SafetyOverride::Inherit
};
self
}
pub fn cd(mut self, dir: impl Into<PathBuf>) -> Self {
self.cli_overrides.cd = Some(dir.into());
self
}
pub fn remote(mut self, remote: impl Into<String>) -> Self {
let remote = remote.into();
self.cli_overrides.remote = (!remote.trim().is_empty()).then_some(remote);
self
}
pub fn remote_auth_token_env(mut self, env_var: impl Into<String>) -> Self {
let env_var = env_var.into();
self.cli_overrides.remote_auth_token_env = (!env_var.trim().is_empty()).then_some(env_var);
self
}
pub fn local_provider(mut self, provider: LocalProvider) -> Self {
self.cli_overrides.local_provider = Some(provider);
self
}
pub fn oss(mut self, enable: bool) -> Self {
self.cli_overrides.oss = if enable {
FlagState::Enable
} else {
FlagState::Disable
};
self
}
pub fn enable_feature(mut self, name: impl Into<String>) -> Self {
self.cli_overrides.feature_toggles.enable.push(name.into());
self
}
pub fn disable_feature(mut self, name: impl Into<String>) -> Self {
self.cli_overrides.feature_toggles.disable.push(name.into());
self
}
pub fn search(mut self, enable: bool) -> Self {
self.cli_overrides.search = if enable {
FlagState::Enable
} else {
FlagState::Disable
};
self
}
pub fn capability_overrides(mut self, overrides: crate::CapabilityOverrides) -> Self {
self.capability_overrides = overrides;
self
}
pub fn capability_feature_overrides(
mut self,
overrides: crate::CapabilityFeatureOverrides,
) -> Self {
self.capability_overrides.features = overrides;
self
}
pub fn capability_feature_hints(mut self, features: crate::CodexFeatureFlags) -> Self {
self.capability_overrides.features = crate::CapabilityFeatureOverrides::enabling(features);
self
}
pub fn capability_snapshot(mut self, snapshot: crate::CodexCapabilities) -> Self {
self.capability_overrides.snapshot = Some(snapshot);
self
}
pub fn capability_version_override(mut self, version: crate::CodexVersionInfo) -> Self {
self.capability_overrides.version = Some(version);
self
}
pub fn capability_cache_policy(mut self, policy: crate::CapabilityCachePolicy) -> Self {
self.capability_cache_policy = policy;
self
}
pub fn bypass_capability_cache(mut self, bypass: bool) -> Self {
self.capability_cache_policy = if bypass {
crate::CapabilityCachePolicy::Bypass
} else {
crate::CapabilityCachePolicy::PreferCache
};
self
}
pub fn build(self) -> crate::CodexClient {
let command_env =
CommandEnvironment::new(self.binary, self.codex_home, self.create_home_dirs);
crate::CodexClient {
command_env,
model: self.model,
timeout: self.timeout,
color_mode: self.color_mode,
working_dir: self.working_dir,
add_dirs: self.add_dirs,
images: self.images,
json_output: self.json_output,
output_schema: self.output_schema,
quiet: self.quiet,
mirror_stdout: self.mirror_stdout,
json_event_log: self.json_event_log,
cli_overrides: self.cli_overrides,
capability_overrides: self.capability_overrides,
capability_cache_policy: self.capability_cache_policy,
}
}
}
impl Default for CodexClientBuilder {
fn default() -> Self {
Self {
binary: crate::defaults::default_binary_path(),
codex_home: None,
create_home_dirs: true,
model: None,
timeout: crate::defaults::DEFAULT_TIMEOUT,
color_mode: ColorMode::Never,
working_dir: None,
add_dirs: Vec::new(),
images: Vec::new(),
json_output: false,
output_schema: false,
quiet: false,
mirror_stdout: true,
json_event_log: None,
cli_overrides: CliOverrides::default(),
capability_overrides: crate::CapabilityOverrides::default(),
capability_cache_policy: crate::CapabilityCachePolicy::default(),
}
}
}