use super::background::BackgroundManager;
use super::{PlanDecision, Tool, ToolResult, parse_tool_args, schema_to_tool_params};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::{Value, json};
use std::borrow::Cow;
use std::sync::{Arc, atomic::AtomicBool};
#[derive(Deserialize, JsonSchema)]
struct SessionParams {
action: String,
sid: String,
#[serde(default)]
text: Option<String>,
#[serde(default)]
timeout_ms: Option<u64>,
}
#[derive(Debug)]
pub struct SessionTool {
pub manager: Arc<BackgroundManager>,
}
impl SessionTool {
pub const NAME: &'static str = "Session";
}
impl Tool for SessionTool {
fn name(&self) -> &str {
Self::NAME
}
fn description(&self) -> Cow<'_, str> {
r#"
Operate on an interactive process session's stdin/stdout. Use this tool when Bash/Powershell returns a sid (session ID) with interactive: true.
Actions:
- stdin: Write text to the process. Use \n for newlines. For example, to answer a prompt, send the text followed by \n.
- stdout: Read the process's output since your last read. Blocks up to timeout_ms (default 500) waiting for new output.
- quit: Terminate the session. Drops the PTY handle, the process receives SIGHUP and exits naturally.
Example workflow:
1. Bash with interactive: true → returns { "sid": "bg_5", ... }
2. Session action=stdin, sid=bg_5, text="hello\n" → sends "hello" + Enter
3. Session action=stdout, sid=bg_5 → reads process output
4. Session action=quit, sid=bg_5 → terminates the session
"#.into()
}
fn parameters_schema(&self) -> Value {
schema_to_tool_params::<SessionParams>()
}
fn execute(&self, arguments: &str, _cancelled: &Arc<AtomicBool>) -> ToolResult {
let params: SessionParams = match parse_tool_args(arguments) {
Ok(p) => p,
Err(e) => return e,
};
let result = match params.action.as_str() {
"stdin" => {
let text = match params.text {
Some(t) => t,
None => {
return ToolResult {
output: json!({ "ok": false, "error": "action=stdin requires 'text' parameter" }).to_string(),
is_error: true,
images: vec![],
plan_decision: PlanDecision::None,
};
}
};
let text = text.replace("\\n", "\n");
match self.manager.session_stdin(¶ms.sid, &text) {
Ok(()) => json!({ "ok": true }),
Err(e) => json!({ "ok": false, "error": e }),
}
}
"stdout" => {
let timeout_ms = params.timeout_ms.unwrap_or(500);
match self.manager.session_stdout(¶ms.sid, timeout_ms) {
Ok(output) => json!({ "ok": true, "output": output }),
Err(e) => json!({ "ok": false, "error": e }),
}
}
"quit" => match self.manager.session_quit(¶ms.sid) {
Ok(()) => json!({ "ok": true }),
Err(e) => json!({ "ok": false, "error": e }),
},
other => {
json!({ "ok": false, "error": format!("unknown action: {}", other) })
}
};
ToolResult {
output: result.to_string(),
is_error: false,
images: vec![],
plan_decision: PlanDecision::None,
}
}
fn requires_confirmation(&self) -> bool {
false
}
fn confirmation_message(&self, _arguments: &str) -> String {
String::new()
}
}