Files
zesdex/src/tool/bash_tools.rs
T

103 lines
3.1 KiB
Rust
Raw Normal View History

//! Tool implementations for interacting with background bash jobs: `bash_output`
//! and `bash_kill`. Both take a `job_id` produced by `bash` with `run_in_background=true`.
use serde_json::{json, Value};
use anyhow::{Result, anyhow};
use super::Tool;
use super::ToolCtx;
/// Tool: fetch buffered output from a background bash job by `job_id`.
pub struct BashOutput;
impl Tool for BashOutput {
fn name(&self) -> &'static str {
"bash_output"
}
fn description(&self) -> &'static str {
"Retrieve output from a background bash job by job_id"
}
fn parameters(&self) -> Value {
json!({
"type": "object",
"properties": {
"job_id": {
"type": "string",
"description": "Job ID returned by bash with run_in_background=true"
}
},
"required": ["job_id"]
})
}
fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result<String> {
let job_id = args.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("missing required argument: job_id"))?
.to_string();
// Validate that job_id looks like a UUID to prevent injection
// into the global job registry.
if !is_valid_job_id(&job_id) {
anyhow::bail!("invalid job_id format: expected UUID");
}
match crate::app::bgbash::control::bash_output(&job_id) {
Some(lines) => Ok(lines.join("\n")),
None => Ok(format!("No new output from job '{job_id}'")),
}
}
}
/// Tool: terminate a running background bash job by `job_id`.
pub struct BashKill;
impl Tool for BashKill {
fn name(&self) -> &'static str {
"bash_kill"
}
fn description(&self) -> &'static str {
"Kill a background bash job by job_id"
}
fn parameters(&self) -> Value {
json!({
"type": "object",
"properties": {
"job_id": {
"type": "string",
"description": "Job ID returned by bash with run_in_background=true"
}
},
"required": ["job_id"]
})
}
fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result<String> {
let job_id = args.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("missing required argument: job_id"))?
.to_string();
if !is_valid_job_id(&job_id) {
anyhow::bail!("invalid job_id format: expected UUID");
}
crate::app::bgbash::control::bash_kill(&job_id)?;
Ok(format!("Killed background job '{job_id}'"))
}
}
/// Validate that a `job_id` matches UUID v4 format (hex with dashes).
fn is_valid_job_id(id: &str) -> bool {
// UUID v4 format: 8-4-4-4-12 hex digits
let parts: Vec<&str> = id.split('-').collect();
if parts.len() != 5 {
return false;
}
parts.iter().all(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_hexdigit()))
&& parts[0].len() == 8
&& parts[1].len() == 4
&& parts[2].len() == 4
&& parts[3].len() == 4
&& parts[4].len() == 12
}