agent_engine/tools/shell/
start.rs1use crate::tools::{Tool, ToolContext};
4use crate::{Result, RuntimeError};
5use serde_json::{json, Value};
6
7pub struct ShellStartTool;
8
9#[async_trait::async_trait]
10impl Tool for ShellStartTool {
11 fn origin(&self) -> crate::tools::ToolOrigin {
12 crate::tools::ToolOrigin::Builtin
13 }
14
15 fn name(&self) -> &str {
16 "shell_start"
17 }
18
19 fn description(&self) -> &str {
20 "Start a new interactive shell session with a PTY. Returns a session ID and the initial output. Use shell_send to interact and shell_end to close."
21 }
22
23 fn parameters(&self) -> Value {
24 json!({
25 "type": "object",
26 "properties": {
27 "command": {
28 "type": "string",
29 "description": "Command to run (default: user's default shell). Examples: 'bash', 'python3', 'ssh user@host'"
30 },
31 "working_directory": {
32 "type": "string",
33 "description": "Working directory for the session (default: current directory)"
34 },
35 "env": {
36 "type": "object",
37 "description": "Additional environment variables as key-value pairs",
38 "additionalProperties": { "type": "string" }
39 },
40 "rows": {
41 "type": "integer",
42 "description": "Terminal rows (default: from config, fallback 24)"
43 },
44 "cols": {
45 "type": "integer",
46 "description": "Terminal columns (default: from config, fallback 80)"
47 },
48 "readiness_timeout_ms": {
49 "type": "integer",
50 "description": "Override output readiness timeout for this session (ms)"
51 },
52 "idle_timeout": {
53 "type": "integer",
54 "description": "Override idle timeout for this session (seconds)"
55 }
56 },
57 "required": []
58 })
59 }
60
61 async fn execute(&self, params: Value, ctx: ToolContext) -> Result<String> {
62 let mgr = ctx
63 .capabilities
64 .session_manager
65 .as_ref()
66 .ok_or_else(|| RuntimeError::Tool("Shell sessions not available".into()))?;
67
68 let command = params["command"].as_str().map(|s| s.to_string());
69 let working_directory = params["working_directory"]
70 .as_str()
71 .map(|s| s.to_string())
72 .or_else(|| {
73 ctx.capabilities
74 .cwd
75 .as_ref()
76 .map(|p| p.to_string_lossy().into_owned())
77 });
78 let rows = params["rows"].as_u64().map(|r| r as u16);
79 let cols = params["cols"].as_u64().map(|c| c as u16);
80 let readiness_timeout_ms = params["readiness_timeout_ms"].as_u64();
81 let idle_timeout = params["idle_timeout"].as_u64();
82
83 let env = params["env"]
84 .as_object()
85 .map(|obj| {
86 obj.iter()
87 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
88 .collect()
89 })
90 .unwrap_or_default();
91
92 let opts = super::SessionOpts {
93 command,
94 working_directory,
95 env,
96 rows,
97 cols,
98 readiness_timeout_ms,
99 idle_timeout,
100 };
101
102 let (session_id, output, status) = mgr
103 .create_session_cancellable(
104 opts,
105 ctx.channels.tx_delta.as_ref(),
106 ctx.limits.max_tool_buffer,
107 ctx.capabilities.launch_cancel.as_ref(),
108 )
109 .await?;
110
111 let mut result = format!("[Session {} | {}]\n", session_id, status);
112 if !output.is_empty() {
113 result.push_str(&output);
114 }
115 Ok(result)
116 }
117}