use crate::types::*;
use std::future::Future;
pub use crate::types::{ToolDefinition, ToolInputSchema};
pub type ToolFuture =
std::pin::Pin<Box<dyn Future<Output = Result<ToolResult, crate::error::AgentError>> + Send>>;
pub trait Tool {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn input_schema(&self) -> ToolInputSchema;
fn execute(
&self,
input: serde_json::Value,
context: &ToolContext,
) -> impl Future<Output = Result<ToolResult, crate::error::AgentError>> + Send;
}
fn bash_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"command": {
"type": "string",
"description": "The shell command to execute"
}
}),
required: Some(vec!["command".to_string()]),
}
}
fn file_read_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"path": {
"type": "string",
"description": "The file path to read"
}
}),
required: Some(vec!["path".to_string()]),
}
}
fn file_write_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"path": {
"type": "string",
"description": "The file path to write to"
},
"content": {
"type": "string",
"description": "The content to write"
}
}),
required: Some(vec!["path".to_string(), "content".to_string()]),
}
}
fn glob_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"pattern": {
"type": "string",
"description": "The glob pattern to match"
}
}),
required: Some(vec!["pattern".to_string()]),
}
}
fn grep_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"pattern": {
"type": "string",
"description": "The regex pattern to search for"
},
"path": {
"type": "string",
"description": "The file or directory to search in"
}
}),
required: Some(vec!["pattern".to_string()]),
}
}
fn file_edit_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"file_path": {
"type": "string",
"description": "The absolute path to the file to modify"
},
"old_string": {
"type": "string",
"description": "The exact text to find and replace"
},
"new_string": {
"type": "string",
"description": "The replacement text"
},
"replace_all": {
"type": "boolean",
"description": "Replace all occurrences (default false)"
}
}),
required: Some(vec![
"file_path".to_string(),
"old_string".to_string(),
"new_string".to_string(),
]),
}
}
fn notebook_edit_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"file_path": {
"type": "string",
"description": "Path to the .ipynb file"
},
"command": {
"type": "string",
"enum": ["insert", "replace", "delete"],
"description": "The edit operation to perform"
},
"cell_number": {
"type": "number",
"description": "Cell index (0-based) to operate on"
},
"cell_type": {
"type": "string",
"enum": ["code", "markdown"],
"description": "Type of cell (for insert/replace)"
},
"source": {
"type": "string",
"description": "Cell content (for insert/replace)"
}
}),
required: Some(vec![
"file_path".to_string(),
"command".to_string(),
"cell_number".to_string(),
]),
}
}
fn web_fetch_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"url": {
"type": "string",
"description": "The URL to fetch content from"
},
"headers": {
"type": "object",
"description": "Optional HTTP headers",
"additionalProperties": {
"type": "string"
}
}
}),
required: Some(vec!["url".to_string()]),
}
}
fn web_search_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"query": {
"type": "string",
"description": "The search query"
},
"num_results": {
"type": "number",
"description": "Number of results to return (default: 5)"
}
}),
required: Some(vec!["query".to_string()]),
}
}
const ALL_TOOLS: &[(&str, &str, fn() -> ToolDefinition)] = &[
("Bash", "Execute shell commands", || ToolDefinition {
name: "Bash".to_string(),
description: "Execute shell commands".to_string(),
input_schema: bash_schema(),
annotations: None,
}),
("FileRead", "Read files from filesystem", || {
ToolDefinition {
name: "FileRead".to_string(),
description: "Read files from filesystem".to_string(),
input_schema: file_read_schema(),
annotations: None,
}
}),
("FileWrite", "Write content to files", || ToolDefinition {
name: "FileWrite".to_string(),
description: "Write content to files".to_string(),
input_schema: file_write_schema(),
annotations: None,
}),
("Glob", "Find files by pattern", || ToolDefinition {
name: "Glob".to_string(),
description: "Find files by pattern".to_string(),
input_schema: glob_schema(),
annotations: None,
}),
("Grep", "Search file contents", || ToolDefinition {
name: "Grep".to_string(),
description: "Search file contents".to_string(),
input_schema: grep_schema(),
annotations: None,
}),
(
"FileEdit",
"Edit files by performing exact string replacements",
|| ToolDefinition {
name: "FileEdit".to_string(),
description: "Edit files by performing exact string replacements".to_string(),
input_schema: file_edit_schema(),
annotations: None,
},
),
(
"NotebookEdit",
"Edit Jupyter notebook (.ipynb) cells",
|| ToolDefinition {
name: "NotebookEdit".to_string(),
description: "Edit Jupyter notebook (.ipynb) cells".to_string(),
input_schema: notebook_edit_schema(),
annotations: None,
},
),
(
"WebFetch",
"Fetch content from a URL and return it as text",
|| {
ToolDefinition {
name: "WebFetch".to_string(),
description: "Fetch content from a URL and return it as text. Supports HTML pages, JSON APIs, and plain text. Strips HTML tags for readability.".to_string(),
input_schema: web_fetch_schema(),
annotations: None,
}
},
),
("WebSearch", "Search the web for information", || {
ToolDefinition {
name: "WebSearch".to_string(),
description: "Search the web for information. Returns search results with titles, URLs, and snippets.".to_string(),
input_schema: web_search_schema(),
annotations: None,
}
}),
(
"Agent",
"Launch a new agent to handle complex multi-step tasks",
|| {
ToolDefinition {
name: "Agent".to_string(),
description: "Launch a new agent to handle complex, multi-step tasks autonomously. Use this tool to spawn specialized subagents.".to_string(),
input_schema: agent_schema(),
annotations: None,
}
},
),
("TaskCreate", "Create a new task in the task list", || {
ToolDefinition {
name: "TaskCreate".to_string(),
description: "Create a new task in the task list".to_string(),
input_schema: task_create_schema(),
annotations: None,
}
}),
("TaskList", "List all tasks in the task list", || {
ToolDefinition {
name: "TaskList".to_string(),
description: "List all tasks in the task list".to_string(),
input_schema: task_list_schema(),
annotations: None,
}
}),
("TaskUpdate", "Update an existing task", || ToolDefinition {
name: "TaskUpdate".to_string(),
description: "Update an existing task's status or details".to_string(),
input_schema: task_update_schema(),
annotations: None,
}),
("TaskGet", "Get details of a specific task", || {
ToolDefinition {
name: "TaskGet".to_string(),
description: "Get details of a specific task by ID".to_string(),
input_schema: task_get_schema(),
annotations: None,
}
}),
(
"TeamCreate",
"Create a team of agents for parallel work",
|| ToolDefinition {
name: "TeamCreate".to_string(),
description: "Create a team of agents that can work in parallel".to_string(),
input_schema: team_create_schema(),
annotations: None,
},
),
("TeamDelete", "Delete a team of agents", || ToolDefinition {
name: "TeamDelete".to_string(),
description: "Delete a previously created team".to_string(),
input_schema: team_delete_schema(),
annotations: None,
}),
("SendMessage", "Send a message to another agent", || {
ToolDefinition {
name: "SendMessage".to_string(),
description: "Send a message to another agent".to_string(),
input_schema: send_message_schema(),
annotations: None,
}
}),
("EnterWorktree", "Create and enter a git worktree", || {
ToolDefinition {
name: "EnterWorktree".to_string(),
description: "Create and enter a git worktree for isolated work".to_string(),
input_schema: enter_worktree_schema(),
annotations: None,
}
}),
(
"ExitWorktree",
"Exit a worktree and return to original directory",
|| ToolDefinition {
name: "ExitWorktree".to_string(),
description: "Exit a worktree and return to the original working directory".to_string(),
input_schema: exit_worktree_schema(),
annotations: None,
},
),
("EnterPlanMode", "Enter structured planning mode", || {
ToolDefinition {
name: "EnterPlanMode".to_string(),
description: "Enter structured planning mode to explore and design implementation"
.to_string(),
input_schema: enter_plan_mode_schema(),
annotations: None,
}
}),
("ExitPlanMode", "Exit planning mode", || ToolDefinition {
name: "ExitPlanMode".to_string(),
description: "Exit planning mode and present the plan for approval".to_string(),
input_schema: exit_plan_mode_schema(),
annotations: None,
}),
(
"AskUserQuestion",
"Ask the user a question with multiple choice options",
|| ToolDefinition {
name: "AskUserQuestion".to_string(),
description: "Ask the user a question with multiple choice options".to_string(),
input_schema: ask_user_question_schema(),
annotations: None,
},
),
("ToolSearch", "Search for available tools", || {
ToolDefinition {
name: "ToolSearch".to_string(),
description: "Search for available tools by name or description".to_string(),
input_schema: tool_search_schema(),
annotations: None,
}
}),
("CronCreate", "Create a scheduled task", || ToolDefinition {
name: "CronCreate".to_string(),
description: "Create a scheduled task that runs on a cron schedule".to_string(),
input_schema: cron_create_schema(),
annotations: None,
}),
("CronDelete", "Delete a scheduled task", || ToolDefinition {
name: "CronDelete".to_string(),
description: "Delete a previously created scheduled task".to_string(),
input_schema: cron_delete_schema(),
annotations: None,
}),
("CronList", "List all scheduled tasks", || ToolDefinition {
name: "CronList".to_string(),
description: "List all scheduled tasks".to_string(),
input_schema: cron_list_schema(),
annotations: None,
}),
("Config", "Read or update configuration", || {
ToolDefinition {
name: "Config".to_string(),
description: "Read or update dynamic configuration".to_string(),
input_schema: config_schema(),
annotations: None,
}
}),
("TodoWrite", "Write todo list items", || ToolDefinition {
name: "TodoWrite".to_string(),
description: "Write todo list items for the session".to_string(),
input_schema: todo_write_schema(),
annotations: None,
}),
("Skill", "Invoke a skill by name", || ToolDefinition {
name: "Skill".to_string(),
description: "Invoke a skill by name to execute its commands".to_string(),
input_schema: skill_schema(),
annotations: None,
}),
];
fn agent_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"description": {
"type": "string",
"description": "A short description (3-5 words) summarizing what the agent will do"
},
"subagent_type": {
"type": "string",
"description": "The type of subagent to use. If omitted, uses the general-purpose agent."
},
"prompt": {
"type": "string",
"description": "The task prompt for the subagent to execute"
},
"model": {
"type": "string",
"description": "Optional model override for this subagent"
},
"max_turns": {
"type": "number",
"description": "Maximum number of turns for this subagent (default: 10)"
},
"run_in_background": {
"type": "boolean",
"description": "Whether to run the agent in the background (default: false)"
},
"isolation": {
"type": "string",
"enum": ["worktree", "remote"],
"description": "Isolation mode: 'worktree' for git worktree, 'remote' for remote CCR"
}
}),
required: Some(vec!["description".to_string(), "prompt".to_string()]),
}
}
fn task_create_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"subject": { "type": "string", "description": "A brief title for the task" },
"description": { "type": "string", "description": "What needs to be done" },
"activeForm": { "type": "string", "description": "Spinner text when in_progress" }
}),
required: Some(vec!["subject".to_string(), "description".to_string()]),
}
}
fn task_list_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({}),
required: None,
}
}
fn task_update_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"taskId": { "type": "string", "description": "The ID of the task to update" },
"subject": { "type": "string", "description": "New subject for the task" },
"description": { "type": "string", "description": "New description" },
"status": { "type": "string", "enum": ["pending", "in_progress", "completed", "deleted"], "description": "New status" },
"activeForm": { "type": "string", "description": "New spinner text" }
}),
required: Some(vec!["taskId".to_string()]),
}
}
fn task_get_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"taskId": { "type": "string", "description": "The ID of the task to retrieve" }
}),
required: Some(vec!["taskId".to_string()]),
}
}
fn team_create_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"name": { "type": "string", "description": "Name of the team" },
"description": { "type": "string", "description": "Description of what the team does" },
"agents": { "type": "array", "items": serde_json::json!({}), "description": "List of agents in the team" }
}),
required: Some(vec!["name".to_string()]),
}
}
fn team_delete_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"name": { "type": "string", "description": "Name of the team to delete" }
}),
required: Some(vec!["name".to_string()]),
}
}
fn send_message_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"to": { "type": "string", "description": "Agent name to send message to" },
"message": { "type": "string", "description": "Message content" }
}),
required: Some(vec!["to".to_string(), "message".to_string()]),
}
}
fn enter_worktree_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"name": { "type": "string", "description": "Optional name for the worktree" }
}),
required: None,
}
}
fn exit_worktree_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"action": { "type": "string", "enum": ["keep", "remove"], "description": "What to do with the worktree" },
"discardChanges": { "type": "boolean", "description": "Discard uncommitted changes before removing" }
}),
required: None,
}
}
fn enter_plan_mode_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"allowedPrompts": { "type": "array", "items": { "type": "string" }, "description": "Prompt-based permissions" }
}),
required: None,
}
}
fn exit_plan_mode_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({}),
required: None,
}
}
fn ask_user_question_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"question": { "type": "string", "description": "The question to ask the user" },
"header": { "type": "string", "description": "Short label displayed as a chip/tag" },
"options": { "type": "array", "items": serde_json::json!({}), "description": "Available choices" },
"multiSelect": { "type": "boolean", "description": "Allow multiple answers" }
}),
required: Some(vec![
"question".to_string(),
"header".to_string(),
"options".to_string(),
]),
}
}
fn tool_search_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"query": { "type": "string", "description": "Search query to find relevant tools" }
}),
required: Some(vec!["query".to_string()]),
}
}
fn cron_create_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"cron": { "type": "string", "description": "5-field cron expression" },
"prompt": { "type": "string", "description": "The prompt to execute" },
"recurring": { "type": "boolean", "description": "true = repeat, false = one-shot" },
"durable": { "type": "boolean", "description": "true = persist across restarts" }
}),
required: Some(vec!["cron".to_string(), "prompt".to_string()]),
}
}
fn cron_delete_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"id": { "type": "string", "description": "Job ID returned by CronCreate" }
}),
required: Some(vec!["id".to_string()]),
}
}
fn cron_list_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({}),
required: None,
}
}
fn config_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"action": { "type": "string", "enum": ["get", "set", "list"], "description": "Action to perform" },
"key": { "type": "string", "description": "Configuration key" },
"value": { "type": "string", "description": "Configuration value" }
}),
required: Some(vec!["action".to_string()]),
}
}
fn todo_write_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"todos": { "type": "array", "items": serde_json::json!({}), "description": "List of todo items" }
}),
required: Some(vec!["todos".to_string()]),
}
}
fn skill_schema() -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"skill": { "type": "string", "description": "The name of the skill to invoke" }
}),
required: Some(vec!["skill".to_string()]),
}
}
pub fn get_all_base_tools() -> Vec<ToolDefinition> {
ALL_TOOLS.iter().map(|f| f.2()).collect()
}
pub fn filter_tools(
tools: Vec<ToolDefinition>,
allowed: Option<Vec<String>>,
disallowed: Option<Vec<String>>,
) -> Vec<ToolDefinition> {
let mut result = tools;
if let Some(allowed) = allowed {
let allowed_set: std::collections::HashSet<_> = allowed.into_iter().collect();
result.retain(|t| allowed_set.contains(&t.name));
}
if let Some(disallowed) = disallowed {
let disallowed_set: std::collections::HashSet<_> = disallowed.into_iter().collect();
result.retain(|t| !disallowed_set.contains(&t.name));
}
result
}
#[derive(Debug, Clone)]
pub struct ToolWithMetadata {
pub name: String,
pub aliases: Option<Vec<String>>,
}
pub fn tool_matches_name(tool: &ToolWithMetadata, name: &str) -> bool {
tool.name == name
|| tool
.aliases
.as_ref()
.map_or(false, |a| a.contains(&name.to_string()))
}
pub fn find_tool_by_name<'a>(
tools: &'a [ToolDefinition],
name: &str,
) -> Option<&'a ToolDefinition> {
tools.iter().find(|t| t.name == name)
}
pub struct PartialToolDefinition {
pub name: String,
pub description: Option<String>,
pub input_schema: Option<ToolInputSchema>,
pub aliases: Option<Vec<String>>,
pub search_hint: Option<String>,
pub max_result_size_chars: Option<usize>,
pub should_defer: Option<bool>,
pub always_load: Option<bool>,
pub is_enabled: Option<Box<dyn Fn() -> bool + Send + Sync>>,
pub is_concurrency_safe: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
pub is_read_only: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
pub is_destructive: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
pub interrupt_behavior: Option<Box<dyn Fn() -> InterruptBehavior + Send + Sync>>,
pub is_search_or_read_command:
Option<Box<dyn Fn(&serde_json::Value) -> SearchOrReadCommand + Send + Sync>>,
pub is_open_world: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
pub requires_user_interaction: Option<Box<dyn Fn() -> bool + Send + Sync>>,
pub is_mcp: Option<bool>,
pub is_lsp: Option<bool>,
pub user_facing_name: Option<Box<dyn Fn(Option<&serde_json::Value>) -> String + Send + Sync>>,
}
impl Default for PartialToolDefinition {
fn default() -> Self {
Self {
name: String::new(),
description: None,
input_schema: None,
aliases: None,
search_hint: None,
max_result_size_chars: None,
should_defer: None,
always_load: None,
is_enabled: None,
is_concurrency_safe: None,
is_read_only: None,
is_destructive: None,
interrupt_behavior: None,
is_search_or_read_command: None,
is_open_world: None,
requires_user_interaction: None,
is_mcp: None,
is_lsp: None,
user_facing_name: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum InterruptBehavior {
Cancel,
Block,
}
impl Default for InterruptBehavior {
fn default() -> Self {
InterruptBehavior::Block
}
}
#[derive(Debug, Clone, Default)]
pub struct SearchOrReadCommand {
pub is_search: bool,
pub is_read: bool,
pub is_list: Option<bool>,
}
pub fn build_tool(def: PartialToolDefinition) -> ToolDefinition {
ToolDefinition {
name: def.name.clone(),
description: def.description.unwrap_or_default(),
input_schema: def.input_schema.unwrap_or_default(),
annotations: Some(ToolAnnotations {
read_only: Some(
def.is_read_only
.map_or(false, |f| f(&serde_json::json!({}))),
),
destructive: Some(
def.is_destructive
.as_ref()
.map_or(false, |f| f(&serde_json::json!({}))),
),
concurrency_safe: Some(
def.is_concurrency_safe
.as_ref()
.map_or(false, |f| f(&serde_json::json!({}))),
),
open_world: None,
idempotent: None,
}),
}
}