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}