Skip to main content

nils_common/provider_runtime/
exec.rs

1use crate::env as shared_env;
2use crate::process as shared_process;
3use std::io::Write;
4use std::sync::atomic::Ordering;
5
6use super::error::CoreError;
7use super::profile::{ExecInvocation, ProviderProfile};
8
9pub fn require_allow_dangerous(
10    profile: &ProviderProfile,
11    caller: Option<&str>,
12    stderr: &mut impl Write,
13) -> bool {
14    if is_true_env(profile, stderr) {
15        return true;
16    }
17
18    let prefix = match caller {
19        Some(value) if !value.is_empty() => value,
20        _ => profile.exec.default_caller_prefix,
21    };
22    let _ = writeln!(
23        stderr,
24        "{prefix}: disabled (set {}=true)",
25        profile.env.allow_dangerous_enabled,
26    );
27    false
28}
29
30pub fn allow_dangerous_status(
31    profile: &ProviderProfile,
32    caller: Option<&str>,
33) -> (bool, Option<String>) {
34    let mut stderr = Vec::new();
35    let enabled = require_allow_dangerous(profile, caller, &mut stderr);
36    let text = String::from_utf8_lossy(&stderr).trim_end().to_string();
37    (enabled, if text.is_empty() { None } else { Some(text) })
38}
39
40pub fn check_allow_dangerous(
41    profile: &ProviderProfile,
42    caller: Option<&str>,
43) -> Result<(), CoreError> {
44    let (enabled, message) = allow_dangerous_status(profile, caller);
45    if enabled {
46        return Ok(());
47    }
48    Err(CoreError::validation(
49        "disabled-policy",
50        message.unwrap_or_else(|| {
51            format!(
52                "execution disabled (set {}=true)",
53                profile.env.allow_dangerous_enabled,
54            )
55        }),
56    )
57    .with_retryable(false))
58}
59
60pub fn exec_dangerous(
61    profile: &ProviderProfile,
62    prompt: &str,
63    caller: &str,
64    stderr: &mut impl Write,
65) -> i32 {
66    if prompt.is_empty() {
67        let _ = writeln!(
68            stderr,
69            "{}: missing prompt",
70            profile.exec.missing_prompt_label
71        );
72        return 1;
73    }
74
75    if !require_allow_dangerous(profile, Some(caller), stderr) {
76        return 1;
77    }
78
79    match profile.exec.invocation {
80        ExecInvocation::CodexStyle => exec_dangerous_codex_style(profile, prompt, stderr),
81        ExecInvocation::GeminiStyle => exec_dangerous_gemini_style(profile, prompt, stderr),
82    }
83}
84
85fn exec_dangerous_codex_style(
86    profile: &ProviderProfile,
87    prompt: &str,
88    stderr: &mut impl Write,
89) -> i32 {
90    let model = shared_env::env_or_default(profile.env.model, profile.defaults.model);
91    let reasoning = shared_env::env_or_default(profile.env.reasoning, profile.defaults.reasoning);
92    let reasoning_arg = format!("model_reasoning_effort=\"{}\"", reasoning);
93    let args = [
94        "exec",
95        "--dangerously-bypass-approvals-and-sandbox",
96        "-s",
97        "workspace-write",
98        "-m",
99        model.as_str(),
100        "-c",
101        reasoning_arg.as_str(),
102        "--",
103        prompt,
104    ];
105
106    run_exec(profile, &args, stderr)
107}
108
109fn exec_dangerous_gemini_style(
110    profile: &ProviderProfile,
111    prompt: &str,
112    stderr: &mut impl Write,
113) -> i32 {
114    let model = shared_env::env_or_default(profile.env.model, profile.defaults.model);
115    let prompt_arg = format!("--prompt={prompt}");
116    let args = [
117        prompt_arg.as_str(),
118        "--model",
119        model.as_str(),
120        "--approval-mode",
121        "yolo",
122    ];
123
124    run_exec(profile, &args, stderr)
125}
126
127fn run_exec(profile: &ProviderProfile, args: &[&str], stderr: &mut impl Write) -> i32 {
128    match shared_process::run_status_inherit(profile.exec.binary_name, args) {
129        Ok(status) => status.code().unwrap_or(1),
130        Err(err) => {
131            let _ = writeln!(stderr, "{}: {err}", profile.exec.failed_exec_message_prefix,);
132            1
133        }
134    }
135}
136
137fn is_true_env(profile: &ProviderProfile, stderr: &mut impl Write) -> bool {
138    let key = profile.env.allow_dangerous_enabled;
139    let Ok(raw) = std::env::var(key) else {
140        return false;
141    };
142
143    let trimmed = raw.trim();
144    if trimmed.is_empty() {
145        return false;
146    }
147
148    match trimmed.to_ascii_lowercase().as_str() {
149        "true" => true,
150        "false" => false,
151        _ => {
152            if !profile
153                .exec
154                .warned_invalid_allow_dangerous
155                .swap(true, Ordering::SeqCst)
156            {
157                let _ = writeln!(
158                    stderr,
159                    "warning: {key} must be true|false (got: {raw}); treating as false"
160                );
161            }
162            false
163        }
164    }
165}