use open_agent::{AgentOptions, Client, HookDecision, Hooks, tool};
use serde_json::json;
use std::sync::{Arc, Mutex};
#[tokio::test]
async fn test_pre_tool_use_blocks_tool_in_auto_mode() {
let dangerous_tool = tool("delete_file", "Delete a file")
.param("path", "string")
.build(|_args| async move {
panic!("Should not execute - hook should block this!");
});
let hooks = Hooks::new().add_pre_tool_use(|event| async move {
if event.tool_name == "delete_file" {
return Some(HookDecision::block("Dangerous operation blocked"));
}
None
});
let options = AgentOptions::builder()
.system_prompt("Test")
.model("qwen3:8b")
.base_url("http://localhost:11434/v1")
.tool(dangerous_tool)
.hooks(hooks)
.auto_execute_tools(true)
.build()
.unwrap();
let _client = Client::new(options).expect("Failed to create client");
}
#[tokio::test]
async fn test_pre_tool_use_modifies_input_in_auto_mode() {
use std::sync::Arc;
use std::sync::Mutex;
let executed_input = Arc::new(Mutex::new(None));
let executed_input_clone = Arc::clone(&executed_input);
let calculation_tool = tool("calculate", "Perform calculation")
.param("value", "number")
.build(move |args| {
let executed_input = Arc::clone(&executed_input_clone);
async move {
let value = args["value"].as_f64().unwrap_or(0.0);
*executed_input.lock().unwrap() = Some(value);
Ok(json!({"result": value * 2.0}))
}
});
let hooks = Hooks::new().add_pre_tool_use(|event| async move {
if event.tool_name == "calculate" {
if let Some(value) = event.tool_input.get("value").and_then(|v| v.as_f64()) {
if value > 100.0 {
let modified = json!({"value": 100.0});
return Some(HookDecision::modify_input(
modified,
"Clamped value to max 100",
));
}
}
}
None
});
let options = AgentOptions::builder()
.system_prompt("Test")
.model("qwen3:8b")
.base_url("http://localhost:11434/v1")
.tool(calculation_tool)
.hooks(hooks)
.auto_execute_tools(true)
.build()
.unwrap();
let _client = Client::new(options).expect("Failed to create client");
}
#[tokio::test]
async fn test_post_tool_use_modifies_result_in_auto_mode() {
let data_tool = tool("get_data", "Get data")
.param("id", "string")
.build(|_args| async move { Ok(json!({"data": "sensitive_information"})) });
let hooks = Hooks::new().add_post_tool_use(|event| async move {
if event.tool_name == "get_data" {
if let Some(data) = event.tool_result.get("data") {
if data.as_str() == Some("sensitive_information") {
let redacted = json!({"data": "[REDACTED]"});
return Some(HookDecision::modify_input(
redacted,
"Redacted sensitive data",
));
}
}
}
None
});
let options = AgentOptions::builder()
.system_prompt("Test")
.model("qwen3:8b")
.base_url("http://localhost:11434/v1")
.tool(data_tool)
.hooks(hooks)
.auto_execute_tools(true)
.build()
.unwrap();
let _client = Client::new(options).expect("Failed to create client");
}
#[tokio::test]
async fn test_post_tool_use_logs_execution_in_auto_mode() {
let log = Arc::new(Mutex::new(Vec::new()));
let log_clone = Arc::clone(&log);
let tool_a = tool("op_a", "Operation A")
.param("x", "number")
.build(
|args| async move { Ok(json!({"result": args["x"].as_f64().unwrap_or(0.0) * 2.0})) },
);
let tool_b = tool("op_b", "Operation B")
.param("y", "number")
.build(
|args| async move { Ok(json!({"result": args["y"].as_f64().unwrap_or(0.0) + 10.0})) },
);
let hooks = Hooks::new().add_post_tool_use(move |event| {
let log = Arc::clone(&log_clone);
async move {
log.lock()
.unwrap()
.push(format!("{}: {:?}", event.tool_name, event.tool_result));
None }
});
let options = AgentOptions::builder()
.system_prompt("Test")
.model("qwen3:8b")
.base_url("http://localhost:11434/v1")
.tool(tool_a)
.tool(tool_b)
.hooks(hooks)
.auto_execute_tools(true)
.build()
.unwrap();
let _client = Client::new(options).expect("Failed to create client");
}
#[tokio::test]
async fn test_multiple_pre_tool_use_hooks() {
let test_tool = tool("test_op", "Test operation")
.param("value", "number")
.build(|_args| async move { Ok(json!({"result": 42})) });
let hooks = Hooks::new()
.add_pre_tool_use(|event| async move {
if let Some(value) = event.tool_input.get("value").and_then(|v| v.as_f64()) {
if value > 1000.0 {
return Some(HookDecision::block("Value too large"));
}
}
None
})
.add_pre_tool_use(|event| async move {
if let Some(value) = event.tool_input.get("value").and_then(|v| v.as_f64()) {
if value > 100.0 {
return Some(HookDecision::modify_input(
json!({"value": 100.0}),
"Clamped to 100",
));
}
}
None
});
let options = AgentOptions::builder()
.system_prompt("Test")
.model("qwen3:8b")
.base_url("http://localhost:11434/v1")
.tool(test_tool)
.hooks(hooks)
.auto_execute_tools(true)
.build()
.unwrap();
let _client = Client::new(options).expect("Failed to create client");
}
#[tokio::test]
async fn test_hooks_dont_break_manual_mode() {
let manual_tool = tool("manual_op", "Manual operation")
.param("x", "number")
.build(
|args| async move { Ok(json!({"result": args["x"].as_f64().unwrap_or(0.0) * 2.0})) },
);
let hooks = Hooks::new()
.add_pre_tool_use(|_event| async move {
Some(HookDecision::block("Should not see this"))
})
.add_post_tool_use(|_event| async move {
None
});
let options = AgentOptions::builder()
.system_prompt("Test")
.model("qwen3:8b")
.base_url("http://localhost:11434/v1")
.tool(manual_tool)
.hooks(hooks)
.auto_execute_tools(false) .build()
.unwrap();
let _client = Client::new(options).expect("Failed to create client");
}