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}