llm_coding_tools_core/operations/bash/
mod.rs

1//! Shell command execution operation.
2
3use crate::ToolOutput;
4use core::fmt::Write;
5use serde::Serialize;
6
7/// Default buffer capacity for stdout/stderr pipe reads.
8/// 32KB covers typical command output without reallocations.
9const PIPE_BUFFER_CAPACITY: usize = 32 * 1024;
10
11/// Result of shell command execution.
12#[derive(Debug, Clone, Serialize)]
13pub struct BashOutput {
14    /// Exit code from the command (None if killed by timeout).
15    pub exit_code: Option<i32>,
16    /// Standard output from the command.
17    pub stdout: String,
18    /// Standard error output from the command.
19    pub stderr: String,
20}
21
22impl BashOutput {
23    /// Formats the bash output into a [`ToolOutput`] for LLM consumption.
24    ///
25    /// Combines stdout, stderr (with `[stderr]` label), and non-zero exit codes
26    /// into a single formatted string.
27    pub fn format_output(&self) -> ToolOutput {
28        // Pre-allocate: stdout + stderr + labels overhead (~34 bytes)
29        // 34 bytes assumes the exit code is up to 10 digits, i.e. int32 range.
30        let estimated = self.stdout.len() + self.stderr.len() + 34;
31        let mut content = String::with_capacity(estimated);
32
33        if !self.stdout.is_empty() {
34            content.push_str(&self.stdout);
35        }
36
37        if !self.stderr.is_empty() {
38            if !content.is_empty() {
39                content.push('\n');
40            }
41            content.push_str("[stderr]\n");
42            content.push_str(&self.stderr);
43        }
44
45        if let Some(code) = self.exit_code {
46            if code != 0 {
47                if !content.is_empty() {
48                    content.push('\n');
49                }
50                // Use write! to avoid format! allocation
51                let _ = write!(content, "[exit code: {code}]");
52            }
53        }
54
55        ToolOutput::new(content)
56    }
57}
58
59#[cfg(not(feature = "blocking"))]
60mod async_impl;
61#[cfg(not(feature = "blocking"))]
62pub use async_impl::execute_command;
63
64#[cfg(feature = "blocking")]
65mod blocking_impl;
66#[cfg(feature = "blocking")]
67pub use blocking_impl::execute_command;