nils_common/provider_runtime/
exec.rs1use 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}