Skip to main content

agent_sdk/
builtin_tools.rs

1//! Bundled registration for the full set of built-in SDK tools.
2//!
3//! The SDK ships many ready-to-use tools (primitives, todo tracking,
4//! web search and fetch, user-interaction).  Hosts that want to run
5//! these through the v2 durable tool runtime need them registered in a
6//! shared [`ToolRegistry<Ctx>`] so `agent-service-host`'s
7//! `RegistryToolExecutor` can dispatch them by name.
8//!
9//! This module centralises the registration plumbing so hosts don't
10//! have to know the constructor shape of every tool family.  Each
11//! family is exposed as its own focused helper and a top-level
12//! [`register_builtin_tools`] wires the common bundle.
13//!
14//! # Example
15//!
16//! ```no_run
17//! use std::sync::Arc;
18//! use tokio::sync::RwLock;
19//! use agent_sdk::builtin_tools::{register_builtin_tools, BuiltinToolsConfig};
20//! use agent_sdk::todo::TodoState;
21//! use agent_sdk::{AgentCapabilities, InMemoryFileSystem, ToolRegistry};
22//!
23//! let fs = Arc::new(InMemoryFileSystem::new("/workspace"));
24//! let mut registry = ToolRegistry::<()>::new();
25//! register_builtin_tools(&mut registry, BuiltinToolsConfig {
26//!     environment: fs,
27//!     capabilities: AgentCapabilities::full_access(),
28//!     todo_state: Some(Arc::new(RwLock::new(TodoState::new()))),
29//!     // `link_fetch` only exists when the `web` feature is enabled, so the
30//!     // field is gated to keep this example compiling under default features
31//!     // (`cargo test --doc -p agent-sdk`).
32//!     #[cfg(feature = "web")]
33//!     link_fetch: true,
34//! });
35//! ```
36
37use std::sync::Arc;
38use tokio::sync::RwLock;
39
40use agent_sdk_tools::tools::ToolRegistry;
41
42use crate::primitive_tools::{BashTool, EditTool, GlobTool, GrepTool, ReadTool, WriteTool};
43use crate::todo::{TodoReadTool, TodoState, TodoWriteTool};
44#[cfg(feature = "web")]
45use crate::web::{LinkFetchTool, SearchProvider, WebSearchTool};
46use crate::{AgentCapabilities, Environment};
47
48/// Configuration for [`register_builtin_tools`].
49///
50/// Every field except `environment` and `capabilities` is optional
51/// because the corresponding tool family has side effects or
52/// dependencies that must be provisioned by the host.
53///
54/// The tool families with more complex constructors — in particular
55/// [`AskUserQuestionTool`](crate::user_interaction::AskUserQuestionTool)
56/// (requires a pair of mpsc channels) and
57/// [`WebSearchTool`] (requires a typed
58/// [`SearchProvider`]) — are **not** wired through this config to
59/// avoid over-coupling it to transport or provider choices.  Register
60/// them with [`register_web_search`] or directly against the registry.
61pub struct BuiltinToolsConfig<E: Environment + 'static> {
62    /// Filesystem environment used by the primitive tools.
63    pub environment: Arc<E>,
64    /// Capability policy enforced by the primitive tools.
65    pub capabilities: AgentCapabilities,
66    /// Shared todo state.  When `Some`, registers `TodoRead` / `TodoWrite`.
67    pub todo_state: Option<Arc<RwLock<TodoState>>>,
68    /// When `true`, registers [`LinkFetchTool`] with default settings.
69    ///
70    /// Only present when the `web` feature is enabled.
71    #[cfg(feature = "web")]
72    pub link_fetch: bool,
73}
74
75/// Register the full bundle of built-in tools described by `config`.
76///
77/// This is a convenience wrapper that delegates to the per-family
78/// helpers ([`register_primitives`], [`register_todo_tools`],
79/// [`register_link_fetch`]).  Hosts that need finer control can call
80/// the helpers directly.
81pub fn register_builtin_tools<Ctx, E>(
82    registry: &mut ToolRegistry<Ctx>,
83    config: BuiltinToolsConfig<E>,
84) where
85    Ctx: Send + Sync + 'static,
86    E: Environment + 'static,
87{
88    register_primitives(registry, config.environment, config.capabilities);
89
90    if let Some(state) = config.todo_state {
91        register_todo_tools(registry, state);
92    }
93
94    #[cfg(feature = "web")]
95    if config.link_fetch {
96        register_link_fetch(registry);
97    }
98}
99
100/// Register the six filesystem / shell primitives
101/// (`Read`, `Write`, `Edit`, `Bash`, `Glob`, `Grep`).
102pub fn register_primitives<Ctx, E>(
103    registry: &mut ToolRegistry<Ctx>,
104    environment: Arc<E>,
105    capabilities: AgentCapabilities,
106) where
107    Ctx: Send + Sync + 'static,
108    E: Environment + 'static,
109{
110    registry.register(ReadTool::new(
111        Arc::clone(&environment),
112        capabilities.clone(),
113    ));
114    registry.register(WriteTool::new(
115        Arc::clone(&environment),
116        capabilities.clone(),
117    ));
118    registry.register(EditTool::new(
119        Arc::clone(&environment),
120        capabilities.clone(),
121    ));
122    registry.register(BashTool::new(
123        Arc::clone(&environment),
124        capabilities.clone(),
125    ));
126    registry.register(GlobTool::new(
127        Arc::clone(&environment),
128        capabilities.clone(),
129    ));
130    registry.register(GrepTool::new(environment, capabilities));
131}
132
133/// Register [`TodoReadTool`] and [`TodoWriteTool`] sharing `state`.
134pub fn register_todo_tools<Ctx>(registry: &mut ToolRegistry<Ctx>, state: Arc<RwLock<TodoState>>)
135where
136    Ctx: Send + Sync + 'static,
137{
138    registry.register(TodoReadTool::new(Arc::clone(&state)));
139    registry.register(TodoWriteTool::new(state));
140}
141
142/// Register [`LinkFetchTool`] with default HTTP client and
143/// SSRF-protecting [`UrlValidator`](crate::web::security::UrlValidator).
144#[cfg(feature = "web")]
145pub fn register_link_fetch<Ctx>(registry: &mut ToolRegistry<Ctx>)
146where
147    Ctx: Send + Sync + 'static,
148{
149    registry.register(LinkFetchTool::new());
150}
151
152/// Register [`WebSearchTool`] backed by `provider`.
153///
154/// Kept separate from [`BuiltinToolsConfig`] so hosts can pick a
155/// concrete [`SearchProvider`] (Brave, custom, etc.) without that
156/// choice leaking into every call site of [`register_builtin_tools`].
157#[cfg(feature = "web")]
158pub fn register_web_search<Ctx, P>(registry: &mut ToolRegistry<Ctx>, provider: P)
159where
160    Ctx: Send + Sync + 'static,
161    P: SearchProvider + 'static,
162{
163    registry.register(WebSearchTool::new(provider));
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::InMemoryFileSystem;
170
171    #[cfg(feature = "web")]
172    #[test]
173    fn register_builtin_tools_wires_primitives_and_todo_and_fetch() {
174        let fs = Arc::new(InMemoryFileSystem::new("/workspace"));
175        let todo = Arc::new(RwLock::new(TodoState::new()));
176        let mut registry = ToolRegistry::<()>::new();
177
178        register_builtin_tools(
179            &mut registry,
180            BuiltinToolsConfig {
181                environment: fs,
182                capabilities: AgentCapabilities::full_access(),
183                todo_state: Some(todo),
184                link_fetch: true,
185            },
186        );
187
188        for expected in [
189            "read",
190            "write",
191            "edit",
192            "bash",
193            "glob",
194            "grep",
195            "todo_read",
196            "todo_write",
197            "link_fetch",
198        ] {
199            assert!(
200                registry.get(expected).is_some(),
201                "expected '{expected}' registered",
202            );
203        }
204    }
205
206    #[test]
207    fn register_primitives_registers_exactly_six_tools() {
208        let fs = Arc::new(InMemoryFileSystem::new("/workspace"));
209        let mut registry = ToolRegistry::<()>::new();
210        register_primitives(&mut registry, fs, AgentCapabilities::read_only());
211
212        for expected in ["read", "write", "edit", "bash", "glob", "grep"] {
213            assert!(
214                registry.get(expected).is_some(),
215                "expected '{expected}' registered",
216            );
217        }
218    }
219
220    #[test]
221    fn builtin_tools_config_skips_optional_families_when_unset() {
222        let fs = Arc::new(InMemoryFileSystem::new("/workspace"));
223        let mut registry = ToolRegistry::<()>::new();
224        register_builtin_tools(
225            &mut registry,
226            BuiltinToolsConfig {
227                environment: fs,
228                capabilities: AgentCapabilities::full_access(),
229                todo_state: None,
230                #[cfg(feature = "web")]
231                link_fetch: false,
232            },
233        );
234
235        assert!(registry.get("read").is_some());
236        assert!(registry.get("todo_read").is_none());
237        assert!(registry.get("link_fetch").is_none());
238    }
239}