Skip to main content

a3s_code_core/tools/builtin/
mod.rs

1//! Native Rust implementations of all built-in tools
2//!
3//! These replace the previous `a3s-tools` binary backend with direct Rust
4//! implementations that execute in-process. Each tool implements the `Tool` trait.
5
6pub(crate) mod bash;
7pub mod batch;
8mod edit;
9mod generate_object;
10pub(crate) mod git;
11mod glob_tool;
12mod grep;
13mod ls;
14mod patch;
15mod read;
16mod web_fetch;
17mod web_search;
18mod write;
19
20use super::registry::ToolRegistry;
21use std::sync::Arc;
22
23/// Normalize a source URL before it can enter durable tool/task metadata.
24/// Credentials, query strings, and fragments are intentionally excluded.
25pub(crate) fn safe_http_source_url(value: &str) -> Option<String> {
26    let mut url = reqwest::Url::parse(value.trim()).ok()?;
27    if !matches!(url.scheme(), "http" | "https") || url.host_str()?.is_empty() {
28        return None;
29    }
30    url.set_username("").ok()?;
31    url.set_password(None).ok()?;
32    url.set_query(None);
33    url.set_fragment(None);
34    Some(url.to_string())
35}
36
37/// Register all baseline built-in tools with the registry, gated by
38/// workspace capabilities.
39///
40/// Tools whose required capability is missing are not registered, so the model
41/// never sees a tool the backend cannot service. `web_fetch` and `web_search`
42/// have no workspace capability and are always registered.
43///
44/// Note: `batch` is NOT registered here — it requires an `Arc<ToolRegistry>`
45/// and must be registered after the registry is wrapped in an Arc.
46pub fn register_builtins(
47    registry: &ToolRegistry,
48    capabilities: &crate::workspace::WorkspaceCapabilities,
49) {
50    if capabilities.read {
51        registry.register_builtin(Arc::new(read::ReadTool));
52        registry.register_builtin(Arc::new(ls::LsTool));
53    }
54    if capabilities.write {
55        registry.register_builtin(Arc::new(write::WriteTool));
56    }
57    if capabilities.read && capabilities.write {
58        registry.register_builtin(Arc::new(edit::EditTool));
59        registry.register_builtin(Arc::new(patch::PatchTool));
60    }
61    if capabilities.exec {
62        registry.register_builtin(Arc::new(bash::BashTool));
63    }
64    if capabilities.search {
65        registry.register_builtin(Arc::new(grep::GrepTool));
66        registry.register_builtin(Arc::new(glob_tool::GlobTool));
67    }
68    if capabilities.git {
69        registry.register_builtin(Arc::new(git::GitTool));
70    }
71    registry.register_builtin(Arc::new(web_fetch::WebFetchTool));
72    registry.register_builtin(Arc::new(web_search::WebSearchTool::new()));
73}
74
75/// Register the batch tool. Must be called after the registry is wrapped in Arc.
76pub fn register_batch(registry: &Arc<ToolRegistry>) {
77    registry.register_builtin(Arc::new(batch::BatchTool::new(Arc::clone(registry))));
78}
79
80/// Register the programmatic tool calling wrapper.
81pub fn register_program(registry: &Arc<ToolRegistry>) {
82    register_program_with_catalog(
83        registry,
84        crate::program::ProgramCatalog::with_builtin_programs(),
85    );
86}
87
88/// Register the programmatic tool calling wrapper with a custom catalog.
89pub fn register_program_with_catalog(
90    registry: &Arc<ToolRegistry>,
91    catalog: crate::program::ProgramCatalog,
92) {
93    registry.register_builtin(Arc::new(crate::tools::ProgramTool::with_catalog(
94        Arc::clone(registry),
95        catalog,
96    )));
97}
98
99/// Register the task delegation tools (task, parallel_task).
100///
101/// Must be called after the registry is wrapped in Arc. Requires an LLM client
102/// and the workspace path so child agent loops can be spawned inline.
103/// Optionally accepts an MCP manager so child sessions inherit MCP tools.
104pub fn register_task(
105    registry: &Arc<ToolRegistry>,
106    llm_client: Arc<dyn crate::llm::LlmClient>,
107    agent_registry: Arc<crate::subagent::AgentRegistry>,
108    workspace: String,
109) {
110    register_task_with_mcp(
111        registry,
112        llm_client,
113        agent_registry,
114        workspace,
115        None,
116        None,
117        None,
118    );
119}
120
121/// Register the task delegation tools with optional MCP manager and parent context.
122///
123/// When `mcp_manager` is provided, delegated child sessions will have access
124/// to all MCP tools from connected servers.
125/// When `parent_context` is provided, child runs inherit parent capabilities.
126/// When `subagent_tracker` is provided, each task registers a
127/// `CancellationToken` against it so callers can cancel by `task_id`.
128pub fn register_task_with_mcp(
129    registry: &Arc<ToolRegistry>,
130    llm_client: Arc<dyn crate::llm::LlmClient>,
131    agent_registry: Arc<crate::subagent::AgentRegistry>,
132    workspace: String,
133    mcp_manager: Option<Arc<crate::mcp::manager::McpManager>>,
134    parent_context: Option<crate::child_run::ChildRunContext>,
135    subagent_tracker: Option<Arc<crate::subagent_task_tracker::InMemorySubagentTaskTracker>>,
136) {
137    register_task_with_mcp_managers(
138        registry,
139        llm_client,
140        agent_registry,
141        workspace,
142        mcp_manager.into_iter().collect(),
143        parent_context,
144        subagent_tracker,
145    );
146}
147
148/// Register task delegation tools with ordered MCP capability sources.
149///
150/// Each manager keeps ownership of its own connections. Later sources shadow
151/// earlier sources on identical fully-qualified tool names inside child runs.
152pub fn register_task_with_mcp_managers(
153    registry: &Arc<ToolRegistry>,
154    llm_client: Arc<dyn crate::llm::LlmClient>,
155    agent_registry: Arc<crate::subagent::AgentRegistry>,
156    workspace: String,
157    mcp_managers: Vec<Arc<crate::mcp::manager::McpManager>>,
158    parent_context: Option<crate::child_run::ChildRunContext>,
159    subagent_tracker: Option<Arc<crate::subagent_task_tracker::InMemorySubagentTaskTracker>>,
160) {
161    use crate::tools::task::{ParallelTaskTool, TaskExecutor, TaskTool};
162    let mut executor =
163        TaskExecutor::with_mcp_managers(agent_registry, llm_client, workspace, mcp_managers);
164    if let Some(ctx) = parent_context {
165        executor = executor.with_parent_context(ctx);
166    }
167    if let Some(tracker) = subagent_tracker {
168        executor = executor.with_subagent_tracker(tracker);
169    }
170    let executor = Arc::new(executor);
171    registry.register_builtin(Arc::new(TaskTool::new(Arc::clone(&executor))));
172    registry.register_builtin(Arc::new(ParallelTaskTool::new(Arc::clone(&executor))));
173}
174
175/// Register the Skill tool for skill-based tool access control.
176pub(crate) fn register_skill(
177    registry: &Arc<ToolRegistry>,
178    llm_client: Arc<dyn crate::llm::LlmClient>,
179    skill_registry: Arc<crate::skills::SkillRegistry>,
180    tool_executor: Arc<crate::tools::ToolExecutor>,
181    base_config: crate::agent::AgentConfig,
182) {
183    use crate::tools::skill::{SearchSkillsTool, SkillTool};
184    registry.register_builtin(Arc::new(SearchSkillsTool::new(Arc::clone(&skill_registry))));
185    registry.register_builtin(Arc::new(SkillTool::new(
186        skill_registry,
187        llm_client,
188        tool_executor,
189        base_config,
190    )));
191}
192
193/// Register the `generate_object` tool for structured JSON output.
194///
195/// Must be called after the registry is wrapped in Arc. Requires an LLM client
196/// so the tool can make its own LLM calls for object generation.
197pub fn register_generate_object(
198    registry: &Arc<ToolRegistry>,
199    llm_client: Arc<dyn crate::llm::LlmClient>,
200) {
201    registry.register_builtin(Arc::new(generate_object::GenerateObjectTool::new(
202        llm_client,
203    )));
204}
205
206#[cfg(test)]
207mod tests {
208    use super::safe_http_source_url;
209
210    #[test]
211    fn safe_source_url_removes_credentials_query_and_fragment() {
212        assert_eq!(
213            safe_http_source_url(
214                "HTTPS://user:password@Example.COM/report?access_token=secret#section"
215            )
216            .as_deref(),
217            Some("https://example.com/report")
218        );
219        assert!(safe_http_source_url("file:///tmp/source").is_none());
220    }
221}