1use nils_common::process as shared_process;
2use std::io::Write;
3use std::sync::atomic::{AtomicBool, Ordering};
4
5const DEFAULT_MODEL: &str = "gpt-5.1-codex-mini";
6const DEFAULT_REASONING: &str = "medium";
7
8static WARNED_INVALID_ALLOW_DANGEROUS: AtomicBool = AtomicBool::new(false);
9
10fn env_or_default(key: &str, default: &str) -> String {
11 std::env::var(key).unwrap_or_else(|_| default.to_string())
12}
13
14fn is_true_env(key: &str, stderr: &mut impl Write) -> bool {
15 let Ok(raw) = std::env::var(key) else {
16 return false;
17 };
18
19 let trimmed = raw.trim();
20 if trimmed.is_empty() {
21 return false;
22 }
23
24 match trimmed.to_ascii_lowercase().as_str() {
25 "true" => true,
26 "false" => false,
27 _ => {
28 if !WARNED_INVALID_ALLOW_DANGEROUS.swap(true, Ordering::SeqCst) {
29 let _ = writeln!(
30 stderr,
31 "warning: {key} must be true|false (got: {raw}); treating as false"
32 );
33 }
34 false
35 }
36 }
37}
38
39pub fn require_allow_dangerous(caller: Option<&str>, stderr: &mut impl Write) -> bool {
40 if is_true_env("CODEX_ALLOW_DANGEROUS_ENABLED", stderr) {
41 return true;
42 }
43
44 let prefix = match caller {
45 Some(value) if !value.is_empty() => value,
46 _ => "codex",
47 };
48 let _ = writeln!(
49 stderr,
50 "{prefix}: disabled (set CODEX_ALLOW_DANGEROUS_ENABLED=true)"
51 );
52 false
53}
54
55pub fn exec_dangerous(prompt: &str, caller: &str, stderr: &mut impl Write) -> i32 {
56 if prompt.is_empty() {
57 let _ = writeln!(stderr, "_codex_exec_dangerous: missing prompt");
58 return 1;
59 }
60
61 if !require_allow_dangerous(Some(caller), stderr) {
62 return 1;
63 }
64
65 let model = env_or_default("CODEX_CLI_MODEL", DEFAULT_MODEL);
66 let reasoning = env_or_default("CODEX_CLI_REASONING", DEFAULT_REASONING);
67 let reasoning_arg = format!("model_reasoning_effort=\"{}\"", reasoning);
68 let args = [
69 "exec",
70 "--dangerously-bypass-approvals-and-sandbox",
71 "-s",
72 "workspace-write",
73 "-m",
74 model.as_str(),
75 "-c",
76 reasoning_arg.as_str(),
77 "--",
78 prompt,
79 ];
80
81 match shared_process::run_status_inherit("codex", &args) {
82 Ok(status) => status.code().unwrap_or(1),
83 Err(err) => {
84 let _ = writeln!(stderr, "codex-tools: failed to run codex exec: {err}");
85 1
86 }
87 }
88}