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