Skip to main content

adk_tool/toolset/
compose.rs

1use adk_core::{ReadonlyContext, Result, Tool, ToolContext, ToolPredicate, Toolset};
2use async_trait::async_trait;
3use serde_json::Value;
4use std::sync::Arc;
5
6/// A toolset wrapper that filters tools from an inner toolset using a predicate.
7///
8/// Works with any `Toolset` implementation. Tools that do not satisfy the
9/// predicate are excluded from the resolved tool list.
10///
11/// # Example
12///
13/// ```rust,ignore
14/// use adk_tool::toolset::{FilteredToolset, string_predicate};
15///
16/// let browser = BrowserToolset::new(session);
17/// let filtered = FilteredToolset::new(
18///     Arc::new(browser),
19///     string_predicate(vec!["navigate".into(), "click".into()]),
20/// );
21/// // Only "navigate" and "click" tools will be exposed
22/// ```
23pub struct FilteredToolset {
24    inner: Arc<dyn Toolset>,
25    predicate: ToolPredicate,
26    name: String,
27}
28
29impl FilteredToolset {
30    /// Wrap `inner` and keep only tools that satisfy `predicate`.
31    pub fn new(inner: Arc<dyn Toolset>, predicate: ToolPredicate) -> Self {
32        let name = format!("{}_filtered", inner.name());
33        Self { inner, predicate, name }
34    }
35
36    /// Wrap `inner` with a custom name and keep only tools that satisfy `predicate`.
37    pub fn with_name(
38        inner: Arc<dyn Toolset>,
39        predicate: ToolPredicate,
40        name: impl Into<String>,
41    ) -> Self {
42        Self { inner, predicate, name: name.into() }
43    }
44}
45
46#[async_trait]
47impl Toolset for FilteredToolset {
48    fn name(&self) -> &str {
49        &self.name
50    }
51
52    async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>> {
53        let all = self.inner.tools(ctx).await?;
54        Ok(all.into_iter().filter(|t| (self.predicate)(t.as_ref())).collect())
55    }
56}
57
58/// A toolset that merges tools from multiple inner toolsets into one.
59///
60/// Toolsets are resolved in registration order. If two inner toolsets
61/// provide a tool with the same name, the first one wins (last-registered
62/// duplicates are dropped with a `tracing::warn`).
63///
64/// # Example
65///
66/// ```rust,ignore
67/// use adk_tool::toolset::MergedToolset;
68///
69/// let merged = MergedToolset::new("all_tools", vec![
70///     Arc::new(browser_toolset),
71///     Arc::new(search_toolset),
72/// ]);
73/// ```
74pub struct MergedToolset {
75    name: String,
76    inner: Vec<Arc<dyn Toolset>>,
77}
78
79impl MergedToolset {
80    /// Create a merged toolset from multiple inner toolsets.
81    pub fn new(name: impl Into<String>, toolsets: Vec<Arc<dyn Toolset>>) -> Self {
82        Self { name: name.into(), inner: toolsets }
83    }
84
85    /// Append another toolset to the merge list.
86    pub fn with_toolset(mut self, toolset: Arc<dyn Toolset>) -> Self {
87        self.inner.push(toolset);
88        self
89    }
90}
91
92#[async_trait]
93impl Toolset for MergedToolset {
94    fn name(&self) -> &str {
95        &self.name
96    }
97
98    async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>> {
99        let mut seen = std::collections::HashSet::new();
100        let mut merged = Vec::new();
101
102        for toolset in &self.inner {
103            let tools = toolset.tools(ctx.clone()).await?;
104            for tool in tools {
105                let tool_name = tool.name().to_string();
106                if seen.contains(&tool_name) {
107                    tracing::warn!(
108                        tool.name = %tool_name,
109                        toolset.name = %toolset.name(),
110                        merged_toolset.name = %self.name,
111                        "duplicate tool name in MergedToolset, skipping"
112                    );
113                    continue;
114                }
115                seen.insert(tool_name);
116                merged.push(tool);
117            }
118        }
119
120        Ok(merged)
121    }
122}
123
124/// A toolset wrapper that prefixes all tool names from an inner toolset.
125///
126/// Useful for namespacing tools when composing multiple toolsets that
127/// might have overlapping tool names.
128///
129/// # Example
130///
131/// ```rust,ignore
132/// use adk_tool::toolset::PrefixedToolset;
133///
134/// let browser = BrowserToolset::new(session);
135/// let prefixed = PrefixedToolset::new(Arc::new(browser), "browser");
136/// // Tools become "browser_navigate", "browser_click", etc.
137/// ```
138pub struct PrefixedToolset {
139    inner: Arc<dyn Toolset>,
140    prefix: String,
141    name: String,
142}
143
144impl PrefixedToolset {
145    /// Wrap `inner` and prefix all tool names with `prefix_`.
146    pub fn new(inner: Arc<dyn Toolset>, prefix: impl Into<String>) -> Self {
147        let prefix = prefix.into();
148        let name = format!("{}_{}", prefix, inner.name());
149        Self { inner, prefix, name }
150    }
151}
152
153#[async_trait]
154impl Toolset for PrefixedToolset {
155    fn name(&self) -> &str {
156        &self.name
157    }
158
159    async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>> {
160        let tools = self.inner.tools(ctx).await?;
161        Ok(tools
162            .into_iter()
163            .map(|t| -> Arc<dyn Tool> { Arc::new(PrefixedTool::new(t, &self.prefix)) })
164            .collect())
165    }
166}
167
168/// Internal wrapper that presents a tool under a prefixed name.
169struct PrefixedTool {
170    inner: Arc<dyn Tool>,
171    prefixed_name: String,
172    prefixed_description: String,
173}
174
175impl PrefixedTool {
176    fn new(inner: Arc<dyn Tool>, prefix: &str) -> Self {
177        let prefixed_name = format!("{prefix}_{}", inner.name());
178        let prefixed_description = inner.description().to_string();
179        Self { inner, prefixed_name, prefixed_description }
180    }
181}
182
183#[async_trait]
184impl Tool for PrefixedTool {
185    fn name(&self) -> &str {
186        &self.prefixed_name
187    }
188
189    fn description(&self) -> &str {
190        &self.prefixed_description
191    }
192
193    fn enhanced_description(&self) -> String {
194        self.inner.enhanced_description()
195    }
196
197    fn is_long_running(&self) -> bool {
198        self.inner.is_long_running()
199    }
200
201    fn parameters_schema(&self) -> Option<Value> {
202        self.inner.parameters_schema()
203    }
204
205    fn response_schema(&self) -> Option<Value> {
206        self.inner.response_schema()
207    }
208
209    fn required_scopes(&self) -> &[&str] {
210        self.inner.required_scopes()
211    }
212
213    async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
214        self.inner.execute(ctx, args).await
215    }
216}