llama-cpp-v3-agent-sdk 0.1.7

Agentic tool-use loop on top of llama-cpp-v3 — local LLM agents with built-in tools
Documentation
use serde::{Deserialize, Serialize};

/// Permission decision for a tool call.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PermissionDecision {
    /// Allow this single call.
    AllowOnce,
    /// Allow all future calls to this tool.
    AllowAlways,
    /// Deny this single call.
    DenyOnce,
    /// Deny all future calls to this tool.
    DenyAlways,
}

impl PermissionDecision {
    pub fn is_allowed(self) -> bool {
        matches!(self, Self::AllowOnce | Self::AllowAlways)
    }
}

/// Permission request sent to the user/callback.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionRequest {
    /// Name of the tool being invoked.
    pub tool_name: String,
    /// Human-readable description of what the tool will do.
    pub description: String,
    /// Whether this action is considered dangerous.
    pub dangerous: bool,
    /// The raw arguments (for inspection).
    pub arguments: serde_json::Value,
}

/// Configures how the agent handles permissions.
pub enum PermissionMode {
    /// Auto-approve everything (dangerous!).
    AutoApprove,
    /// Use a callback to ask for permission.
    Callback(Box<dyn Fn(&PermissionRequest) -> PermissionDecision + Send + Sync>),
}

impl std::fmt::Debug for PermissionMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PermissionMode::AutoApprove => write!(f, "AutoApprove"),
            PermissionMode::Callback(_) => write!(f, "Callback(...)"),
        }
    }
}

/// Tracks per-tool permission state across the session.
pub struct PermissionTracker {
    mode: PermissionMode,
    /// Tools that have been granted "always allow".
    always_allowed: std::collections::HashSet<String>,
    /// Tools that have been denied "always deny".
    always_denied: std::collections::HashSet<String>,
}

impl PermissionTracker {
    pub fn new(mode: PermissionMode) -> Self {
        Self {
            mode,
            always_allowed: std::collections::HashSet::new(),
            always_denied: std::collections::HashSet::new(),
        }
    }

    /// Check if a tool call is permitted.
    ///
    /// Returns `true` if the tool may execute, `false` otherwise.
    pub fn check(&mut self, request: &PermissionRequest) -> bool {
        // Already permanently decided?
        if self.always_allowed.contains(&request.tool_name) {
            return true;
        }
        if self.always_denied.contains(&request.tool_name) {
            return false;
        }

        match &self.mode {
            PermissionMode::AutoApprove => true,
            PermissionMode::Callback(cb) => {
                let decision = cb(request);
                match decision {
                    PermissionDecision::AllowAlways => {
                        self.always_allowed.insert(request.tool_name.clone());
                        true
                    }
                    PermissionDecision::DenyAlways => {
                        self.always_denied.insert(request.tool_name.clone());
                        false
                    }
                    PermissionDecision::AllowOnce => true,
                    PermissionDecision::DenyOnce => false,
                }
            }
        }
    }
}