Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/dallay/corvus/llms.txt

Use this file to discover all available pages before exploring further.

Example: HTTP GET Tool

A complete example of building a tool that fetches URLs and returns status codes. Source: examples/custom_tool.rs

Full Implementation

//! Example: Implementing a custom Tool for Corvus

use anyhow::Result;
use async_trait::async_trait;
use serde_json::{json, Value};

/// Mirrors src/tools/traits.rs
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ToolResult {
    pub success: bool,
    pub output: String,
    pub error: Option<String>,
}

#[async_trait]
pub trait Tool: Send + Sync {
    fn name(&self) -> &str;
    fn description(&self) -> &str;
    fn parameters_schema(&self) -> Value;
    async fn execute(&self, args: Value) -> Result<ToolResult>;
}

/// Example: A tool that fetches a URL and returns the status code
pub struct HttpGetTool;

#[async_trait]
impl Tool for HttpGetTool {
    fn name(&self) -> &str {
        "http_get"
    }

    fn description(&self) -> &str {
        "Fetch a URL and return the HTTP status code and content length"
    }

    fn parameters_schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "url": { "type": "string", "description": "URL to fetch" }
            },
            "required": ["url"]
        })
    }

    async fn execute(&self, args: Value) -> Result<ToolResult> {
        let url = args["url"]
            .as_str()
            .ok_or_else(|| anyhow::anyhow!("Missing 'url' parameter"))?;

        match reqwest::get(url).await {
            Ok(resp) => {
                let status = resp.status().as_u16();
                let len = resp.content_length().unwrap_or(0);
                Ok(ToolResult {
                    success: status < 400,
                    output: format!("HTTP {status} — {len} bytes"),
                    error: None,
                })
            }
            Err(e) => Ok(ToolResult {
                success: false,
                output: String::new(),
                error: Some(format!("Request failed: {e}")),
            }),
        }
    }
}

Integration Steps

1. Add to Corvus

Copy implementation to src/tools/http_get.rs

2. Register Tool

Add to src/tools/mod.rs:
pub mod http_get;

pub fn default_tools(
    security: Arc<SecurityPolicy>,
    memory: Arc<dyn Memory>,
    runtime: Arc<dyn RuntimeAdapter>,
    config: &Config,
) -> Vec<Arc<dyn Tool>> {
    vec![
        // ... existing tools
        Arc::new(http_get::HttpGetTool),
    ]
}

3. Test

Add tests in src/tools/http_get.rs:
#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_http_get_success() {
        let tool = HttpGetTool;
        let result = tool.execute(json!({
            "url": "https://httpbin.org/status/200"
        })).await.unwrap();
        
        assert!(result.success);
        assert!(result.output.contains("HTTP 200"));
    }
    
    #[tokio::test]
    async fn test_http_get_not_found() {
        let tool = HttpGetTool;
        let result = tool.execute(json!({
            "url": "https://httpbin.org/status/404"
        })).await.unwrap();
        
        assert!(!result.success);
        assert!(result.output.contains("HTTP 404"));
    }
}
Run tests:
cargo test tools::http_get

4. Use

The agent can now fetch URLs:
corvus agent -m "Check if https://docs.rs is online"
Agent response:
Let me check...

[Tool: http_get] {"url": "https://docs.rs"}
✔ HTTP 200 — 12345 bytes

Yes, https://docs.rs is online and responding with HTTP 200.

Enhancements

Add Headers Support

fn parameters_schema(&self) -> Value {
    json!({
        "type": "object",
        "properties": {
            "url": { "type": "string" },
            "headers": {
                "type": "object",
                "description": "Custom headers (key-value pairs)"
            }
        },
        "required": ["url"]
    })
}

async fn execute(&self, args: Value) -> Result<ToolResult> {
    let url = args["url"].as_str()?;
    
    let mut req = reqwest::Client::new().get(url);
    
    if let Some(headers) = args["headers"].as_object() {
        for (key, value) in headers {
            if let Some(val) = value.as_str() {
                req = req.header(key, val);
            }
        }
    }
    
    // Execute request...
}

Add Timeout

use std::time::Duration;

let client = reqwest::Client::builder()
    .timeout(Duration::from_secs(10))
    .build()?;

let resp = client.get(url).send().await?;

Return JSON Body

match reqwest::get(url).await {
    Ok(resp) => {
        let status = resp.status().as_u16();
        let body = resp.text().await?;
        
        let output = serde_json::json!({
            "status": status,
            "body": body,
            "success": status < 400
        });
        
        Ok(ToolResult {
            success: status < 400,
            output: output.to_string(),
            error: None,
        })
    }
    // ...
}

Build docs developers (and LLMs) love