Skip to main content

adk_core/
tool.rs

1use crate::{CallbackContext, EventActions, MemoryEntry, Result};
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::sync::Arc;
6
7/// The core trait for all tools that agents can invoke.
8///
9/// Tools extend agent capabilities with custom functions. Each tool has a name,
10/// description, optional parameter schema, and an async `execute` method.
11#[async_trait]
12pub trait Tool: Send + Sync {
13    /// Returns the unique name of this tool.
14    fn name(&self) -> &str;
15    /// Returns a human-readable description of what this tool does.
16    fn description(&self) -> &str;
17
18    /// Returns the tool declaration that should be exposed to model providers.
19    ///
20    /// The default implementation produces the standard ADK function-tool
21    /// declaration (`name`, `description`, optional `parameters`, optional
22    /// `response`). Provider-specific built-in tools may override this to attach
23    /// additional metadata that the provider adapters understand.
24    ///
25    /// # Example
26    ///
27    /// ```rust,ignore
28    /// fn declaration(&self) -> serde_json::Value {
29    ///     serde_json::json!({
30    ///         "name": self.name(),
31    ///         "description": self.description(),
32    ///         "x-adk-openai-tool": {
33    ///             "type": "web_search_2025_08_26"
34    ///         }
35    ///     })
36    /// }
37    /// ```
38    fn declaration(&self) -> Value {
39        let mut decl = serde_json::json!({
40            "name": self.name(),
41            "description": self.enhanced_description(),
42        });
43
44        if let Some(params) = self.parameters_schema() {
45            decl["parameters"] = params;
46        }
47
48        if let Some(response) = self.response_schema() {
49            decl["response"] = response;
50        }
51
52        decl
53    }
54
55    /// Returns an enhanced description that may include additional notes.
56    /// For long-running tools, this includes a warning not to call the tool
57    /// again if it has already returned a pending status.
58    /// Default implementation returns the base description.
59    fn enhanced_description(&self) -> String {
60        self.description().to_string()
61    }
62
63    /// Indicates whether the tool is a long-running operation.
64    /// Long-running tools typically return a task ID immediately and
65    /// complete the operation asynchronously.
66    fn is_long_running(&self) -> bool {
67        false
68    }
69
70    /// Indicates whether this tool is a built-in server-side tool (e.g., `google_search`, `url_context`).
71    ///
72    /// Built-in tools are executed server-side by the model provider and should not be
73    /// executed locally by the agent. The default implementation returns `false`.
74    fn is_builtin(&self) -> bool {
75        false
76    }
77
78    /// Returns the JSON Schema for this tool's parameters, if any.
79    fn parameters_schema(&self) -> Option<Value> {
80        None
81    }
82    /// Returns the JSON Schema for this tool's response, if any.
83    fn response_schema(&self) -> Option<Value> {
84        None
85    }
86
87    /// Returns the scopes required to execute this tool.
88    ///
89    /// When non-empty, the framework can enforce that the calling user
90    /// possesses **all** listed scopes before dispatching `execute()`.
91    /// The default implementation returns an empty slice (no scopes required).
92    ///
93    /// # Example
94    ///
95    /// ```rust,ignore
96    /// fn required_scopes(&self) -> &[&str] {
97    ///     &["finance:write", "verified"]
98    /// }
99    /// ```
100    fn required_scopes(&self) -> &[&str] {
101        &[]
102    }
103
104    /// Indicates whether this tool performs no side effects.
105    /// Read-only tools may be executed concurrently in Auto mode.
106    fn is_read_only(&self) -> bool {
107        false
108    }
109
110    /// Indicates whether this tool is safe for concurrent execution.
111    /// Used by the Parallel strategy to validate dispatch safety.
112    fn is_concurrency_safe(&self) -> bool {
113        false
114    }
115
116    /// Executes the tool with the given context and arguments.
117    async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value>;
118}
119
120/// Context available to tools during execution.
121///
122/// Extends [`CallbackContext`] with tool-specific operations like accessing
123/// the function call ID, managing event actions, and searching memory.
124#[async_trait]
125pub trait ToolContext: CallbackContext {
126    /// Returns the function call ID for this tool invocation.
127    fn function_call_id(&self) -> &str;
128    /// Get the current event actions. Returns an owned copy for thread safety.
129    fn actions(&self) -> EventActions;
130    /// Set the event actions (e.g., to trigger escalation or skip summarization).
131    fn set_actions(&self, actions: EventActions);
132    /// Searches memory for entries matching the query.
133    async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>>;
134
135    /// Returns the scopes granted to the current user for this invocation.
136    ///
137    /// Implementations may resolve scopes from session state, JWT claims,
138    /// or an external identity provider. The default returns an empty set
139    /// (no scopes granted), which means scope-protected tools will be denied
140    /// unless the implementation is overridden.
141    fn user_scopes(&self) -> Vec<String> {
142        vec![]
143    }
144
145    /// Retrieve a secret by name from the configured secret provider.
146    ///
147    /// Returns `Ok(Some(value))` if a secret provider is configured and the
148    /// secret exists, `Ok(None)` if no secret provider is configured, or an
149    /// error if the provider fails.
150    ///
151    /// # Example
152    ///
153    /// ```rust,ignore
154    /// async fn use_secret(ctx: &dyn ToolContext) -> adk_core::Result<()> {
155    ///     if let Some(api_key) = ctx.get_secret("slack-bot-token").await? {
156    ///         // use the secret
157    ///     }
158    ///     Ok(())
159    /// }
160    /// ```
161    async fn get_secret(&self, _name: &str) -> Result<Option<String>> {
162        Ok(None)
163    }
164}
165
166/// Configuration for automatic tool retry on failure.
167///
168/// Controls how many times a failed tool execution is retried before
169/// propagating the error. Applied as a flat delay between attempts
170/// (no exponential backoff in V1).
171///
172/// # Example
173///
174/// ```rust
175/// use std::time::Duration;
176/// use adk_core::RetryBudget;
177///
178/// // Retry up to 2 times with 500ms between attempts (3 total attempts)
179/// let budget = RetryBudget::new(2, Duration::from_millis(500));
180/// assert_eq!(budget.max_retries, 2);
181/// ```
182#[derive(Debug, Clone)]
183pub struct RetryBudget {
184    /// Maximum number of retry attempts (not counting the initial attempt).
185    /// E.g., `max_retries: 2` means up to 3 total attempts.
186    pub max_retries: u32,
187    /// Delay between retries. Applied as a flat delay (no backoff in V1).
188    pub delay: std::time::Duration,
189}
190
191impl RetryBudget {
192    /// Create a new retry budget.
193    ///
194    /// # Arguments
195    ///
196    /// * `max_retries` - Maximum retry attempts (not counting the initial attempt)
197    /// * `delay` - Flat delay between retry attempts
198    pub fn new(max_retries: u32, delay: std::time::Duration) -> Self {
199        Self { max_retries, delay }
200    }
201}
202
203/// A collection of tools that can be resolved dynamically from context.
204#[async_trait]
205pub trait Toolset: Send + Sync {
206    /// Returns the name of this toolset.
207    fn name(&self) -> &str;
208    /// Returns the tools available in this toolset for the given context.
209    async fn tools(&self, ctx: Arc<dyn crate::ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>>;
210}
211
212/// Controls how multiple tool calls from a single LLM response are dispatched.
213#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
214pub enum ToolExecutionStrategy {
215    /// Execute tools one at a time in LLM-returned order. Default.
216    #[default]
217    Sequential,
218    /// Execute all tools concurrently via `join_all`.
219    Parallel,
220    /// Execute read-only tools concurrently, then mutable tools sequentially.
221    Auto,
222}
223
224/// Controls how the framework handles skills/agents that request unavailable tools.
225#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
226pub enum ValidationMode {
227    /// Reject the operation entirely if any requested tool is missing from the registry.
228    #[default]
229    Strict,
230    /// Bind available tools, omit missing ones, and log a warning.
231    Permissive,
232}
233
234/// A registry that maps tool names to concrete tool instances.
235///
236/// Implementations resolve string identifiers (e.g. from a skill or config)
237/// into executable `Arc<dyn Tool>` instances.
238pub trait ToolRegistry: Send + Sync {
239    /// Resolve a tool name to a concrete tool instance.
240    /// Returns `None` if the tool is not available in this registry.
241    fn resolve(&self, tool_name: &str) -> Option<Arc<dyn Tool>>;
242
243    /// Returns a list of all tool names available in this registry.
244    fn available_tools(&self) -> Vec<String> {
245        vec![]
246    }
247}
248
249/// A predicate function for filtering tools.
250pub type ToolPredicate = Box<dyn Fn(&dyn Tool) -> bool + Send + Sync>;
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255    use crate::{Content, EventActions, ReadonlyContext, RunConfig};
256    use std::sync::Mutex;
257
258    struct TestTool {
259        name: String,
260    }
261
262    #[allow(dead_code)]
263    struct TestContext {
264        content: Content,
265        config: RunConfig,
266        actions: Mutex<EventActions>,
267    }
268
269    impl TestContext {
270        fn new() -> Self {
271            Self {
272                content: Content::new("user"),
273                config: RunConfig::default(),
274                actions: Mutex::new(EventActions::default()),
275            }
276        }
277    }
278
279    #[async_trait]
280    impl ReadonlyContext for TestContext {
281        fn invocation_id(&self) -> &str {
282            "test"
283        }
284        fn agent_name(&self) -> &str {
285            "test"
286        }
287        fn user_id(&self) -> &str {
288            "user"
289        }
290        fn app_name(&self) -> &str {
291            "app"
292        }
293        fn session_id(&self) -> &str {
294            "session"
295        }
296        fn branch(&self) -> &str {
297            ""
298        }
299        fn user_content(&self) -> &Content {
300            &self.content
301        }
302    }
303
304    #[async_trait]
305    impl CallbackContext for TestContext {
306        fn artifacts(&self) -> Option<Arc<dyn crate::Artifacts>> {
307            None
308        }
309    }
310
311    #[async_trait]
312    impl ToolContext for TestContext {
313        fn function_call_id(&self) -> &str {
314            "call-123"
315        }
316        fn actions(&self) -> EventActions {
317            self.actions.lock().unwrap().clone()
318        }
319        fn set_actions(&self, actions: EventActions) {
320            *self.actions.lock().unwrap() = actions;
321        }
322        async fn search_memory(&self, _query: &str) -> Result<Vec<crate::MemoryEntry>> {
323            Ok(vec![])
324        }
325    }
326
327    #[async_trait]
328    impl Tool for TestTool {
329        fn name(&self) -> &str {
330            &self.name
331        }
332
333        fn description(&self) -> &str {
334            "test tool"
335        }
336
337        async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
338            Ok(Value::String("result".to_string()))
339        }
340    }
341
342    #[test]
343    fn test_tool_trait() {
344        let tool = TestTool { name: "test".to_string() };
345        assert_eq!(tool.name(), "test");
346        assert_eq!(tool.description(), "test tool");
347        assert!(!tool.is_long_running());
348    }
349
350    #[tokio::test]
351    async fn test_tool_execute() {
352        let tool = TestTool { name: "test".to_string() };
353        let ctx = Arc::new(TestContext::new()) as Arc<dyn ToolContext>;
354        let result = tool.execute(ctx, Value::Null).await.unwrap();
355        assert_eq!(result, Value::String("result".to_string()));
356    }
357}