Skip to main content

garudust_core/
tool.rs

1use std::{collections::HashMap, sync::Arc};
2
3use async_trait::async_trait;
4use tokio::sync::RwLock;
5
6use crate::{
7    budget::IterationBudget,
8    config::AgentConfig,
9    error::{AgentError, ToolError},
10    memory::MemoryStore,
11    types::{ToolResult, ToolSchema},
12};
13
14/// Accumulated permissions from all skills loaded in the current session.
15/// Each entry maps a tool name to `true` (allowed) or `false` (denied).
16/// Union semantics: `true` from any skill wins over `false` from another.
17/// Tools absent from the map are not restricted by skill permissions.
18#[derive(Debug, Default, Clone)]
19pub struct SkillPermissions(pub HashMap<String, bool>);
20
21impl SkillPermissions {
22    /// Merge another skill's permissions using union semantics (allow wins).
23    pub fn merge(&mut self, other: &HashMap<String, bool>) {
24        for (tool, allowed) in other {
25            let entry = self.0.entry(tool.clone()).or_insert(false);
26            if *allowed {
27                *entry = true;
28            }
29        }
30    }
31
32    /// Returns `Some(false)` only if the tool is explicitly denied by every
33    /// loaded skill that mentions it. Returns `None` if no skill restricts it.
34    pub fn check(&self, tool_name: &str) -> Option<bool> {
35        self.0.get(tool_name).copied()
36    }
37}
38
39#[async_trait]
40pub trait Tool: Send + Sync + 'static {
41    fn name(&self) -> &str;
42    fn description(&self) -> &str;
43    fn schema(&self) -> serde_json::Value;
44    fn toolset(&self) -> &str;
45
46    /// Returns true for tools that write, delete, or execute — i.e. operations
47    /// that are hard to reverse. The registry uses this to gate approval and
48    /// emit an audit-log entry before dispatch, regardless of how the tool
49    /// encodes its arguments internally.
50    fn is_destructive(&self) -> bool {
51        false
52    }
53
54    /// Parameter-aware variant called by the registry. Override this when
55    /// destructiveness depends on the specific arguments (e.g. a terminal tool
56    /// that can also run read-only commands). The default delegates to
57    /// `is_destructive()` so existing tools need no changes.
58    fn is_destructive_for(&self, _params: &serde_json::Value) -> bool {
59        self.is_destructive()
60    }
61
62    /// Return `true` to opt out of the agent's global `tool_timeout_secs` budget.
63    /// Override on tools that manage their own timeout internally (e.g. `Terminal`).
64    fn bypass_dispatch_timeout(&self) -> bool {
65        false
66    }
67
68    async fn execute(
69        &self,
70        params: serde_json::Value,
71        ctx: &ToolContext,
72    ) -> Result<ToolResult, ToolError>;
73
74    fn to_schema(&self) -> ToolSchema {
75        ToolSchema {
76            name: self.name().to_string(),
77            description: self.description().to_string(),
78            parameters: self.schema(),
79        }
80    }
81}
82
83#[async_trait]
84pub trait SubAgentRunner: Send + Sync + 'static {
85    async fn run_task(&self, task: &str, session_id: &str) -> Result<String, AgentError>;
86}
87
88pub struct ToolContext {
89    pub session_id: String,
90    pub agent_id: String,
91    pub iteration: u32,
92    pub budget: Arc<IterationBudget>,
93    pub memory: Arc<dyn MemoryStore>,
94    pub config: Arc<AgentConfig>,
95    pub approver: Arc<dyn CommandApprover>,
96    pub sub_agent: Option<Arc<dyn SubAgentRunner>>,
97    /// Accumulated permissions from all skills loaded this session via skill_view.
98    /// Shared across all tool dispatches within the same agent turn.
99    pub skill_permissions: Arc<RwLock<SkillPermissions>>,
100    /// Tools required by skills loaded this session (via required_tools frontmatter).
101    /// skill_view appends to this; the agent checks it at end-turn.
102    pub required_tools: Arc<RwLock<Vec<String>>>,
103}
104
105#[async_trait]
106pub trait CommandApprover: Send + Sync + 'static {
107    /// Called by ToolRegistry::dispatch() for every destructive tool before
108    /// execute(). `tool_name` is the registered tool name; `params` is the
109    /// JSON-serialised parameter object passed by the model.
110    async fn approve(&self, tool_name: &str, params: &str) -> ApprovalDecision;
111}
112
113#[derive(Debug, Clone, PartialEq)]
114pub enum ApprovalDecision {
115    Approved,
116    ApprovedAlways,
117    Denied,
118    Yolo,
119}