use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use crate::client::models::Tool as LLMTool;
use crate::controller::types::TurnId;
use crate::permissions::PermissionRequest;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolType {
ApiCall,
BashCmd,
WebSearch,
TextEdit,
Custom,
FileRead,
UserInteraction,
}
#[derive(Debug, Clone)]
pub struct ToolContext {
pub session_id: i64,
pub tool_use_id: String,
pub turn_id: Option<TurnId>,
#[doc(hidden)]
pub permissions_pre_approved: bool,
}
impl ToolContext {
pub fn new(session_id: i64, tool_use_id: impl Into<String>, turn_id: Option<TurnId>) -> Self {
Self {
session_id,
tool_use_id: tool_use_id.into(),
turn_id,
permissions_pre_approved: false,
}
}
pub fn with_pre_approved_permissions(
session_id: i64,
tool_use_id: impl Into<String>,
turn_id: Option<TurnId>,
) -> Self {
Self {
session_id,
tool_use_id: tool_use_id.into(),
turn_id,
permissions_pre_approved: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolResultStatus {
Success,
Error,
Timeout,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ResultContentType {
Json,
Markdown,
Yaml,
#[default]
PlainText,
Xml,
Auto,
}
#[derive(Debug, Clone)]
pub struct DisplayResult {
pub content: String,
pub content_type: ResultContentType,
pub is_truncated: bool,
pub full_length: usize,
}
pub type DisplayTitleFn = Box<dyn Fn(&HashMap<String, serde_json::Value>) -> String + Send + Sync>;
pub type DisplayContentFn =
Box<dyn Fn(&HashMap<String, serde_json::Value>, &str) -> DisplayResult + Send + Sync>;
pub struct DisplayConfig {
pub display_name: String,
pub display_title: DisplayTitleFn,
pub display_content: DisplayContentFn,
}
impl DisplayConfig {
pub fn default_for(name: &str) -> Self {
let name_owned = name.to_string();
Self {
display_name: name_owned,
display_title: Box::new(|_| String::new()),
display_content: Box::new(|_, result| DisplayResult {
content: result.to_string(),
content_type: ResultContentType::PlainText,
is_truncated: false,
full_length: result.lines().count(),
}),
}
}
}
impl std::fmt::Display for ToolResultStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ToolResultStatus::Success => write!(f, "success"),
ToolResultStatus::Error => write!(f, "error"),
ToolResultStatus::Timeout => write!(f, "timeout"),
}
}
}
#[derive(Debug, Clone)]
pub struct ToolResult {
pub session_id: i64,
pub tool_name: String,
pub display_name: Option<String>,
pub tool_use_id: String,
pub input: HashMap<String, serde_json::Value>,
pub content: String,
pub status: ToolResultStatus,
pub error: Option<String>,
pub turn_id: Option<TurnId>,
pub compact_summary: Option<String>,
}
impl ToolResult {
#[allow(clippy::too_many_arguments)]
pub fn success(
session_id: i64,
tool_name: String,
display_name: Option<String>,
tool_use_id: String,
input: HashMap<String, serde_json::Value>,
content: String,
turn_id: Option<TurnId>,
compact_summary: Option<String>,
) -> Self {
Self {
session_id,
tool_name,
display_name,
tool_use_id,
input,
content,
status: ToolResultStatus::Success,
error: None,
turn_id,
compact_summary,
}
}
pub fn error(
session_id: i64,
tool_name: String,
tool_use_id: String,
input: HashMap<String, serde_json::Value>,
error: String,
turn_id: Option<TurnId>,
) -> Self {
let summary = format!("[{}: error]", tool_name);
Self {
session_id,
tool_name,
display_name: None,
tool_use_id,
input,
content: String::new(),
status: ToolResultStatus::Error,
error: Some(error),
turn_id,
compact_summary: Some(summary),
}
}
pub fn timeout(
session_id: i64,
tool_name: String,
tool_use_id: String,
input: HashMap<String, serde_json::Value>,
turn_id: Option<TurnId>,
) -> Self {
let summary = format!("[{}: timeout]", tool_name);
Self {
session_id,
tool_name,
display_name: None,
tool_use_id,
input,
content: String::new(),
status: ToolResultStatus::Timeout,
error: Some("Tool execution timed out".to_string()),
turn_id,
compact_summary: Some(summary),
}
}
}
#[derive(Debug, Clone)]
pub struct ToolBatchResult {
pub batch_id: i64,
pub session_id: i64,
pub turn_id: Option<TurnId>,
pub results: Vec<ToolResult>,
}
#[derive(Debug, Clone)]
pub struct ToolRequest {
pub tool_use_id: String,
pub tool_name: String,
pub input: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub input_schema: String,
}
pub trait Executable: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn input_schema(&self) -> &str;
fn tool_type(&self) -> ToolType;
fn execute(
&self,
context: ToolContext,
input: HashMap<String, serde_json::Value>,
) -> Pin<Box<dyn Future<Output = Result<String, String>> + Send>>;
fn to_definition(&self) -> ToolDefinition {
ToolDefinition {
name: self.name().to_string(),
description: self.description().to_string(),
input_schema: self.input_schema().to_string(),
}
}
fn to_llm_tool(&self) -> LLMTool {
LLMTool::new(self.name(), self.description(), self.input_schema())
}
fn display_config(&self) -> DisplayConfig {
DisplayConfig::default_for(self.name())
}
fn compact_summary(
&self,
_input: &HashMap<String, serde_json::Value>,
_result: &str,
) -> String {
format!("[{}: completed]", self.name())
}
fn required_permissions(
&self,
_context: &ToolContext,
_input: &HashMap<String, serde_json::Value>,
) -> Option<Vec<PermissionRequest>> {
None }
fn handles_own_permissions(&self) -> bool {
false
}
fn cleanup_session(&self, _session_id: i64) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async {})
}
}