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
131/// Configuration for automatic tool retry on failure.
132///
133/// Controls how many times a failed tool execution is retried before
134/// propagating the error. Applied as a flat delay between attempts
135/// (no exponential backoff in V1).
136///
137/// # Example
138///
139/// ```rust
140/// use std::time::Duration;
141/// use adk_core::RetryBudget;
142///
143/// // Retry up to 2 times with 500ms between attempts (3 total attempts)
144/// let budget = RetryBudget::new(2, Duration::from_millis(500));
145/// assert_eq!(budget.max_retries, 2);
146/// ```
147#[derive(Debug, Clone)]
148pub struct RetryBudget {
149 /// Maximum number of retry attempts (not counting the initial attempt).
150 /// E.g., `max_retries: 2` means up to 3 total attempts.
151 pub max_retries: u32,
152 /// Delay between retries. Applied as a flat delay (no backoff in V1).
153 pub delay: std::time::Duration,
154}
155
156impl RetryBudget {
157 /// Create a new retry budget.
158 ///
159 /// # Arguments
160 ///
161 /// * `max_retries` - Maximum retry attempts (not counting the initial attempt)
162 /// * `delay` - Flat delay between retry attempts
163 pub fn new(max_retries: u32, delay: std::time::Duration) -> Self {
164 Self { max_retries, delay }
165 }
166}
167
168#[async_trait]
169pub trait Toolset: Send + Sync {
170 fn name(&self) -> &str;
171 async fn tools(&self, ctx: Arc<dyn crate::ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>>;
172}
173
174/// Controls how multiple tool calls from a single LLM response are dispatched.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
176pub enum ToolExecutionStrategy {
177 /// Execute tools one at a time in LLM-returned order. Default.
178 #[default]
179 Sequential,
180 /// Execute all tools concurrently via `join_all`.
181 Parallel,
182 /// Execute read-only tools concurrently, then mutable tools sequentially.
183 Auto,
184}
185
186/// Controls how the framework handles skills/agents that request unavailable tools.
187#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
188pub enum ValidationMode {
189 /// Reject the operation entirely if any requested tool is missing from the registry.
190 #[default]
191 Strict,
192 /// Bind available tools, omit missing ones, and log a warning.
193 Permissive,
194}
195
196/// A registry that maps tool names to concrete tool instances.
197///
198/// Implementations resolve string identifiers (e.g. from a skill or config)
199/// into executable `Arc<dyn Tool>` instances.
200pub trait ToolRegistry: Send + Sync {
201 /// Resolve a tool name to a concrete tool instance.
202 /// Returns `None` if the tool is not available in this registry.
203 fn resolve(&self, tool_name: &str) -> Option<Arc<dyn Tool>>;
204
205 /// Returns a list of all tool names available in this registry.
206 fn available_tools(&self) -> Vec<String> {
207 vec![]
208 }
209}
210
211pub type ToolPredicate = Box<dyn Fn(&dyn Tool) -> bool + Send + Sync>;
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use crate::{Content, EventActions, ReadonlyContext, RunConfig};
217 use std::sync::Mutex;
218
219 struct TestTool {
220 name: String,
221 }
222
223 #[allow(dead_code)]
224 struct TestContext {
225 content: Content,
226 config: RunConfig,
227 actions: Mutex<EventActions>,
228 }
229
230 impl TestContext {
231 fn new() -> Self {
232 Self {
233 content: Content::new("user"),
234 config: RunConfig::default(),
235 actions: Mutex::new(EventActions::default()),
236 }
237 }
238 }
239
240 #[async_trait]
241 impl ReadonlyContext for TestContext {
242 fn invocation_id(&self) -> &str {
243 "test"
244 }
245 fn agent_name(&self) -> &str {
246 "test"
247 }
248 fn user_id(&self) -> &str {
249 "user"
250 }
251 fn app_name(&self) -> &str {
252 "app"
253 }
254 fn session_id(&self) -> &str {
255 "session"
256 }
257 fn branch(&self) -> &str {
258 ""
259 }
260 fn user_content(&self) -> &Content {
261 &self.content
262 }
263 }
264
265 #[async_trait]
266 impl CallbackContext for TestContext {
267 fn artifacts(&self) -> Option<Arc<dyn crate::Artifacts>> {
268 None
269 }
270 }
271
272 #[async_trait]
273 impl ToolContext for TestContext {
274 fn function_call_id(&self) -> &str {
275 "call-123"
276 }
277 fn actions(&self) -> EventActions {
278 self.actions.lock().unwrap().clone()
279 }
280 fn set_actions(&self, actions: EventActions) {
281 *self.actions.lock().unwrap() = actions;
282 }
283 async fn search_memory(&self, _query: &str) -> Result<Vec<crate::MemoryEntry>> {
284 Ok(vec![])
285 }
286 }
287
288 #[async_trait]
289 impl Tool for TestTool {
290 fn name(&self) -> &str {
291 &self.name
292 }
293
294 fn description(&self) -> &str {
295 "test tool"
296 }
297
298 async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
299 Ok(Value::String("result".to_string()))
300 }
301 }
302
303 #[test]
304 fn test_tool_trait() {
305 let tool = TestTool { name: "test".to_string() };
306 assert_eq!(tool.name(), "test");
307 assert_eq!(tool.description(), "test tool");
308 assert!(!tool.is_long_running());
309 }
310
311 #[tokio::test]
312 async fn test_tool_execute() {
313 let tool = TestTool { name: "test".to_string() };
314 let ctx = Arc::new(TestContext::new()) as Arc<dyn ToolContext>;
315 let result = tool.execute(ctx, Value::Null).await.unwrap();
316 assert_eq!(result, Value::String("result".to_string()));
317 }
318}