rho-coding-agent 1.4.1

A lightweight agent harness inspired by Pi
Documentation
use std::{sync::Arc, time::Duration};

use rho_sdk::tool::{
    OperationKind, Tool, ToolContext, ToolError, ToolFuture, ToolInvocation, ToolMetadata,
    ToolOutput, ToolProgress, ToolSecurity,
};

pub(super) const NAME: &str = "tui_fixture_progress";

pub(super) fn from_env() -> Option<Arc<dyn Tool>> {
    (std::env::var_os("RHO_TUI_TEST_MODE").as_deref() == Some(std::ffi::OsStr::new("matrix")))
        .then(|| Arc::new(TuiFixtureProgressTool) as Arc<dyn Tool>)
}

struct TuiFixtureProgressTool;

impl Tool for TuiFixtureProgressTool {
    fn spec(&self) -> rho_sdk::model::ToolSpec {
        rho_sdk::model::ToolSpec {
            name: NAME.into(),
            description: "Deterministic progress fixture for source-build TUI smoke tests.".into(),
            input_schema: serde_json::json!({
                "type": "object",
                "additionalProperties": false,
                "properties": {},
            }),
        }
    }

    fn security(&self) -> ToolSecurity {
        ToolSecurity::built_in([])
    }

    fn call<'a>(&'a self, _invocation: ToolInvocation, context: ToolContext) -> ToolFuture<'a> {
        Box::pin(async move {
            send_progress(&context, "deterministic progress update one", 1).await?;
            fixture_sleep(&context, Duration::from_secs(3)).await?;
            send_progress(&context, "deterministic progress update two", 2).await?;
            fixture_sleep(&context, Duration::from_millis(300)).await?;
            Ok(
                ToolOutput::text("deterministic fixture tool result").metadata(
                    ToolMetadata::new().operation(OperationKind::Other("tui_fixture".into())),
                ),
            )
        })
    }
}

async fn send_progress(
    context: &ToolContext,
    message: &str,
    completed: u64,
) -> Result<(), ToolError> {
    if !context
        .progress()
        .send(
            ToolProgress::message(message).units(completed, 2).metadata(
                ToolMetadata::new().operation(OperationKind::Other("tui_fixture".into())),
            ),
        )
        .await
    {
        return Err(ToolError::cancelled());
    }
    Ok(())
}

async fn fixture_sleep(context: &ToolContext, duration: Duration) -> Result<(), ToolError> {
    tokio::select! {
        () = tokio::time::sleep(duration) => Ok(()),
        () = context.cancellation().cancelled() => Err(ToolError::cancelled()),
    }
}