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}