agentkit-tools-core 0.2.2

Tool traits, registry, execution, permissions, approvals, and auth for agentkit.
Documentation
# agentkit-tools-core

Core abstractions for defining, registering, executing, and governing tools in agentkit.

This crate provides:

- **`Tool` trait** — the interface every tool implements (spec + async invoke)
- **`ToolRegistry`** — a name-keyed collection of tools
- **`BasicToolExecutor`** — looks up tools, checks permissions, invokes them
- **Permission system** — composable policies (`PathPolicy`, `CommandPolicy`, `McpServerPolicy`, `CustomKindPolicy`) combined via `CompositePermissionChecker`
- **Interruption types**`ApprovalRequest` and `AuthRequest` for human-in-the-loop flows
- **Capability bridge**`ToolCapabilityProvider` adapts a registry into the agentkit capability layer

## Defining a tool

Implement the `Tool` trait with a `ToolSpec` and an async `invoke` method.
If the tool performs operations that need permission checks (filesystem
access, shell commands), override `proposed_requests` to declare them.

```rust,no_run
use agentkit_core::{MetadataMap, ToolOutput, ToolResultPart};
use agentkit_tools_core::{
    FileSystemPermissionRequest, PermissionRequest, Tool, ToolContext, ToolError,
    ToolName, ToolRequest, ToolResult, ToolSpec,
};
use async_trait::async_trait;
use serde_json::json;
use std::path::PathBuf;

/// A tool that reads a file and returns its contents.
struct ReadFileTool {
    spec: ToolSpec,
}

impl ReadFileTool {
    fn new() -> Self {
        Self {
            spec: ToolSpec::new(
                "read_file",
                "Read a file from the local filesystem",
                json!({
                    "type": "object",
                    "properties": {
                        "path": { "type": "string", "description": "Absolute file path" }
                    },
                    "required": ["path"]
                }),
            ),
        }
    }
}

#[async_trait]
impl Tool for ReadFileTool {
    fn spec(&self) -> &ToolSpec {
        &self.spec
    }

    // Declare that invoking this tool requires filesystem-read permission.
    fn proposed_requests(
        &self,
        request: &ToolRequest,
    ) -> Result<Vec<Box<dyn PermissionRequest>>, ToolError> {
        let path = request.input["path"]
            .as_str()
            .ok_or_else(|| ToolError::InvalidInput("missing `path`".into()))?;
        Ok(vec![Box::new(FileSystemPermissionRequest::Read {
            path: PathBuf::from(path),
            metadata: MetadataMap::new(),
        })])
    }

    async fn invoke(
        &self,
        request: ToolRequest,
        _ctx: &mut ToolContext<'_>,
    ) -> Result<ToolResult, ToolError> {
        let path = request.input["path"].as_str().unwrap();
        let content = std::fs::read_to_string(path)
            .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
        Ok(ToolResult::new(ToolResultPart::success(
            request.call_id,
            ToolOutput::text(content),
        )))
    }
}
```

## Registering tools and executing them

Build a `ToolRegistry`, wrap it in a `BasicToolExecutor`, and call
`execute` with a `ToolRequest` and `ToolContext`.

```rust,no_run
use agentkit_capabilities::CapabilityContext;
use agentkit_core::{MetadataMap, SessionId, ToolOutput, ToolResultPart, TurnId};
use agentkit_tools_core::{
    BasicToolExecutor, PermissionChecker, PermissionDecision, Tool, ToolContext, ToolError,
    ToolExecutionOutcome, ToolExecutor, ToolName, ToolRegistry, ToolRequest, ToolResult, ToolSpec,
};
use async_trait::async_trait;
use serde_json::json;

// A minimal tool for demonstration.
struct EchoTool { spec: ToolSpec }

#[async_trait]
impl Tool for EchoTool {
    fn spec(&self) -> &ToolSpec { &self.spec }
    async fn invoke(
        &self, request: ToolRequest, _ctx: &mut ToolContext<'_>,
    ) -> Result<ToolResult, ToolError> {
        Ok(ToolResult::new(ToolResultPart::success(
            request.call_id,
            ToolOutput::structured(json!({ "ok": true })),
        )))
    }
}

// Permit everything — real agents should use CompositePermissionChecker.
struct AllowAll;
impl PermissionChecker for AllowAll {
    fn evaluate(
        &self, _request: &dyn agentkit_tools_core::PermissionRequest,
    ) -> PermissionDecision {
        PermissionDecision::Allow
    }
}

# #[tokio::main]
# async fn main() {
let registry = ToolRegistry::new().with(EchoTool {
    spec: ToolSpec::new("echo", "Return a fixed payload", json!({ "type": "object" })),
});

let executor = BasicToolExecutor::new(registry);
let metadata = MetadataMap::new();
let mut ctx = ToolContext {
    capability: CapabilityContext {
        session_id: Some(&SessionId::new("s1")),
        turn_id: Some(&TurnId::new("t1")),
        metadata: &metadata,
    },
    permissions: &AllowAll,
    resources: &(),
    cancellation: None,
};

let outcome = executor
    .execute(
        ToolRequest::new(
            "call-1",
            "echo",
            json!({}),
            SessionId::new("s1"),
            TurnId::new("t1"),
        ),
        &mut ctx,
    )
    .await;

assert!(matches!(outcome, ToolExecutionOutcome::Completed(_)));
# }
```

## Setting up permissions

Use `CompositePermissionChecker` to layer multiple policies. Policies are
evaluated in order; the first `Deny` short-circuits. Any `RequireApproval`
is returned unless a later policy denies.

```rust
use agentkit_tools_core::{
    CommandPolicy, CompositePermissionChecker, PathPolicy,
    McpServerPolicy, PermissionDecision,
};

let permissions = CompositePermissionChecker::new(PermissionDecision::Allow)
    // Allow filesystem access under the workspace; protect .env files.
    .with_policy(
        PathPolicy::new()
            .allow_root("/workspace/project")
            .protect_root("/workspace/project/.env"),
    )
    // Allow git and cargo; require approval for anything else.
    .with_policy(
        CommandPolicy::new()
            .allow_executable("git")
            .allow_executable("cargo")
            .deny_executable("rm")
            .require_approval_for_unknown(true),
    )
    // Trust a specific MCP server.
    .with_policy(
        McpServerPolicy::new()
            .trust_server("github-mcp"),
    );
```