use crate::{ToolRateLimit, ToolSchema};
use serde_json::json;
pub fn shell() -> ToolSchema {
ToolSchema {
name: "shell".to_string(),
description: "Execute a shell command and return stdout/stderr.".to_string(),
parameters: json!({
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The shell command to execute"
},
"cwd": {
"type": "string",
"description": "Working directory (optional)"
},
"timeout_ms": {
"type": "integer",
"description": "Timeout in milliseconds (optional)"
}
},
"required": ["command"]
}),
returns: Some(json!({
"type": "object",
"properties": {
"stdout": { "type": "string" },
"stderr": { "type": "string" },
"exit_code": { "type": "integer" }
}
})),
idempotent: false,
cache_ttl_secs: None,
rate_limit: None,
}
}
pub fn read_file() -> ToolSchema {
ToolSchema {
name: "read_file".to_string(),
description: "Read the contents of a file.".to_string(),
parameters: json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute or relative file path"
},
"encoding": {
"type": "string",
"description": "Text encoding (default: utf-8)"
},
"offset": {
"type": "integer",
"description": "0-based starting line offset (optional)"
},
"limit": {
"type": "integer",
"description": "Maximum number of lines to return (optional)"
}
},
"required": ["path"]
}),
returns: Some(json!({
"type": "object",
"properties": {
"content": { "type": "string" },
"size_bytes": { "type": "integer" }
}
})),
idempotent: true,
cache_ttl_secs: Some(10),
rate_limit: None,
}
}
pub fn edit_file() -> ToolSchema {
ToolSchema {
name: "edit_file".to_string(),
description: "Replace a unique text span in a file.".to_string(),
parameters: json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute or relative file path"
},
"old_text": {
"type": "string",
"description": "Existing text to replace. Must match uniquely."
},
"new_text": {
"type": "string",
"description": "Replacement text"
}
},
"required": ["path", "old_text", "new_text"]
}),
returns: Some(json!({
"type": "object",
"properties": {
"edited": { "type": "string" },
"diff_summary": { "type": "string" }
}
})),
idempotent: false,
cache_ttl_secs: None,
rate_limit: None,
}
}
pub fn write_file() -> ToolSchema {
ToolSchema {
name: "write_file".to_string(),
description: "Write content to a file, creating it if it doesn't exist.".to_string(),
parameters: json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute or relative file path"
},
"content": {
"type": "string",
"description": "Content to write"
},
"append": {
"type": "boolean",
"description": "Append instead of overwrite (default: false)"
}
},
"required": ["path", "content"]
}),
returns: Some(json!({
"type": "object",
"properties": {
"bytes_written": { "type": "integer" }
}
})),
idempotent: false,
cache_ttl_secs: None,
rate_limit: None,
}
}
pub fn find_files() -> ToolSchema {
ToolSchema {
name: "find_files".to_string(),
description: "Find files by name or simple glob pattern within a directory tree."
.to_string(),
parameters: json!({
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "File name or simple glob pattern (supports * and ?)"
},
"path": {
"type": "string",
"description": "Root search path (default: .)"
},
"max_results": {
"type": "integer",
"description": "Maximum number of matching files to return (default: 50)"
}
},
"required": ["pattern"]
}),
returns: Some(json!({
"type": "object",
"properties": {
"files": {
"type": "array",
"items": { "type": "string" }
},
"count": { "type": "integer" },
"truncated": { "type": "boolean" }
}
})),
idempotent: true,
cache_ttl_secs: Some(5),
rate_limit: None,
}
}
pub fn grep_files() -> ToolSchema {
ToolSchema {
name: "grep_files".to_string(),
description: "Search file contents recursively using a regex pattern.".to_string(),
parameters: json!({
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "Regex pattern to search for"
},
"path": {
"type": "string",
"description": "Root search path (default: .)"
},
"max_results": {
"type": "integer",
"description": "Maximum number of matching lines to return (default: 50)"
}
},
"required": ["pattern"]
}),
returns: Some(json!({
"type": "object",
"properties": {
"matches": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": { "type": "string" },
"line": { "type": "integer" },
"text": { "type": "string" }
}
}
},
"count": { "type": "integer" },
"truncated": { "type": "boolean" }
}
})),
idempotent: true,
cache_ttl_secs: Some(5),
rate_limit: None,
}
}
pub fn list_dir() -> ToolSchema {
ToolSchema {
name: "list_dir".to_string(),
description: "List files and directories in a path.".to_string(),
parameters: json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Directory path"
},
"recursive": {
"type": "boolean",
"description": "List recursively (default: false)"
},
"pattern": {
"type": "string",
"description": "Glob pattern filter (optional)"
}
},
"required": ["path"]
}),
returns: Some(json!({
"type": "object",
"properties": {
"entries": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"path": { "type": "string" },
"is_dir": { "type": "boolean" },
"size_bytes": { "type": "integer" }
}
}
}
}
})),
idempotent: true,
cache_ttl_secs: Some(5),
rate_limit: None,
}
}
pub fn http_request() -> ToolSchema {
ToolSchema {
name: "http_request".to_string(),
description: "Make an HTTP request to a URL.".to_string(),
parameters: json!({
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The URL to request"
},
"method": {
"type": "string",
"description": "HTTP method (GET, POST, PUT, DELETE, PATCH)",
"enum": ["GET", "POST", "PUT", "DELETE", "PATCH"]
},
"headers": {
"type": "object",
"description": "Request headers as key-value pairs"
},
"body": {
"type": "string",
"description": "Request body (for POST/PUT/PATCH)"
}
},
"required": ["url"]
}),
returns: Some(json!({
"type": "object",
"properties": {
"status": { "type": "integer" },
"headers": { "type": "object" },
"body": { "type": "string" }
}
})),
idempotent: false,
cache_ttl_secs: None,
rate_limit: Some(ToolRateLimit {
max_calls: 30,
interval_secs: 60.0,
}),
}
}
pub fn calculate() -> ToolSchema {
ToolSchema {
name: "calculate".to_string(),
description: "Evaluate a mathematical expression.".to_string(),
parameters: json!({
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4')"
}
},
"required": ["expression"]
}),
returns: Some(json!({
"type": "object",
"properties": {
"result": { "type": "number" }
}
})),
idempotent: true,
cache_ttl_secs: Some(3600),
rate_limit: None,
}
}
pub fn search() -> ToolSchema {
ToolSchema {
name: "search".to_string(),
description: "Search for information using a query string.".to_string(),
parameters: json!({
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
},
"max_results": {
"type": "integer",
"description": "Maximum number of results (default: 10)"
}
},
"required": ["query"]
}),
returns: Some(json!({
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": { "type": "string" },
"snippet": { "type": "string" },
"url": { "type": "string" }
}
}
}
}
})),
idempotent: true,
cache_ttl_secs: Some(60),
rate_limit: Some(ToolRateLimit {
max_calls: 10,
interval_secs: 60.0,
}),
}
}
pub fn browser() -> ToolSchema {
ToolSchema {
name: "browser".to_string(),
description: "Navigate and interact with web pages in a browser.".to_string(),
parameters: json!({
"type": "object",
"properties": {
"action": {
"type": "string",
"description": "Browser action to perform",
"enum": ["navigate", "click", "fill", "screenshot", "text", "back", "forward"]
},
"url": {
"type": "string",
"description": "URL to navigate to (for 'navigate' action)"
},
"selector": {
"type": "string",
"description": "CSS selector for the target element"
},
"value": {
"type": "string",
"description": "Value to fill (for 'fill' action)"
}
},
"required": ["action"]
}),
returns: Some(json!({
"type": "object",
"properties": {
"success": { "type": "boolean" },
"content": { "type": "string" },
"screenshot_path": { "type": "string" }
}
})),
idempotent: false,
cache_ttl_secs: None,
rate_limit: Some(ToolRateLimit {
max_calls: 60,
interval_secs: 60.0,
}),
}
}
pub fn all() -> Vec<ToolSchema> {
vec![
shell(),
read_file(),
edit_file(),
write_file(),
list_dir(),
find_files(),
grep_files(),
http_request(),
calculate(),
search(),
browser(),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_schemas_have_required_fields() {
for schema in all() {
assert!(!schema.name.is_empty(), "schema name is empty");
assert!(
!schema.description.is_empty(),
"schema {} has no description",
schema.name
);
assert!(
schema.parameters.is_object(),
"schema {} parameters not an object",
schema.name
);
let params = schema.parameters.as_object().unwrap();
assert_eq!(
params.get("type").and_then(|v| v.as_str()),
Some("object"),
"schema {} parameters type not 'object'",
schema.name
);
assert!(
params.contains_key("properties"),
"schema {} parameters missing 'properties'",
schema.name
);
assert!(
params.contains_key("required"),
"schema {} parameters missing 'required'",
schema.name
);
}
}
#[test]
fn schemas_are_unique() {
let schemas = all();
let names: Vec<&str> = schemas.iter().map(|s| s.name.as_str()).collect();
let mut unique = names.clone();
unique.sort();
unique.dedup();
assert_eq!(names.len(), unique.len(), "duplicate schema names");
}
#[test]
fn idempotent_tools_have_cache_hints() {
for schema in all() {
if schema.idempotent && schema.name != "shell" {
}
}
assert!(read_file().cache_ttl_secs.is_some());
assert!(list_dir().cache_ttl_secs.is_some());
assert!(calculate().cache_ttl_secs.is_some());
assert!(search().cache_ttl_secs.is_some());
}
#[test]
fn rate_limited_tools() {
assert!(http_request().rate_limit.is_some());
assert!(search().rate_limit.is_some());
assert!(browser().rate_limit.is_some());
assert!(shell().rate_limit.is_none());
}
}