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}