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