Adding Tools
Adding Custom Tools
Section titled “Adding Custom Tools”Tools are how Codive interacts with your system. Codive comes with built-in tools for file operations, shell commands, and code search. You can extend Codive by adding custom tools for your specific workflows.
Built-in Tools
Section titled “Built-in Tools”Codive includes these tools out of the box:
| Tool | Description |
|---|---|
read | Read file contents |
write | Write or modify files |
bash | Execute shell commands |
glob | Find files by pattern |
grep | Search file contents |
Creating a Custom Tool
Section titled “Creating a Custom Tool”Tools are defined in Rust and implement the Tool trait.
The Tool Trait
Section titled “The Tool Trait”use async_trait::async_trait;use serde::{Deserialize, Serialize};
#[async_trait]pub trait Tool: Send + Sync { /// Tool name used in prompts fn name(&self) -> &str;
/// Description shown to the AI fn description(&self) -> &str;
/// JSON schema for parameters fn parameters_schema(&self) -> serde_json::Value;
/// Execute the tool async fn execute( &self, params: serde_json::Value, context: &ToolContext, ) -> Result<ToolOutput, ToolError>;}Example: HTTP Request Tool
Section titled “Example: HTTP Request Tool”Let’s create a tool that makes HTTP requests:
-
Create the tool file
Create
src/tools/http.rs:use async_trait::async_trait;use reqwest::Client;use serde::{Deserialize, Serialize};use serde_json::json;use crate::tool::{Tool, ToolContext, ToolError, ToolOutput};pub struct HttpTool {client: Client,}impl HttpTool {pub fn new() -> Self {Self {client: Client::new(),}}}#[derive(Deserialize)]struct HttpParams {method: String,url: String,headers: Option<std::collections::HashMap<String, String>>,body: Option<String>,}#[async_trait]impl Tool for HttpTool {fn name(&self) -> &str {"http"}fn description(&self) -> &str {"Make HTTP requests to external APIs. \Supports GET, POST, PUT, DELETE methods."}fn parameters_schema(&self) -> serde_json::Value {json!({"type": "object","required": ["method", "url"],"properties": {"method": {"type": "string","enum": ["GET", "POST", "PUT", "DELETE"],"description": "HTTP method"},"url": {"type": "string","description": "Request URL"},"headers": {"type": "object","description": "Optional headers","additionalProperties": { "type": "string" }},"body": {"type": "string","description": "Request body (for POST/PUT)"}}})}async fn execute(&self,params: serde_json::Value,_context: &ToolContext,) -> Result<ToolOutput, ToolError> {let params: HttpParams = serde_json::from_value(params).map_err(|e| ToolError::InvalidParams(e.to_string()))?;let mut request = match params.method.as_str() {"GET" => self.client.get(¶ms.url),"POST" => self.client.post(¶ms.url),"PUT" => self.client.put(¶ms.url),"DELETE" => self.client.delete(¶ms.url),_ => return Err(ToolError::InvalidParams(format!("Unknown method: {}", params.method))),};if let Some(headers) = params.headers {for (key, value) in headers {request = request.header(&key, &value);}}if let Some(body) = params.body {request = request.body(body);}let response = request.send().await.map_err(|e| ToolError::Execution(e.to_string()))?;let status = response.status();let body = response.text().await.map_err(|e| ToolError::Execution(e.to_string()))?;Ok(ToolOutput {content: format!("Status: {}\n\nBody:\n{}",status,body),metadata: Some(json!({"status_code": status.as_u16()})),})}} -
Register the tool
In
src/tools/mod.rs:mod http;pub use http::HttpTool;pub fn all_tools() -> Vec<Box<dyn Tool>> {vec![Box::new(ReadTool::new()),Box::new(WriteTool::new()),Box::new(BashTool::new()),Box::new(GlobTool::new()),Box::new(GrepTool::new()),Box::new(HttpTool::new()), // Add our new tool]} -
Use the tool
Now you can ask Codive:
> fetch the JSON from https://api.example.com/usersCodive will use your HTTP tool to make the request.
Tool Best Practices
Section titled “Tool Best Practices”Clear Descriptions
Section titled “Clear Descriptions”Write descriptions that help the AI understand when to use the tool:
fn description(&self) -> &str { "Search for files in the project directory using glob patterns. \ Use this when you need to find files by name or extension. \ Examples: '**/*.rs' finds all Rust files, 'src/**/*.test.ts' \ finds all test files in src."}Accurate Schemas
Section titled “Accurate Schemas”Define parameter schemas precisely:
fn parameters_schema(&self) -> serde_json::Value { json!({ "type": "object", "required": ["pattern"], // Mark required fields "properties": { "pattern": { "type": "string", "description": "Glob pattern (e.g., '**/*.rs')" }, "path": { "type": "string", "description": "Optional starting directory", "default": "." // Document defaults } } })}Error Handling
Section titled “Error Handling”Provide helpful error messages:
async fn execute(&self, params: Value, ctx: &ToolContext) -> Result<ToolOutput, ToolError> { let path = params["path"] .as_str() .ok_or_else(|| ToolError::InvalidParams( "Missing required parameter 'path'. \ Provide the file path to read." .to_string() ))?;
// Validate the path exists if !std::path::Path::new(path).exists() { return Err(ToolError::Execution( format!("File not found: {}. \ Check the path and try again.", path) )); }
// ... rest of implementation}Security Considerations
Section titled “Security Considerations”Implement safety checks:
async fn execute(&self, params: Value, ctx: &ToolContext) -> Result<ToolOutput, ToolError> { let command = params["command"].as_str().unwrap();
// Block dangerous commands let blocked = ["rm -rf /", "sudo", "> /dev/"]; for pattern in blocked { if command.contains(pattern) { return Err(ToolError::Execution( format!("Command blocked for safety: {}", command) )); } }
// Proceed with execution // ...}Tool Configuration
Section titled “Tool Configuration”Configure tools in codive.toml:
[tools]enabled = ["read", "write", "bash", "glob", "grep", "http"]
[tools.http]# Custom configuration for HTTP tooltimeout = 30max_redirects = 5allowed_domains = ["api.example.com", "api.github.com"]Access configuration in your tool:
pub struct HttpTool { client: Client, config: HttpConfig,}
impl HttpTool { pub fn from_config(config: &Config) -> Self { let http_config = config.tools.http.clone(); Self { client: Client::builder() .timeout(Duration::from_secs(http_config.timeout)) .build() .unwrap(), config: http_config, } }}Example Tools
Section titled “Example Tools”Database Query Tool
Section titled “Database Query Tool”pub struct DatabaseTool { pool: PgPool,}
#[async_trait]impl Tool for DatabaseTool { fn name(&self) -> &str { "database" }
fn description(&self) -> &str { "Execute read-only SQL queries against the database." }
async fn execute(&self, params: Value, _ctx: &ToolContext) -> Result<ToolOutput, ToolError> { let query = params["query"].as_str().unwrap();
// Only allow SELECT queries if !query.trim().to_uppercase().starts_with("SELECT") { return Err(ToolError::Execution( "Only SELECT queries are allowed".to_string() )); }
let rows: Vec<serde_json::Value> = sqlx::query(query) .fetch_all(&self.pool) .await .map_err(|e| ToolError::Execution(e.to_string()))? .iter() .map(|row| row_to_json(row)) .collect();
Ok(ToolOutput { content: serde_json::to_string_pretty(&rows).unwrap(), metadata: None, }) }}API Documentation Tool
Section titled “API Documentation Tool”pub struct ApiDocTool { openapi_spec: OpenApiSpec,}
#[async_trait]impl Tool for ApiDocTool { fn name(&self) -> &str { "api_docs" }
fn description(&self) -> &str { "Search and retrieve API documentation. \ Use to find endpoint details, request/response schemas." }
async fn execute(&self, params: Value, _ctx: &ToolContext) -> Result<ToolOutput, ToolError> { let query = params["query"].as_str().unwrap();
let results = self.openapi_spec.search(query);
Ok(ToolOutput { content: format_results(&results), metadata: None, }) }}Next Steps
Section titled “Next Steps”- Learn about custom providers
- Set up IDE integration