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