langchain_rust/tools/command_executor/
command_executor.rs1use std::error::Error;
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5use serde_json::{json, Value};
6
7use crate::tools::Tool;
8
9pub struct CommandExecutor {
10 platform: String,
11}
12
13impl CommandExecutor {
14 pub fn new<S: Into<String>>(platform: S) -> Self {
20 Self {
21 platform: platform.into(),
22 }
23 }
24}
25
26impl Default for CommandExecutor {
27 fn default() -> Self {
28 Self::new("linux")
29 }
30}
31
32#[derive(Deserialize, Serialize, Debug)]
33struct CommandInput {
34 cmd: String,
35 #[serde(default)]
36 args: Vec<String>,
37}
38#[derive(Serialize, Deserialize, Debug)]
39struct CommandsWrapper {
40 commands: Vec<CommandInput>,
41}
42
43#[async_trait]
44impl Tool for CommandExecutor {
45 fn name(&self) -> String {
46 String::from("Command_Executor")
47 }
48 fn description(&self) -> String {
49 format!(
50 r#""This tool let you run command on the terminal"
51 "The input should be an array with commands for the following platform: {}"
52 "examle of input: [{{ "cmd": "ls", "args": [] }},{{"cmd":"mkdir","args":["test"]}}]"
53 "Should be a comma separated commands"
54 "#,
55 self.platform
56 )
57 }
58
59 fn parameters(&self) -> Value {
60 let prompt = format!(
61 "This tool let you run command on the terminal.
62 The input should be an array with commands for the following platform: {}",
63 self.platform
64 );
65 json!(
66
67 {
68 "description": prompt,
69 "type": "object",
70 "properties": {
71 "commands": {
72 "description": "An array of command objects to be executed",
73 "type": "array",
74 "items": {
75 "type": "object",
76 "properties": {
77 "cmd": {
78 "type": "string",
79 "description": "The command to execute"
80 },
81 "args": {
82 "type": "array",
83 "items": {
84 "type": "string"
85 },
86 "default": [],
87 "description": "List of arguments for the command"
88 }
89 },
90 "required": ["cmd"],
91 "additionalProperties": false,
92 "description": "Object representing a command and its optional arguments"
93 }
94 }
95 },
96 "required": ["commands"],
97 "additionalProperties": false
98 }
99 )
100 }
101
102 async fn parse_input(&self, input: &str) -> Value {
103 log::info!("Parsing input: {}", input);
104
105 let wrapper_result = serde_json::from_str::<CommandsWrapper>(input);
107
108 if let Ok(wrapper) = wrapper_result {
109 serde_json::to_value(wrapper.commands).unwrap_or_else(|err| {
112 log::error!("Serialization error: {}", err);
113 Value::Null
114 })
115 } else {
116 let commands_result = serde_json::from_str::<Vec<CommandInput>>(input);
119
120 commands_result.map_or_else(
121 |err| {
122 log::error!("Failed to parse input: {}", err);
123 Value::Null
124 },
125 |commands| serde_json::to_value(commands).unwrap_or(Value::Null),
126 )
127 }
128 }
129
130 async fn run(&self, input: Value) -> Result<String, Box<dyn Error>> {
131 let commands: Vec<CommandInput> = serde_json::from_value(input)?;
132 let mut result = String::new();
133
134 for command in commands {
135 let mut command_to_execute = std::process::Command::new(&command.cmd);
136 command_to_execute.args(&command.args);
137
138 let output = command_to_execute.output()?;
139
140 result.push_str(&format!(
141 "Command: {}\nOutput: {}",
142 command.cmd,
143 String::from_utf8_lossy(&output.stdout),
144 ));
145
146 if !output.status.success() {
147 return Err(Box::new(std::io::Error::new(
148 std::io::ErrorKind::Other,
149 format!(
150 "Command {} failed with status: {}",
151 command.cmd, output.status
152 ),
153 )));
154 }
155 }
156
157 Ok(result)
158 }
159}
160
161#[cfg(test)]
162mod test {
163 use super::*;
164 use serde_json::json;
165 #[tokio::test]
166 async fn test_with_string_executor() {
167 let tool = CommandExecutor::new("linux");
168 let input = json!({
169 "commands": [
170 {
171 "cmd": "ls",
172 "args": []
173 }
174 ]
175 });
176 println!("{}", &input.to_string());
177 let result = tool.call(&input.to_string()).await.unwrap();
178 println!("Res: {}", result);
179 }
180}