use crate::tools::{PlanDecision, Tool, ToolResult, schema_to_tool_params};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::Value;
use std::borrow::Cow;
use std::sync::{Arc, Mutex, atomic::AtomicBool};
#[derive(Deserialize, JsonSchema)]
#[allow(dead_code)]
struct LoadToolParams {
name: String,
}
#[derive(Debug)]
pub struct LoadTool {
deferred_tools: Arc<Mutex<Vec<String>>>,
session_loaded_deferred: Arc<Mutex<Vec<String>>>,
}
impl LoadTool {
pub const NAME: &'static str = "LoadTool";
pub const STATIC_DESCRIPTION: &'static str = "Load a deferred tool so it becomes available in subsequent turns. \
Use this when you need a tool that is not currently available in your tool list. \
The tool name must match exactly. After loading, the tool will be available in the next turn.";
pub fn new(
deferred_tools: Arc<Mutex<Vec<String>>>,
session_loaded_deferred: Arc<Mutex<Vec<String>>>,
) -> Self {
Self {
deferred_tools,
session_loaded_deferred,
}
}
}
impl Tool for LoadTool {
fn name(&self) -> &str {
Self::NAME
}
fn description(&self) -> Cow<'_, str> {
Cow::Borrowed(Self::STATIC_DESCRIPTION)
}
fn parameters_schema(&self) -> Value {
schema_to_tool_params::<LoadToolParams>()
}
fn execute(&self, arguments: &str, _cancelled: &Arc<AtomicBool>) -> ToolResult {
let params: LoadToolParams = match serde_json::from_str(arguments) {
Ok(p) => p,
Err(e) => {
return ToolResult {
output: format!("Failed to parse arguments: {e}"),
is_error: true,
images: vec![],
plan_decision: PlanDecision::None,
};
}
};
let tool_name = params.name.trim().to_string();
if tool_name.is_empty() {
return ToolResult {
output: "Tool name cannot be empty.".to_string(),
is_error: true,
images: vec![],
plan_decision: PlanDecision::None,
};
}
let mut deferred = match self.deferred_tools.lock() {
Ok(guard) => guard,
Err(e) => {
e.into_inner()
}
};
let idx = deferred.iter().position(|n| n == &tool_name);
match idx {
Some(i) => {
deferred.remove(i);
if let Ok(mut loaded) = self.session_loaded_deferred.lock()
&& !loaded.iter().any(|n| n == &tool_name)
{
loaded.push(tool_name.clone());
}
ToolResult {
output: format!(
"Tool '{}' has been loaded successfully. It will be available in the next turn.",
tool_name
),
is_error: false,
images: vec![],
plan_decision: PlanDecision::None,
}
}
None => {
ToolResult {
output: format!(
"Tool '{}' is not in the deferred list. It may already be loaded or does not exist.",
tool_name
),
is_error: true,
images: vec![],
plan_decision: PlanDecision::None,
}
}
}
}
fn requires_confirmation(&self) -> bool {
false
}
}