Skip to main content

adk_core/
tool.rs

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