crabtalk_runtime/os/
tool.rs1use crate::{Env, host::Host};
4use schemars::JsonSchema;
5use serde::Deserialize;
6use std::collections::BTreeMap;
7use wcore::{
8 agent::{AsTool, ToolDescription},
9 model::Tool,
10};
11
12#[derive(Deserialize, JsonSchema)]
13pub struct Bash {
14 pub command: String,
16 #[serde(default)]
18 pub env: BTreeMap<String, String>,
19}
20
21impl ToolDescription for Bash {
22 const DESCRIPTION: &'static str = "Run a shell command.";
23}
24
25pub fn tools() -> Vec<Tool> {
26 vec![Bash::as_tool()]
27}
28
29impl<H: Host> Env<H> {
30 pub async fn dispatch_bash(
36 &self,
37 args: &str,
38 conversation_id: Option<u64>,
39 ) -> Result<String, String> {
40 let input: Bash =
41 serde_json::from_str(args).map_err(|e| format!("invalid arguments: {e}"))?;
42 let conversation_cwd = if let Some(id) = conversation_id {
43 self.host.conversation_cwd(id)
44 } else {
45 None
46 };
47 let cwd = conversation_cwd.as_deref().unwrap_or(&self.cwd);
48
49 let mut cmd = tokio::process::Command::new("bash");
50 cmd.args(["-c", &input.command])
51 .envs(&input.env)
52 .current_dir(cwd)
53 .stdout(std::process::Stdio::piped())
54 .stderr(std::process::Stdio::piped());
55
56 let child = cmd.spawn().map_err(|e| {
57 serde_json::json!({
58 "stdout": "",
59 "stderr": format!("bash failed: {e}"),
60 "exit_code": -1
61 })
62 .to_string()
63 })?;
64
65 match tokio::time::timeout(std::time::Duration::from_secs(30), child.wait_with_output())
66 .await
67 {
68 Ok(Ok(output)) => {
69 let stdout = String::from_utf8_lossy(&output.stdout);
70 let stderr = String::from_utf8_lossy(&output.stderr);
71 let exit_code = output.status.code().unwrap_or(-1);
72 Ok(serde_json::json!({
73 "stdout": stdout.as_ref(),
74 "stderr": stderr.as_ref(),
75 "exit_code": exit_code
76 })
77 .to_string())
78 }
79 Ok(Err(e)) => Err(serde_json::json!({
80 "stdout": "",
81 "stderr": format!("bash failed: {e}"),
82 "exit_code": -1
83 })
84 .to_string()),
85 Err(_) => Err(serde_json::json!({
86 "stdout": "",
87 "stderr": "bash timed out after 30 seconds",
88 "exit_code": -1
89 })
90 .to_string()),
91 }
92 }
93}