pub mod bash;
pub mod read;
pub mod write;
pub mod edit;
pub mod load_skill;
pub mod ls;
pub mod find;
pub mod grep;
use async_trait::async_trait;
use robit_ai::ChatCompletionTools;
use serde_json::Value;
use std::collections::HashMap;
use std::any::Any;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::error::Result;
use crate::event::SessionId;
use crate::frontend::Frontend;
#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters_schema(&self) -> Value;
fn requires_confirmation(&self) -> bool;
async fn execute(&self, args: Value, ctx: &ToolContext) -> Result<ToolResult>;
}
#[derive(Debug, Clone)]
pub struct ToolResult {
pub content: String,
pub is_error: bool,
}
impl ToolResult {
pub fn success(content: impl Into<String>) -> Self {
Self {
content: content.into(),
is_error: false,
}
}
pub fn error(content: impl Into<String>) -> Self {
Self {
content: content.into(),
is_error: true,
}
}
}
pub fn resolve_path(file_path: &str, working_dir: &Path) -> PathBuf {
let p = PathBuf::from(file_path);
if p.is_absolute() {
p
} else {
working_dir.join(p)
}
}
pub struct ToolContext {
pub working_dir: PathBuf,
pub session_id: SessionId,
pub frontend: Arc<dyn Frontend>,
pub extensions: HashMap<String, Arc<dyn Any + Send + Sync>>,
}
#[derive(Debug, Clone)]
pub struct ToolCallInfo {
pub id: String,
pub name: String,
pub arguments: String,
}
pub struct ToolRegistry {
tools: HashMap<String, Box<dyn Tool>>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
}
}
pub fn register(&mut self, tool: impl Tool + 'static) {
self.tools.insert(tool.name().to_string(), Box::new(tool));
}
pub fn tool_names(&self) -> Vec<&str> {
self.tools.keys().map(|s| s.as_str()).collect()
}
pub fn contains(&self, name: &str) -> bool {
self.tools.contains_key(name)
}
pub fn tool_schemas(&self) -> Vec<ChatCompletionTools> {
self.tools
.values()
.map(|tool| {
let function = serde_json::json!({
"name": tool.name(),
"description": tool.description(),
"parameters": tool.parameters_schema(),
});
let tool_json = serde_json::json!({
"type": "function",
"function": function,
});
serde_json::from_value(tool_json)
.expect("tool schema should be valid ChatCompletionTools")
})
.collect()
}
pub async fn execute(
&self,
name: &str,
args: Value,
ctx: &ToolContext,
) -> ToolResult {
tracing::info!("ToolRegistry.execute called: name='{}', args={:?}", name, args);
tracing::debug!("Available tools: {:?}", self.tool_names());
match self.tools.get(name) {
Some(tool) => {
tracing::debug!("Found tool '{}', executing...", name);
match tool.execute(args, ctx).await {
Ok(result) => result,
Err(e) => ToolResult::error(format!("Tool execution error: {}", e)),
}
},
None => {
let available: Vec<&str> = self.tools.keys().map(|s| s.as_str()).collect();
tracing::error!("Tool '{}' not found! Available tools: {:?}", name, available);
ToolResult::error(format!(
"Tool '{}' not found. Available tools: {:?}",
name, available
))
}
}
}
pub fn requires_confirmation(&self, name: &str) -> bool {
self.tools
.get(name)
.map(|t| t.requires_confirmation())
.unwrap_or(false)
}
pub fn tools(&self) -> Vec<&dyn Tool> {
self.tools.values().map(|t| t.as_ref()).collect()
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}