Skip to main content

everruns_core/
tool_execution.rs

1//! Neutral contracts for tool execution and tool-scoped authorities.
2
3use crate::error::Result;
4use crate::tool_context::ToolContext;
5use crate::tool_types::{ToolCall, ToolDefinition, ToolResult};
6use crate::typed_id::SessionId;
7use async_trait::async_trait;
8use std::collections::HashMap;
9
10fn build_tool_map(tool_defs: &[ToolDefinition]) -> HashMap<&str, &ToolDefinition> {
11    tool_defs.iter().map(|def| (def.name(), def)).collect()
12}
13
14/// Trait for executing tool calls
15///
16/// Implementations handle the actual tool execution:
17/// - Webhook calls
18/// - Built-in function execution
19/// - Mock execution for testing
20#[async_trait]
21pub trait ToolExecutor: Send + Sync {
22    /// Execute a single tool call (without context)
23    ///
24    /// This is the legacy method that doesn't provide context to tools.
25    /// Use `execute_with_context` when context is available.
26    async fn execute(&self, tool_call: &ToolCall, tool_def: &ToolDefinition) -> Result<ToolResult>;
27
28    /// Execute a single tool call with context
29    ///
30    /// This method provides runtime context to tools that need it (like filesystem tools).
31    /// The default implementation delegates to `execute()`.
32    async fn execute_with_context(
33        &self,
34        tool_call: &ToolCall,
35        tool_def: &ToolDefinition,
36        _context: &ToolContext,
37    ) -> Result<ToolResult> {
38        // Default: delegate to execute(), ignoring context
39        self.execute(tool_call, tool_def).await
40    }
41
42    /// Execute multiple tool calls (default: sequential)
43    async fn execute_batch(
44        &self,
45        tool_calls: &[ToolCall],
46        tool_defs: &[ToolDefinition],
47    ) -> Result<Vec<ToolResult>> {
48        let mut results = Vec::with_capacity(tool_calls.len());
49
50        let tool_map = build_tool_map(tool_defs);
51
52        for tool_call in tool_calls {
53            let tool_def = tool_map.get(tool_call.name.as_str()).ok_or_else(|| {
54                crate::error::AgentLoopError::tool(format!(
55                    "Tool definition not found: {}",
56                    tool_call.name
57                ))
58            })?;
59
60            results.push(self.execute(tool_call, tool_def).await?);
61        }
62
63        Ok(results)
64    }
65
66    /// Execute multiple tool calls in parallel
67    async fn execute_parallel(
68        &self,
69        tool_calls: &[ToolCall],
70        tool_defs: &[ToolDefinition],
71    ) -> Result<Vec<ToolResult>>
72    where
73        Self: Sized,
74    {
75        use futures::future::join_all;
76
77        let tool_map = build_tool_map(tool_defs);
78
79        let futures: Vec<_> = tool_calls
80            .iter()
81            .map(|tool_call| async {
82                let tool_def = tool_map.get(tool_call.name.as_str()).ok_or_else(|| {
83                    crate::error::AgentLoopError::tool(format!(
84                        "Tool definition not found: {}",
85                        tool_call.name
86                    ))
87                })?;
88                self.execute(tool_call, tool_def).await
89            })
90            .collect();
91
92        let results = join_all(futures).await;
93        results.into_iter().collect()
94    }
95}
96
97/// Delegating impl so callers can hold a `ToolExecutor` as a trait object
98/// (e.g. to choose between a plain registry and an MCP-routing composite at
99/// runtime without monomorphizing the consumer).
100#[async_trait]
101impl ToolExecutor for std::sync::Arc<dyn ToolExecutor> {
102    async fn execute(&self, tool_call: &ToolCall, tool_def: &ToolDefinition) -> Result<ToolResult> {
103        (**self).execute(tool_call, tool_def).await
104    }
105
106    async fn execute_with_context(
107        &self,
108        tool_call: &ToolCall,
109        tool_def: &ToolDefinition,
110        context: &ToolContext,
111    ) -> Result<ToolResult> {
112        (**self)
113            .execute_with_context(tool_call, tool_def, context)
114            .await
115    }
116
117    async fn execute_batch(
118        &self,
119        tool_calls: &[ToolCall],
120        tool_defs: &[ToolDefinition],
121    ) -> Result<Vec<ToolResult>> {
122        (**self).execute_batch(tool_calls, tool_defs).await
123    }
124}
125
126/// Trait for checking budget status from within tool execution.
127///
128/// Implemented by gRPC adapters (worker → server) and direct adapters (in-process).
129/// Used by the `check_budget` tool to return real budget data to agents.
130/// The org_id is captured at construction time by the implementing adapter.
131#[async_trait]
132pub trait BudgetChecker: Send + Sync {
133    /// Check all budgets for a session and return a tool-friendly response.
134    async fn check_budgets(&self, session_id: &str) -> Result<crate::budget::BudgetToolResponse>;
135}
136
137// ============================================================================
138// PaymentAuthority - For capability-internal machine payments
139// ============================================================================
140
141/// Internal authority for paid capability operations.
142///
143/// Capabilities call this with fixed, typed requests. The model never receives a
144/// generic paid HTTP tool, wallet credentials, or payment payloads.
145#[async_trait]
146pub trait PaymentAuthority: Send + Sync {
147    async fn execute_machine_payment(
148        &self,
149        session_id: SessionId,
150        request: crate::payment::MachinePaymentRequest,
151    ) -> Result<crate::payment::MachinePaymentResponse>;
152}
153
154/// Per-org gate on outbound tool execution.
155///
156/// Returns `true` if the call is within the per-org budget, `false` if the
157/// org has exceeded its outbound tool rate limit for this window.
158/// Implementations must be fail-open: Valkey/backend errors should return `true`
159/// rather than blocking legitimate tool calls.
160#[async_trait]
161pub trait OutboundToolRateLimiter: Send + Sync {
162    /// Key by the public org UUID (keyed string representation).
163    async fn check_org(&self, org_id: &crate::typed_id::OrgId) -> bool;
164}