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    ///
106    /// [`ToolExecutionStrategy::Auto`] includes a call in its concurrent subset
107    /// only when the selected tool is both read-only and concurrency-safe.
108    fn is_read_only(&self) -> bool {
109        false
110    }
111
112    /// Indicates whether this tool is safe for concurrent execution.
113    ///
114    /// [`ToolExecutionStrategy::Auto`] requires this signal in addition to
115    /// [`Tool::is_read_only`]. [`ToolExecutionStrategy::Parallel`] is an
116    /// explicit caller override and does not inspect either signal.
117    fn is_concurrency_safe(&self) -> bool {
118        false
119    }
120
121    /// Executes the tool with the given context and arguments.
122    async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value>;
123}
124
125/// Context available to tools during execution.
126///
127/// Extends [`CallbackContext`] with tool-specific operations like accessing
128/// the function call ID, managing event actions, and searching memory.
129#[async_trait]
130pub trait ToolContext: CallbackContext {
131    /// Returns the function call ID for this tool invocation.
132    fn function_call_id(&self) -> &str;
133    /// Get the current event actions. Returns an owned copy for thread safety.
134    fn actions(&self) -> EventActions;
135    /// Set the event actions (e.g., to trigger escalation or skip summarization).
136    fn set_actions(&self, actions: EventActions);
137    /// Searches memory for entries matching the query.
138    async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>>;
139
140    /// Emit streaming progress output during long-running tool execution.
141    ///
142    /// Tools call this to push intermediate stdout/stderr to the UI layer
143    /// as it arrives, rather than waiting for the tool to finish. This enables
144    /// streaming terminal output for shell commands, build logs, etc.
145    ///
146    /// # Arguments
147    ///
148    /// * `stream` - The output stream: `"stdout"`, `"stderr"`, or a custom label
149    /// * `chunk` - The text chunk to emit
150    ///
151    /// # Example
152    ///
153    /// ```rust,ignore
154    /// // Inside a tool's execute() method:
155    /// ctx.emit_progress("stdout", "Compiling project...\n").await;
156    /// ctx.emit_progress("stdout", "Build successful!\n").await;
157    /// ctx.emit_progress("stderr", "warning: unused variable\n").await;
158    /// ```
159    ///
160    /// The default implementation is a no-op. Runners and UI layers that support
161    /// streaming output override this to forward chunks to the client.
162    async fn emit_progress(&self, _stream: &str, _chunk: &str) {
163        // Default: discard. Override in runners that support streaming tool output.
164    }
165
166    /// Returns the scopes granted to the current user for this invocation.
167    ///
168    /// Implementations may resolve scopes from session state, JWT claims,
169    /// or an external identity provider. The default returns an empty set
170    /// (no scopes granted), which means scope-protected tools will be denied
171    /// unless the implementation is overridden.
172    fn user_scopes(&self) -> Vec<String> {
173        vec![]
174    }
175
176    /// Retrieve a secret by name from the configured secret provider.
177    ///
178    /// Returns `Ok(Some(value))` if a secret provider is configured and the
179    /// secret exists, `Ok(None)` if no secret provider is configured, or an
180    /// error if the provider fails.
181    ///
182    /// # Example
183    ///
184    /// ```rust,ignore
185    /// async fn use_secret(ctx: &dyn ToolContext) -> adk_core::Result<()> {
186    ///     if let Some(api_key) = ctx.get_secret("slack-bot-token").await? {
187    ///         // use the secret
188    ///     }
189    ///     Ok(())
190    /// }
191    /// ```
192    async fn get_secret(&self, _name: &str) -> Result<Option<String>> {
193        Ok(None)
194    }
195
196    /// Resolves a secret, stating why it is needed.
197    ///
198    /// The tool identity is added by the framework, not taken from the tool, so a
199    /// purpose is the only part a tool contributes. An authorizing
200    /// [`SecretService`](crate::SecretService) sees both.
201    async fn get_secret_for_purpose(&self, name: &str, purpose: &str) -> Result<Option<String>> {
202        let _ = purpose;
203        self.get_secret(name).await
204    }
205}
206
207/// Configuration for automatic tool retry on failure.
208///
209/// Controls how many times a failed tool execution is retried before
210/// propagating the error. Applied as a flat delay between attempts
211/// (no exponential backoff in V1).
212///
213/// # Example
214///
215/// ```rust
216/// use std::time::Duration;
217/// use adk_core::RetryBudget;
218///
219/// // Retry up to 2 times with 500ms between attempts (3 total attempts)
220/// let budget = RetryBudget::new(2, Duration::from_millis(500));
221/// assert_eq!(budget.max_retries, 2);
222/// ```
223#[derive(Debug, Clone)]
224pub struct RetryBudget {
225    /// Maximum number of retry attempts (not counting the initial attempt).
226    /// E.g., `max_retries: 2` means up to 3 total attempts.
227    pub max_retries: u32,
228    /// Delay between retries. Applied as a flat delay (no backoff in V1).
229    pub delay: std::time::Duration,
230}
231
232impl RetryBudget {
233    /// Create a new retry budget.
234    ///
235    /// # Arguments
236    ///
237    /// * `max_retries` - Maximum retry attempts (not counting the initial attempt)
238    /// * `delay` - Flat delay between retry attempts
239    pub fn new(max_retries: u32, delay: std::time::Duration) -> Self {
240        Self { max_retries, delay }
241    }
242}
243
244/// A collection of tools that can be resolved dynamically from context.
245#[async_trait]
246pub trait Toolset: Send + Sync {
247    /// Returns the name of this toolset.
248    fn name(&self) -> &str;
249    /// Returns the tools available in this toolset for the given context.
250    async fn tools(&self, ctx: Arc<dyn crate::ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>>;
251}
252
253/// Controls how multiple tool calls from a single LLM response are dispatched.
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
255pub enum ToolExecutionStrategy {
256    /// Execute tools one at a time in LLM-returned order. Default.
257    #[default]
258    Sequential,
259    /// Execute all tools concurrently without inspecting tool metadata.
260    ///
261    /// This is an explicit caller override. The caller is responsible for
262    /// ensuring every selected tool is safe to execute concurrently.
263    Parallel,
264    /// Execute calls whose tools report both read-only and concurrency-safe
265    /// concurrently, then execute all remaining calls sequentially.
266    Auto,
267}
268
269/// Controls how the framework handles skills/agents that request unavailable tools.
270#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
271pub enum ValidationMode {
272    /// Reject the operation entirely if any requested tool is missing from the registry.
273    #[default]
274    Strict,
275    /// Bind available tools, omit missing ones, and log a warning.
276    Permissive,
277}
278
279/// A registry that maps tool names to concrete tool instances.
280///
281/// Implementations resolve string identifiers (e.g. from a skill or config)
282/// into executable `Arc<dyn Tool>` instances.
283pub trait ToolRegistry: Send + Sync {
284    /// Resolve a tool name to a concrete tool instance.
285    /// Returns `None` if the tool is not available in this registry.
286    fn resolve(&self, tool_name: &str) -> Option<Arc<dyn Tool>>;
287
288    /// Returns a list of all tool names available in this registry.
289    fn available_tools(&self) -> Vec<String> {
290        vec![]
291    }
292}
293
294/// A predicate function for filtering tools.
295pub type ToolPredicate = Box<dyn Fn(&dyn Tool) -> bool + Send + Sync>;
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use crate::{Content, EventActions, ReadonlyContext, RunConfig};
301    use std::sync::Mutex;
302
303    struct TestTool {
304        name: String,
305    }
306
307    #[allow(dead_code)]
308    struct TestContext {
309        content: Content,
310        config: RunConfig,
311        actions: Mutex<EventActions>,
312    }
313
314    impl TestContext {
315        fn new() -> Self {
316            Self {
317                content: Content::new("user"),
318                config: RunConfig::default(),
319                actions: Mutex::new(EventActions::default()),
320            }
321        }
322    }
323
324    #[async_trait]
325    impl ReadonlyContext for TestContext {
326        fn invocation_id(&self) -> &str {
327            "test"
328        }
329        fn agent_name(&self) -> &str {
330            "test"
331        }
332        fn user_id(&self) -> &str {
333            "user"
334        }
335        fn app_name(&self) -> &str {
336            "app"
337        }
338        fn session_id(&self) -> &str {
339            "session"
340        }
341        fn branch(&self) -> &str {
342            ""
343        }
344        fn user_content(&self) -> &Content {
345            &self.content
346        }
347    }
348
349    #[async_trait]
350    impl CallbackContext for TestContext {
351        fn artifacts(&self) -> Option<Arc<dyn crate::Artifacts>> {
352            None
353        }
354    }
355
356    #[async_trait]
357    impl ToolContext for TestContext {
358        fn function_call_id(&self) -> &str {
359            "call-123"
360        }
361        fn actions(&self) -> EventActions {
362            self.actions.lock().unwrap().clone()
363        }
364        fn set_actions(&self, actions: EventActions) {
365            *self.actions.lock().unwrap() = actions;
366        }
367        async fn search_memory(&self, _query: &str) -> Result<Vec<crate::MemoryEntry>> {
368            Ok(vec![])
369        }
370    }
371
372    #[async_trait]
373    impl Tool for TestTool {
374        fn name(&self) -> &str {
375            &self.name
376        }
377
378        fn description(&self) -> &str {
379            "test tool"
380        }
381
382        async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
383            Ok(Value::String("result".to_string()))
384        }
385    }
386
387    #[test]
388    fn test_tool_trait() {
389        let tool = TestTool { name: "test".to_string() };
390        assert_eq!(tool.name(), "test");
391        assert_eq!(tool.description(), "test tool");
392        assert!(!tool.is_long_running());
393    }
394
395    #[tokio::test]
396    async fn test_tool_execute() {
397        let tool = TestTool { name: "test".to_string() };
398        let ctx = Arc::new(TestContext::new()) as Arc<dyn ToolContext>;
399        let result = tool.execute(ctx, Value::Null).await.unwrap();
400        assert_eq!(result, Value::String("result".to_string()));
401    }
402}