Skip to main content

adk_core/
tool.rs

1use crate::{
2    CallbackContext, Event, EventActions, Memory, MemoryEntry, Result, RunConfig, Session,
3};
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::sync::Arc;
8
9/// The core trait for all tools that agents can invoke.
10///
11/// Tools extend agent capabilities with custom functions. Each tool has a name,
12/// description, optional parameter schema, and an async `execute` method.
13#[async_trait]
14pub trait Tool: Send + Sync {
15    /// Returns the unique name of this tool.
16    fn name(&self) -> &str;
17    /// Returns a human-readable description of what this tool does.
18    fn description(&self) -> &str;
19
20    /// Returns the tool declaration that should be exposed to model providers.
21    ///
22    /// The default implementation produces the standard ADK function-tool
23    /// declaration (`name`, `description`, optional `parameters`, optional
24    /// `response`). Provider-specific built-in tools may override this to attach
25    /// additional metadata that the provider adapters understand.
26    ///
27    /// # Example
28    ///
29    /// ```rust,ignore
30    /// fn declaration(&self) -> serde_json::Value {
31    ///     serde_json::json!({
32    ///         "name": self.name(),
33    ///         "description": self.description(),
34    ///         "x-adk-openai-tool": {
35    ///             "type": "web_search_2025_08_26"
36    ///         }
37    ///     })
38    /// }
39    /// ```
40    fn declaration(&self) -> Value {
41        let mut decl = serde_json::json!({
42            "name": self.name(),
43            "description": self.enhanced_description(),
44        });
45
46        if let Some(params) = self.parameters_schema() {
47            decl["parameters"] = params;
48        }
49
50        if let Some(response) = self.response_schema() {
51            decl["response"] = response;
52        }
53
54        decl
55    }
56
57    /// Returns an enhanced description that may include additional notes.
58    /// For long-running tools, this includes a warning not to call the tool
59    /// again if it has already returned a pending status.
60    /// Default implementation returns the base description.
61    fn enhanced_description(&self) -> String {
62        self.description().to_string()
63    }
64
65    /// Indicates whether the tool is a long-running operation.
66    /// Long-running tools typically return a task ID immediately and
67    /// complete the operation asynchronously.
68    fn is_long_running(&self) -> bool {
69        false
70    }
71
72    /// Indicates whether this tool is a built-in server-side tool (e.g., `google_search`, `url_context`).
73    ///
74    /// Built-in tools are executed server-side by the model provider and should not be
75    /// executed locally by the agent. The default implementation returns `false`.
76    fn is_builtin(&self) -> bool {
77        false
78    }
79
80    /// Returns the JSON Schema for this tool's parameters, if any.
81    fn parameters_schema(&self) -> Option<Value> {
82        None
83    }
84    /// Returns the JSON Schema for this tool's response, if any.
85    fn response_schema(&self) -> Option<Value> {
86        None
87    }
88
89    /// Returns the scopes required to execute this tool.
90    ///
91    /// When non-empty, the framework can enforce that the calling user
92    /// possesses **all** listed scopes before dispatching `execute()`.
93    /// The default implementation returns an empty slice (no scopes required).
94    ///
95    /// # Example
96    ///
97    /// ```rust,ignore
98    /// fn required_scopes(&self) -> &[&str] {
99    ///     &["finance:write", "verified"]
100    /// }
101    /// ```
102    fn required_scopes(&self) -> &[&str] {
103        &[]
104    }
105
106    /// Indicates whether this tool performs no side effects.
107    ///
108    /// [`ToolExecutionStrategy::Auto`] includes a call in its concurrent subset
109    /// only when the selected tool is both read-only and concurrency-safe.
110    fn is_read_only(&self) -> bool {
111        false
112    }
113
114    /// Indicates whether this tool is safe for concurrent execution.
115    ///
116    /// [`ToolExecutionStrategy::Auto`] requires this signal in addition to
117    /// [`Tool::is_read_only`]. [`ToolExecutionStrategy::Parallel`] is an
118    /// explicit caller override and does not inspect either signal.
119    fn is_concurrency_safe(&self) -> bool {
120        false
121    }
122
123    /// Executes the tool with the given context and arguments.
124    async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value>;
125}
126
127/// Context available to tools during execution.
128///
129/// Extends [`CallbackContext`] with tool-specific operations like accessing
130/// the function call ID, managing event actions, and searching memory.
131#[async_trait]
132pub trait ToolContext: CallbackContext {
133    /// Returns the function call ID for this tool invocation.
134    fn function_call_id(&self) -> &str;
135    /// Get the current event actions. Returns an owned copy for thread safety.
136    fn actions(&self) -> EventActions;
137    /// Set the event actions (e.g., to trigger escalation or skip summarization).
138    fn set_actions(&self, actions: EventActions);
139    /// Searches memory for entries matching the query.
140    async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>>;
141
142    /// Returns the memory service backing the parent invocation, when exposed.
143    ///
144    /// The default keeps contexts written before this capability backward
145    /// compatible. Agent-as-tool adapters use it only when memory forwarding is
146    /// explicitly enabled.
147    fn memory(&self) -> Option<Arc<dyn Memory>> {
148        None
149    }
150
151    /// Returns the parent session, when exposed by the runtime tool context.
152    ///
153    /// Agent-as-tool adapters can snapshot its history and state for a child
154    /// invocation without sharing mutable session ownership.
155    fn session(&self) -> Option<&dyn Session> {
156        None
157    }
158
159    /// Returns the parent run configuration, when exposed by the runtime.
160    fn run_config(&self) -> Option<&RunConfig> {
161        None
162    }
163
164    /// Returns whether the parent invocation has been cancelled.
165    fn is_cancelled(&self) -> bool {
166        false
167    }
168
169    /// Returns authenticated request metadata inherited from the parent run.
170    fn request_metadata(&self) -> std::collections::HashMap<String, Value> {
171        std::collections::HashMap::new()
172    }
173
174    /// Returns the current nested agent-as-tool delegation depth.
175    fn delegation_depth(&self) -> u32 {
176        0
177    }
178
179    /// Returns the maximum nested agent-as-tool delegation depth.
180    fn max_delegation_depth(&self) -> Option<u32> {
181        None
182    }
183
184    /// Returns the root invocation that owns this orchestration tree.
185    fn orchestration_root_invocation_id(&self) -> &str {
186        self.invocation_id()
187    }
188
189    /// Returns the causal relationship execution containing this tool call.
190    fn orchestration_edge_id(&self) -> Option<&str> {
191        None
192    }
193
194    /// Emits a nested agent event through the parent agent's event stream.
195    ///
196    /// The default is a no-op. AgentTool uses this only when event forwarding is
197    /// explicitly enabled, preserving existing callers' output behavior.
198    async fn emit_event(&self, _event: Event) {}
199
200    /// Emit streaming progress output during long-running tool execution.
201    ///
202    /// Tools call this to push intermediate stdout/stderr to the UI layer
203    /// as it arrives, rather than waiting for the tool to finish. This enables
204    /// streaming terminal output for shell commands, build logs, etc.
205    ///
206    /// # Arguments
207    ///
208    /// * `stream` - The output stream: `"stdout"`, `"stderr"`, or a custom label
209    /// * `chunk` - The text chunk to emit
210    ///
211    /// # Example
212    ///
213    /// ```rust,ignore
214    /// // Inside a tool's execute() method:
215    /// ctx.emit_progress("stdout", "Compiling project...\n").await;
216    /// ctx.emit_progress("stdout", "Build successful!\n").await;
217    /// ctx.emit_progress("stderr", "warning: unused variable\n").await;
218    /// ```
219    ///
220    /// The default implementation is a no-op. Runners and UI layers that support
221    /// streaming output override this to forward chunks to the client.
222    async fn emit_progress(&self, _stream: &str, _chunk: &str) {
223        // Default: discard. Override in runners that support streaming tool output.
224    }
225
226    /// Returns the scopes granted to the current user for this invocation.
227    ///
228    /// Implementations may resolve scopes from session state, JWT claims,
229    /// or an external identity provider. The default returns an empty set
230    /// (no scopes granted), which means scope-protected tools will be denied
231    /// unless the implementation is overridden.
232    fn user_scopes(&self) -> Vec<String> {
233        vec![]
234    }
235
236    /// Retrieve a secret by name from the configured secret provider.
237    ///
238    /// Returns `Ok(Some(value))` if a secret provider is configured and the
239    /// secret exists, `Ok(None)` if no secret provider is configured, or an
240    /// error if the provider fails.
241    ///
242    /// # Example
243    ///
244    /// ```rust,ignore
245    /// async fn use_secret(ctx: &dyn ToolContext) -> adk_core::Result<()> {
246    ///     if let Some(api_key) = ctx.get_secret("slack-bot-token").await? {
247    ///         // use the secret
248    ///     }
249    ///     Ok(())
250    /// }
251    /// ```
252    async fn get_secret(&self, _name: &str) -> Result<Option<String>> {
253        Ok(None)
254    }
255
256    /// Resolves a secret, stating why it is needed.
257    ///
258    /// The tool identity is added by the framework, not taken from the tool, so a
259    /// purpose is the only part a tool contributes. An authorizing
260    /// [`SecretService`](crate::SecretService) sees both.
261    async fn get_secret_for_purpose(&self, name: &str, purpose: &str) -> Result<Option<String>> {
262        let _ = purpose;
263        self.get_secret(name).await
264    }
265}
266
267/// Configuration for automatic tool retry on failure.
268///
269/// Controls how many times a failed tool execution is retried before
270/// propagating the error. Applied as a flat delay between attempts
271/// (no exponential backoff in V1).
272///
273/// # Example
274///
275/// ```rust
276/// use std::time::Duration;
277/// use adk_core::RetryBudget;
278///
279/// // Retry up to 2 times with 500ms between attempts (3 total attempts)
280/// let budget = RetryBudget::new(2, Duration::from_millis(500));
281/// assert_eq!(budget.max_retries, 2);
282/// ```
283#[derive(Debug, Clone)]
284pub struct RetryBudget {
285    /// Maximum number of retry attempts (not counting the initial attempt).
286    /// E.g., `max_retries: 2` means up to 3 total attempts.
287    pub max_retries: u32,
288    /// Delay between retries. Applied as a flat delay (no backoff in V1).
289    pub delay: std::time::Duration,
290}
291
292impl RetryBudget {
293    /// Create a new retry budget.
294    ///
295    /// # Arguments
296    ///
297    /// * `max_retries` - Maximum retry attempts (not counting the initial attempt)
298    /// * `delay` - Flat delay between retry attempts
299    pub fn new(max_retries: u32, delay: std::time::Duration) -> Self {
300        Self { max_retries, delay }
301    }
302}
303
304/// A collection of tools that can be resolved dynamically from context.
305#[async_trait]
306pub trait Toolset: Send + Sync {
307    /// Returns the name of this toolset.
308    fn name(&self) -> &str;
309    /// Returns the tools available in this toolset for the given context.
310    async fn tools(&self, ctx: Arc<dyn crate::ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>>;
311}
312
313/// Controls how multiple tool calls from a single LLM response are dispatched.
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
315pub enum ToolExecutionStrategy {
316    /// Execute tools one at a time in LLM-returned order. Default.
317    #[default]
318    Sequential,
319    /// Execute all tools concurrently without inspecting tool metadata.
320    ///
321    /// This is an explicit caller override. The caller is responsible for
322    /// ensuring every selected tool is safe to execute concurrently.
323    Parallel,
324    /// Execute calls whose tools report both read-only and concurrency-safe
325    /// concurrently, then execute all remaining calls sequentially.
326    Auto,
327}
328
329/// Controls how the framework handles skills/agents that request unavailable tools.
330#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
331pub enum ValidationMode {
332    /// Reject the operation entirely if any requested tool is missing from the registry.
333    #[default]
334    Strict,
335    /// Bind available tools, omit missing ones, and log a warning.
336    Permissive,
337}
338
339/// A registry that maps tool names to concrete tool instances.
340///
341/// Implementations resolve string identifiers (e.g. from a skill or config)
342/// into executable `Arc<dyn Tool>` instances.
343pub trait ToolRegistry: Send + Sync {
344    /// Resolve a tool name to a concrete tool instance.
345    /// Returns `None` if the tool is not available in this registry.
346    fn resolve(&self, tool_name: &str) -> Option<Arc<dyn Tool>>;
347
348    /// Returns a list of all tool names available in this registry.
349    fn available_tools(&self) -> Vec<String> {
350        vec![]
351    }
352}
353
354/// A predicate function for filtering tools.
355pub type ToolPredicate = Box<dyn Fn(&dyn Tool) -> bool + Send + Sync>;
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360    use crate::{Content, EventActions, ReadonlyContext, RunConfig};
361    use std::sync::Mutex;
362
363    struct TestTool {
364        name: String,
365    }
366
367    #[allow(dead_code)]
368    struct TestContext {
369        content: Content,
370        config: RunConfig,
371        actions: Mutex<EventActions>,
372    }
373
374    impl TestContext {
375        fn new() -> Self {
376            Self {
377                content: Content::new("user"),
378                config: RunConfig::default(),
379                actions: Mutex::new(EventActions::default()),
380            }
381        }
382    }
383
384    #[async_trait]
385    impl ReadonlyContext for TestContext {
386        fn invocation_id(&self) -> &str {
387            "test"
388        }
389        fn agent_name(&self) -> &str {
390            "test"
391        }
392        fn user_id(&self) -> &str {
393            "user"
394        }
395        fn app_name(&self) -> &str {
396            "app"
397        }
398        fn session_id(&self) -> &str {
399            "session"
400        }
401        fn branch(&self) -> &str {
402            ""
403        }
404        fn user_content(&self) -> &Content {
405            &self.content
406        }
407    }
408
409    #[async_trait]
410    impl CallbackContext for TestContext {
411        fn artifacts(&self) -> Option<Arc<dyn crate::Artifacts>> {
412            None
413        }
414    }
415
416    #[async_trait]
417    impl ToolContext for TestContext {
418        fn function_call_id(&self) -> &str {
419            "call-123"
420        }
421        fn actions(&self) -> EventActions {
422            self.actions.lock().unwrap().clone()
423        }
424        fn set_actions(&self, actions: EventActions) {
425            *self.actions.lock().unwrap() = actions;
426        }
427        async fn search_memory(&self, _query: &str) -> Result<Vec<crate::MemoryEntry>> {
428            Ok(vec![])
429        }
430    }
431
432    #[async_trait]
433    impl Tool for TestTool {
434        fn name(&self) -> &str {
435            &self.name
436        }
437
438        fn description(&self) -> &str {
439            "test tool"
440        }
441
442        async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
443            Ok(Value::String("result".to_string()))
444        }
445    }
446
447    #[test]
448    fn test_tool_trait() {
449        let tool = TestTool { name: "test".to_string() };
450        assert_eq!(tool.name(), "test");
451        assert_eq!(tool.description(), "test tool");
452        assert!(!tool.is_long_running());
453    }
454
455    #[tokio::test]
456    async fn test_tool_execute() {
457        let tool = TestTool { name: "test".to_string() };
458        let ctx = Arc::new(TestContext::new()) as Arc<dyn ToolContext>;
459        let result = tool.execute(ctx, Value::Null).await.unwrap();
460        assert_eq!(result, Value::String("result".to_string()));
461    }
462}