use molo::{
BroadcastChannel, CliMessageChannel, MessageChannel, MpscChannel, SharedState, Tool, ToolError,
ToolSchema, WatchChannel,
};
use schemars::JsonSchema;
use serde::Deserialize;
use std::sync::Arc;
#[derive(Debug, Deserialize, JsonSchema)]
struct ConfirmArgs {
#[schemars(description = "Description of the operation to confirm, e.g. \"delete file x\"")]
operation: String,
}
struct ConfirmTool {
channel: Arc<dyn MessageChannel>,
}
#[async_trait::async_trait]
impl Tool for ConfirmTool {
fn schema(&self) -> ToolSchema {
ToolSchema {
name: "confirm".into(),
description: "Confirms with the user before executing a dangerous operation; only continue if the user replies yes.".into(),
parameters: serde_json::to_value(schemars::schema_for!(ConfirmArgs))
.expect("tool schema must serialize"),
}
}
async fn call(
&self,
arguments: serde_json::Value,
_state: &SharedState,
) -> Result<String, ToolError> {
let args: ConfirmArgs = serde_json::from_value(arguments)?;
let answer = self
.channel
.ask(&format!(
"Confirm executing the operation \"{}\"? Reply yes or no",
args.operation
))
.await
.map_err(|e| ToolError::Execution(e.to_string()))?;
if answer == "yes" {
Ok("Confirmed, continue executing.".into())
} else {
Ok("The user declined; the operation was cancelled.".into())
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let channel: Arc<dyn MessageChannel> = Arc::new(CliMessageChannel::new());
channel.notify("Starting the task...").await?;
let answer = channel
.ask("Execute a dangerous operation? Reply yes or no")
.await?;
println!("you replied: {answer}");
let confirm = ConfirmTool {
channel: channel.clone(),
};
let result = confirm
.call(
serde_json::json!({ "operation": "delete the entire project directory" }),
&SharedState::new(),
)
.await?;
println!("tool result: {result}");
let (agent_a, agent_b) = MpscChannel::pair();
let ask = agent_a.ask("help me calculate 1+2");
let b_side = async {
let incoming = agent_b.recv().await?;
println!("agent B received the question: {}", incoming.text());
incoming.reply("3".into())
};
let (answer, b_result) = tokio::join!(ask, b_side);
b_result?;
println!("agent A received the reply: {}", answer?);
let broadcast = BroadcastChannel::new(16);
let worker_a = broadcast.subscribe();
let worker_b = broadcast.subscribe();
broadcast
.notify("all workers, the task is starting")
.await?;
let (msg_a, msg_b) = tokio::join!(worker_a.recv(), worker_b.recv());
println!("worker A received: {}", msg_a?.text());
println!("worker B received: {}", msg_b?.text());
let watch = WatchChannel::new();
let observer = watch.subscribe();
watch.notify("status: running").await?;
println!("observer received: {}", observer.recv().await?.text());
Ok(())
}