crabtalk_runtime/os/
tool.rs1use crate::{RuntimeHook, bridge::RuntimeBridge};
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<B: RuntimeBridge> RuntimeHook<B> {
30 pub async fn dispatch_bash(&self, args: &str, session_id: Option<u64>) -> String {
32 let input: Bash = match serde_json::from_str(args) {
33 Ok(v) => v,
34 Err(e) => return format!("invalid arguments: {e}"),
35 };
36 let session_cwd = if let Some(id) = session_id {
37 self.bridge.session_cwd(id)
38 } else {
39 None
40 };
41 let cwd = session_cwd.as_deref().unwrap_or(&self.cwd);
42
43 let mut cmd = tokio::process::Command::new("bash");
44 cmd.args(["-c", &input.command])
45 .envs(&input.env)
46 .current_dir(cwd)
47 .stdout(std::process::Stdio::piped())
48 .stderr(std::process::Stdio::piped());
49
50 let child = match cmd.spawn() {
51 Ok(c) => c,
52 Err(e) => {
53 return serde_json::json!({
54 "stdout": "",
55 "stderr": format!("bash failed: {e}"),
56 "exit_code": -1
57 })
58 .to_string();
59 }
60 };
61
62 match tokio::time::timeout(std::time::Duration::from_secs(30), child.wait_with_output())
63 .await
64 {
65 Ok(Ok(output)) => {
66 let stdout = String::from_utf8_lossy(&output.stdout);
67 let stderr = String::from_utf8_lossy(&output.stderr);
68 let exit_code = output.status.code().unwrap_or(-1);
69 serde_json::json!({
70 "stdout": stdout.as_ref(),
71 "stderr": stderr.as_ref(),
72 "exit_code": exit_code
73 })
74 .to_string()
75 }
76 Ok(Err(e)) => serde_json::json!({
77 "stdout": "",
78 "stderr": format!("bash failed: {e}"),
79 "exit_code": -1
80 })
81 .to_string(),
82 Err(_) => serde_json::json!({
83 "stdout": "",
84 "stderr": "bash timed out after 30 seconds",
85 "exit_code": -1
86 })
87 .to_string(),
88 }
89 }
90}