use rho_sdk::{
tool::{Tool, ToolErrorKind, ToolInvocation, ToolPreparationContext},
CancellationToken, CapabilityKind, CapabilityOperation, CapabilityRequest, ToolCallId,
Workspace,
};
use serde_json::json;
use tempfile::TempDir;
use super::SaveAgentTool;
use crate::agent::{persist_destination_path, AgentSaveLocation};
use crate::workspace::ProjectTrust;
fn invocation(arguments: serde_json::Value) -> ToolInvocation {
ToolInvocation::new(ToolCallId::from_string("call-1").unwrap(), arguments)
}
#[tokio::test]
async fn prepare_rejects_invalid_definition() {
let root = TempDir::new().unwrap();
let tool = SaveAgentTool {
max_output_bytes: 12_000,
};
let result = tool
.prepare(
invocation(json!({
"location": "rho-home",
"contents": "---\nid: bad\n---\n"
})),
ToolPreparationContext::new(
Some(Workspace::new(root.path()).unwrap()),
CancellationToken::new(),
),
)
.await;
let Err(error) = result else {
panic!("expected invalid definition to fail prepare");
};
assert_eq!(error.kind(), ToolErrorKind::InvalidArguments);
}
#[tokio::test]
async fn prepare_rejects_missing_id() {
let root = TempDir::new().unwrap();
let tool = SaveAgentTool {
max_output_bytes: 12_000,
};
let result = tool
.prepare(
invocation(json!({
"location": "rho-home",
"contents": "---\ndescription: save_agent fixture\nprompt: extend\n---\nYou are a fixture.\n"
})),
ToolPreparationContext::new(
Some(Workspace::new(root.path()).unwrap()),
CancellationToken::new(),
),
)
.await;
let Err(error) = result else {
panic!("expected missing id to fail prepare");
};
assert_eq!(error.kind(), ToolErrorKind::InvalidArguments);
assert!(error.to_string().contains("id"), "{error}");
}
fn valid_draft(id: &str) -> String {
format!(
"---\nid: {id}\ndescription: save_agent fixture\nprompt: extend\n---\nYou are a fixture.\n"
)
}
#[tokio::test]
async fn prepare_requests_read_when_destination_is_missing() {
let root = TempDir::new().unwrap();
let id = "saveagentreadcap";
let dest = persist_destination_path(
AgentSaveLocation::RhoHome,
root.path(),
crate::paths::home_dir().as_deref(),
ProjectTrust::Untrusted,
id,
)
.unwrap();
let tool = SaveAgentTool {
max_output_bytes: 12_000,
};
let prepared = tool
.prepare(
invocation(json!({
"location": "rho-home",
"contents": valid_draft(id)
})),
ToolPreparationContext::new(
Some(Workspace::new(root.path()).unwrap()),
CancellationToken::new(),
),
)
.await
.unwrap();
let kinds = prepared
.capabilities()
.iter()
.map(CapabilityRequest::kind)
.collect::<Vec<_>>();
assert!(kinds.contains(&CapabilityKind::Write));
assert!(kinds.contains(&CapabilityKind::Read));
assert!(prepared.capabilities().iter().any(|request| {
matches!(
request.operation(),
CapabilityOperation::ReadPath { path, .. } if path == &dest
)
}));
}