use indexmap::IndexMap;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CodexExecArgs {
pub input: String,
pub base_url: Option<String>,
pub api_key: Option<String>,
pub thread_id: Option<String>,
pub images: Option<Vec<String>>,
pub model: Option<String>,
pub sandbox_mode: Option<super::SandboxMode>,
pub working_directory: Option<String>,
pub additional_directories: Option<Vec<String>>,
pub skip_git_repo_check: Option<bool>,
pub output_schema_file: Option<String>,
pub model_reasoning_effort: Option<super::ModelReasoningEffort>,
pub network_access_enabled: Option<bool>,
pub web_search_enabled: Option<bool>,
pub approval_policy: Option<super::ApprovalMode>,
}
impl CodexExecArgs {
pub fn to_command_args(&self) -> Vec<String> {
let mut args: Vec<String> = vec!["exec".into(), "--experimental-json".into()];
if let Some(model) = &self.model {
args.push("--model".into());
args.push(model.clone());
}
if let Some(sandbox_mode) = self.sandbox_mode {
args.push("--sandbox".into());
args.push(sandbox_mode_value(sandbox_mode).into());
}
if let Some(cwd) = &self.working_directory {
args.push("--cd".into());
args.push(cwd.clone());
}
if let Some(dirs) = &self.additional_directories {
for dir in dirs {
args.push("--add-dir".into());
args.push(dir.clone());
}
}
if self.skip_git_repo_check == Some(true) {
args.push("--skip-git-repo-check".into());
}
if let Some(schema_path) = &self.output_schema_file {
args.push("--output-schema".into());
args.push(schema_path.clone());
}
if let Some(effort) = self.model_reasoning_effort {
args.push("--config".into());
args.push(format!(
"model_reasoning_effort=\"{}\"",
reasoning_effort_value(effort)
));
}
if let Some(enabled) = self.network_access_enabled {
args.push("--config".into());
args.push(format!(
"sandbox_workspace_write.network_access={}",
bool_lit(enabled)
));
}
if let Some(enabled) = self.web_search_enabled {
args.push("--config".into());
args.push(format!("features.web_search_request={}", bool_lit(enabled)));
}
if let Some(policy) = self.approval_policy {
args.push("--config".into());
args.push(format!(
"approval_policy=\"{}\"",
approval_mode_value(policy)
));
}
if let Some(images) = &self.images {
for image in images {
args.push("--image".into());
args.push(image.clone());
}
}
if let Some(thread_id) = &self.thread_id {
args.push("resume".into());
args.push(thread_id.clone());
}
args
}
pub fn to_env(&self, mut base: IndexMap<String, String>) -> IndexMap<String, String> {
base.entry(INTERNAL_ORIGINATOR_ENV.into())
.or_insert_with(|| RUST_SDK_ORIGINATOR.into());
if let Some(url) = &self.base_url {
base.insert("OPENAI_BASE_URL".into(), url.clone());
}
if let Some(key) = &self.api_key {
base.insert("CODEX_API_KEY".into(), key.clone());
}
base
}
}
const INTERNAL_ORIGINATOR_ENV: &str = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE";
const RUST_SDK_ORIGINATOR: &str = "codex_sdk_rs";
fn bool_lit(value: bool) -> &'static str {
if value { "true" } else { "false" }
}
fn sandbox_mode_value(mode: super::SandboxMode) -> &'static str {
match mode {
super::SandboxMode::ReadOnly => "read-only",
super::SandboxMode::WorkspaceWrite => "workspace-write",
super::SandboxMode::DangerFullAccess => "danger-full-access",
}
}
fn reasoning_effort_value(effort: super::ModelReasoningEffort) -> &'static str {
match effort {
super::ModelReasoningEffort::Minimal => "minimal",
super::ModelReasoningEffort::Low => "low",
super::ModelReasoningEffort::Medium => "medium",
super::ModelReasoningEffort::High => "high",
}
}
fn approval_mode_value(mode: super::ApprovalMode) -> &'static str {
match mode {
super::ApprovalMode::Never => "never",
super::ApprovalMode::OnRequest => "on-request",
super::ApprovalMode::OnFailure => "on-failure",
super::ApprovalMode::Untrusted => "untrusted",
}
}