Skip to content

Commit c7e440f

Browse files
authored
feat: added env variable USER_PROMPT and enabled removal + sanitizing of en… (#2244)
1 parent 4a342f5 commit c7e440f

File tree

3 files changed

+41
-15
lines changed

3 files changed

+41
-15
lines changed

crates/chat-cli/src/cli/chat/cli/hooks.rs

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ impl HookExecutor {
164164
&mut self,
165165
hooks: Vec<&Hook>,
166166
output: &mut impl Write,
167+
prompt: Option<&str>
167168
) -> Result<Vec<(Hook, String)>, ChatError> {
168169
let mut results = Vec::with_capacity(hooks.len());
169170
let mut futures = FuturesUnordered::new();
@@ -181,7 +182,7 @@ impl HookExecutor {
181182
results.push((index, (hook.clone(), cached.clone())));
182183
continue;
183184
}
184-
let future = self.execute_hook(hook);
185+
let future = self.execute_hook(hook, prompt);
185186
futures.push(async move { (index, future.await) });
186187
}
187188

@@ -299,38 +300,47 @@ impl HookExecutor {
299300
Ok(results.into_iter().map(|(_, r)| r).collect())
300301
}
301302

302-
async fn execute_hook<'a>(&self, hook: &'a Hook) -> (&'a Hook, Result<String>, Duration) {
303+
async fn execute_hook<'a>(&self, hook: &'a Hook, prompt: Option<&str>) -> (&'a Hook, Result<String>, Duration) {
303304
let start_time = Instant::now();
304305
let result = match hook.r#type {
305-
HookType::Inline => self.execute_inline_hook(hook).await,
306+
HookType::Inline => self.execute_inline_hook(hook, prompt).await,
306307
};
307308

308309
(hook, result, start_time.elapsed())
309310
}
310311

311-
async fn execute_inline_hook(&self, hook: &Hook) -> Result<String> {
312+
async fn execute_inline_hook(&self, hook: &Hook, user_prompt: Option<&str>) -> Result<String> {
312313
let command = hook.command.as_ref().ok_or_else(|| eyre!("no command specified"))?;
313314

314315
#[cfg(unix)]
315-
let command_future = tokio::process::Command::new("bash")
316-
.arg("-c")
316+
let mut cmd = tokio::process::Command::new("bash");
317+
#[cfg(unix)]
318+
let cmd = cmd.arg("-c")
317319
.arg(command)
318320
.stdin(Stdio::piped())
319321
.stdout(Stdio::piped())
320-
.stderr(Stdio::piped())
321-
.output();
322+
.stderr(Stdio::piped());
322323

323324
#[cfg(windows)]
324-
let command_future = tokio::process::Command::new("cmd")
325-
.arg("/C")
325+
let mut cmd = tokio::process::Command::new("cmd");
326+
#[cfg(windows)]
327+
let cmd = cmd.arg("/C")
326328
.arg(command)
327329
.stdin(Stdio::piped())
328330
.stdout(Stdio::piped())
329-
.stderr(Stdio::piped())
330-
.output();
331+
.stderr(Stdio::piped());
331332

332333
let timeout = Duration::from_millis(hook.timeout_ms);
333334

335+
// Set USER_PROMPT environment variable if provided
336+
if let Some(prompt) = user_prompt {
337+
// Sanitize the prompt to avoid issues with special characters
338+
let sanitized_prompt = sanitize_user_prompt(prompt);
339+
cmd.env("USER_PROMPT", sanitized_prompt);
340+
}
341+
342+
let command_future = cmd.output();
343+
334344
// Run with timeout
335345
match tokio::time::timeout(timeout, command_future).await {
336346
Ok(result) => {
@@ -387,6 +397,19 @@ impl HookExecutor {
387397
}
388398
}
389399

400+
/// Sanitizes a string value to be used as an environment variable
401+
fn sanitize_user_prompt(input: &str) -> String {
402+
// Limit the size of input to first 4096 characters
403+
let truncated = if input.len() > 4096 {
404+
&input[0..4096]
405+
} else {
406+
input
407+
};
408+
409+
// Remove any potentially problematic characters
410+
truncated.replace(|c: char| c.is_control() && c != '\n' && c != '\r' && c != '\t', "")
411+
}
412+
390413
#[deny(missing_docs)]
391414
#[derive(Debug, PartialEq, Args)]
392415
#[command(

crates/chat-cli/src/cli/chat/context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ impl ContextManager {
272272
/// Run all the currently enabled hooks from both the global and profile contexts.
273273
/// # Returns
274274
/// A vector containing pairs of a [`Hook`] definition and its execution output
275-
pub async fn run_hooks(&mut self, output: &mut impl Write) -> Result<Vec<(Hook, String)>, ChatError> {
275+
pub async fn run_hooks(&mut self, output: &mut impl Write, prompt: Option<&str>) -> Result<Vec<(Hook, String)>, ChatError> {
276276
let hooks = self
277277
.profile_config
278278
.hooks
@@ -282,7 +282,7 @@ impl ContextManager {
282282
hook as &Hook
283283
})
284284
.collect::<Vec<_>>();
285-
self.hook_executor.run_hooks(hooks, output).await
285+
self.hook_executor.run_hooks(hooks, output, prompt).await
286286
}
287287
}
288288

crates/chat-cli/src/cli/chat/conversation.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,10 @@ impl ConversationState {
360360
// Run hooks and add to conversation start and next user message.
361361
let mut conversation_start_context = None;
362362
if let (true, Some(cm)) = (run_hooks, self.context_manager.as_mut()) {
363-
let hook_results = cm.run_hooks(output).await?;
363+
// Get the user prompt from next_message if available
364+
let user_prompt = self.next_message.as_ref().and_then(|m| m.prompt());
365+
let hook_results = cm.run_hooks(output, user_prompt).await?;
366+
364367
conversation_start_context = Some(format_hook_context(hook_results.iter(), HookTrigger::ConversationStart));
365368

366369
// add per prompt content to next_user_message if available

0 commit comments

Comments
 (0)