rho-coding-agent 0.24.1

A lightweight agent harness inspired by Pi
Documentation
use super::*;
use crate::tool::*;
use serde::Deserialize;
use serde_json::json;
use std::time::Duration;

macro_rules! result {
    ($id:expr,$value:expr) => {
        Ok(ToolResult {
            id: $id,
            ok: true,
            content: serde_json::to_string(&$value).unwrap(),
        })
    };
}
#[derive(Clone)]
pub struct StartProcess(ProcessManager);
impl StartProcess {
    pub fn new(m: ProcessManager) -> Self {
        Self(m)
    }
}
#[derive(Deserialize)]
struct StartArgs {
    command: String,
    timeout_seconds: Option<u64>,
}
#[async_trait::async_trait]
impl Tool for StartProcess {
    fn spec(&self) -> ToolSpec {
        ToolSpec {
            name: "start_process".into(),
            description: "Start a managed background shell process.".into(),
            input_schema: json!({"type":"object","properties":{"command":{"type":"string"},"timeout_seconds":{"type":"integer","minimum":1}},"required":["command"]}),
        }
    }
    fn display_command(&self, a: &serde_json::Value) -> Option<String> {
        a["command"].as_str().map(str::to_owned)
    }
    async fn call(
        &self,
        a: serde_json::Value,
        c: ToolContext,
        id: String,
    ) -> Result<ToolResult, ToolError> {
        let a: StartArgs = serde_json::from_value(a)?;
        let s = self
            .0
            .start(
                a.command,
                &c.cwd,
                a.timeout_seconds.map(Duration::from_secs),
            )
            .await
            .map_err(ToolError::Message)?;
        result!(id, s)
    }
}
#[derive(Clone)]
pub struct PollProcess(ProcessManager);
impl PollProcess {
    pub fn new(m: ProcessManager) -> Self {
        Self(m)
    }
}
#[derive(Deserialize)]
struct PollArgs {
    process_id: String,
    cursor: Option<u64>,
    #[serde(default)]
    wait_seconds: u64,
}
#[async_trait::async_trait]
impl Tool for PollProcess {
    fn spec(&self) -> ToolSpec {
        ToolSpec {
            name: "poll_process".into(),
            description: "Read retained process output, optionally waiting for changes.".into(),
            input_schema: json!({"type":"object","properties":{"process_id":{"type":"string"},"cursor":{"type":"integer","minimum":0},"wait_seconds":{"type":"integer","minimum":0,"maximum":30}},"required":["process_id"]}),
        }
    }
    async fn call(
        &self,
        a: serde_json::Value,
        context: ToolContext,
        id: String,
    ) -> Result<ToolResult, ToolError> {
        let a: PollArgs = serde_json::from_value(a)?;
        if a.wait_seconds > 30 {
            return Err(ToolError::Message(
                "wait_seconds must be between 0 and 30".into(),
            ));
        }
        let s = self
            .0
            .poll_bounded(
                &a.process_id,
                a.cursor,
                Duration::from_secs(a.wait_seconds),
                context.max_output_bytes,
            )
            .await
            .map_err(ToolError::Message)?;
        result!(id, s)
    }
}
#[derive(Clone)]
pub struct WriteProcess(ProcessManager);
impl WriteProcess {
    pub fn new(m: ProcessManager) -> Self {
        Self(m)
    }
}
#[derive(Deserialize)]
struct WriteArgs {
    process_id: String,
    #[serde(default)]
    text: String,
    #[serde(default)]
    close_stdin: bool,
}
#[async_trait::async_trait]
impl Tool for WriteProcess {
    fn spec(&self) -> ToolSpec {
        ToolSpec {
            name: "write_process".into(),
            description: "Write text to a managed process or close its stdin.".into(),
            input_schema: json!({"type":"object","properties":{"process_id":{"type":"string"},"text":{"type":"string"},"close_stdin":{"type":"boolean"}},"required":["process_id"]}),
        }
    }
    async fn call(
        &self,
        a: serde_json::Value,
        _: ToolContext,
        id: String,
    ) -> Result<ToolResult, ToolError> {
        let a: WriteArgs = serde_json::from_value(a)?;
        self.0
            .write(&a.process_id, &a.text, a.close_stdin)
            .await
            .map_err(ToolError::Message)?;
        result!(
            id,
            json!({"process_id":a.process_id,"written":a.text.len(),"stdin_closed":a.close_stdin})
        )
    }
}
#[derive(Clone)]
pub struct StopProcess(ProcessManager);
impl StopProcess {
    pub fn new(m: ProcessManager) -> Self {
        Self(m)
    }
}
#[derive(Deserialize)]
struct StopArgs {
    process_id: String,
    grace_seconds: Option<u64>,
}
#[async_trait::async_trait]
impl Tool for StopProcess {
    fn spec(&self) -> ToolSpec {
        ToolSpec {
            name: "stop_process".into(),
            description: "Stop a managed process tree gracefully, then forcibly.".into(),
            input_schema: json!({"type":"object","properties":{"process_id":{"type":"string"},"grace_seconds":{"type":"integer","minimum":0}},"required":["process_id"]}),
        }
    }
    async fn call(
        &self,
        a: serde_json::Value,
        _: ToolContext,
        id: String,
    ) -> Result<ToolResult, ToolError> {
        let a: StopArgs = serde_json::from_value(a)?;
        self.0
            .stop(
                &a.process_id,
                Duration::from_secs(a.grace_seconds.unwrap_or(2)),
            )
            .await
            .map_err(ToolError::Message)?;
        result!(id, json!({"process_id":a.process_id,"stop_requested":true}))
    }
}
#[derive(Clone)]
pub struct ListProcesses(ProcessManager);
impl ListProcesses {
    pub fn new(m: ProcessManager) -> Self {
        Self(m)
    }
}
#[async_trait::async_trait]
impl Tool for ListProcesses {
    fn spec(&self) -> ToolSpec {
        ToolSpec {
            name: "list_processes".into(),
            description: "List processes owned by this Rho instance.".into(),
            input_schema: json!({"type":"object","properties":{}}),
        }
    }
    async fn call(
        &self,
        _: serde_json::Value,
        _: ToolContext,
        id: String,
    ) -> Result<ToolResult, ToolError> {
        result!(id, self.0.list())
    }
}