a3s_code_core/tools/builtin/
mod.rs1pub(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 hybrid_search;
17mod ls;
18mod patch;
19mod read;
20mod safe_http;
21mod search;
22mod semantic_search;
23mod web_fetch;
24mod web_search;
25mod write;
26
27use super::registry::ToolRegistry;
28use std::sync::Arc;
29
30pub(crate) fn safe_http_source_url(value: &str) -> Option<String> {
33 let mut url = reqwest::Url::parse(value.trim()).ok()?;
34 if !matches!(url.scheme(), "http" | "https") || url.host_str()?.is_empty() {
35 return None;
36 }
37 url.set_username("").ok()?;
38 url.set_password(None).ok()?;
39 url.set_query(None);
40 url.set_fragment(None);
41 Some(url.to_string())
42}
43
44pub fn register_builtins(
54 registry: &ToolRegistry,
55 workspace_services: &crate::workspace::WorkspaceServices,
56) {
57 let capabilities = workspace_services.capabilities();
58 if capabilities.read {
59 registry.register_builtin(Arc::new(read::ReadTool));
60 registry.register_builtin(Arc::new(ls::LsTool));
61 }
62 if capabilities.write {
63 registry.register_builtin(Arc::new(write::WriteTool));
64 if workspace_services.local_root().is_some() {
65 registry.register_builtin(Arc::new(download::DownloadTool));
66 }
67 }
68 if capabilities.read && capabilities.write {
69 registry.register_builtin(Arc::new(edit::EditTool));
70 registry.register_builtin(Arc::new(patch::PatchTool));
71 }
72 if capabilities.exec {
73 registry.register_builtin(Arc::new(bash::BashTool));
74 }
75 let semantic_enabled = capabilities.read && workspace_services.workspace_retrieval().is_some();
76 let persistent_backend_enabled =
77 capabilities.read && workspace_services.persistent_index().is_some();
78 if capabilities.search || semantic_enabled || persistent_backend_enabled {
79 let search = search::SearchTool::new(capabilities.read)
80 .with_backend_search(capabilities.search)
81 .with_semantic(semantic_enabled)
82 .with_persistent_backend(persistent_backend_enabled);
83 registry.register_builtin(Arc::new(search));
84 }
85 if workspace_services.code_intelligence().is_some() {
86 code_intelligence::register(registry);
87 }
88 if capabilities.git {
89 registry.register_builtin(Arc::new(git::GitTool));
90 }
91 registry.register_builtin(Arc::new(web_fetch::WebFetchTool));
92 registry.register_builtin(Arc::new(web_search::WebSearchTool::new()));
93}
94
95#[cfg(test)]
96pub(crate) fn repository_tool_parameter_schemas() -> Vec<(String, serde_json::Value)> {
97 use crate::tools::Tool;
98
99 let read = read::ReadTool;
100 let search = search::SearchTool::new(true);
101 let edit = edit::EditTool;
102 vec![
103 (read.name().to_string(), read.parameters()),
104 (search.name().to_string(), search.parameters()),
105 (edit.name().to_string(), edit.parameters()),
106 ]
107}
108
109pub fn register_batch(registry: &Arc<ToolRegistry>) {
111 registry.register_builtin(Arc::new(batch::BatchTool::new_registry_bound(Arc::clone(
112 registry,
113 ))));
114}
115
116pub fn register_program(registry: &Arc<ToolRegistry>) {
118 register_program_with_catalog(
119 registry,
120 crate::program::ProgramCatalog::with_builtin_programs(),
121 );
122}
123
124pub fn register_program_with_catalog(
126 registry: &Arc<ToolRegistry>,
127 catalog: crate::program::ProgramCatalog,
128) {
129 registry.register_builtin(Arc::new(
130 crate::tools::ProgramTool::with_catalog_registry_bound(Arc::clone(registry), catalog),
131 ));
132}
133
134pub fn register_task(
140 registry: &Arc<ToolRegistry>,
141 llm_client: Arc<dyn crate::llm::LlmClient>,
142 agent_registry: Arc<crate::subagent::AgentRegistry>,
143 workspace: String,
144) {
145 register_task_with_mcp(
146 registry,
147 llm_client,
148 agent_registry,
149 workspace,
150 None,
151 None,
152 None,
153 );
154}
155
156pub fn register_task_with_mcp(
164 registry: &Arc<ToolRegistry>,
165 llm_client: Arc<dyn crate::llm::LlmClient>,
166 agent_registry: Arc<crate::subagent::AgentRegistry>,
167 workspace: String,
168 mcp_manager: Option<Arc<crate::mcp::manager::McpManager>>,
169 parent_context: Option<crate::child_run::ChildRunContext>,
170 subagent_tracker: Option<Arc<crate::subagent_task_tracker::InMemorySubagentTaskTracker>>,
171) {
172 register_task_with_mcp_managers(
173 registry,
174 llm_client,
175 agent_registry,
176 workspace,
177 mcp_manager.into_iter().collect(),
178 parent_context,
179 subagent_tracker,
180 );
181}
182
183pub fn register_task_with_mcp_managers(
188 registry: &Arc<ToolRegistry>,
189 llm_client: Arc<dyn crate::llm::LlmClient>,
190 agent_registry: Arc<crate::subagent::AgentRegistry>,
191 workspace: String,
192 mcp_managers: Vec<Arc<crate::mcp::manager::McpManager>>,
193 parent_context: Option<crate::child_run::ChildRunContext>,
194 subagent_tracker: Option<Arc<crate::subagent_task_tracker::InMemorySubagentTaskTracker>>,
195) {
196 register_task_internal(
197 registry,
198 llm_client,
199 agent_registry,
200 workspace,
201 mcp_managers,
202 Vec::new(),
203 parent_context,
204 subagent_tracker,
205 None,
206 );
207}
208
209#[allow(clippy::too_many_arguments)]
211pub(crate) fn register_task_with_mcp_managers_and_scheduler(
212 registry: &Arc<ToolRegistry>,
213 llm_client: Arc<dyn crate::llm::LlmClient>,
214 agent_registry: Arc<crate::subagent::AgentRegistry>,
215 workspace: String,
216 mcp_managers: Vec<Arc<crate::mcp::manager::McpManager>>,
217 parent_context: Option<crate::child_run::ChildRunContext>,
218 subagent_tracker: Option<Arc<crate::subagent_task_tracker::InMemorySubagentTaskTracker>>,
219 task_scheduler: Arc<crate::task_scheduler::TaskScheduler>,
220) {
221 register_task_with_mcp_sources_and_scheduler(
222 registry,
223 llm_client,
224 agent_registry,
225 workspace,
226 mcp_managers,
227 Vec::new(),
228 parent_context,
229 subagent_tracker,
230 task_scheduler,
231 );
232}
233
234#[allow(clippy::too_many_arguments)]
237pub(crate) fn register_task_with_mcp_sources_and_scheduler(
238 registry: &Arc<ToolRegistry>,
239 llm_client: Arc<dyn crate::llm::LlmClient>,
240 agent_registry: Arc<crate::subagent::AgentRegistry>,
241 workspace: String,
242 mcp_managers: Vec<Arc<crate::mcp::manager::McpManager>>,
243 mcp_bindings: Vec<Arc<crate::mcp::McpBinding>>,
244 parent_context: Option<crate::child_run::ChildRunContext>,
245 subagent_tracker: Option<Arc<crate::subagent_task_tracker::InMemorySubagentTaskTracker>>,
246 task_scheduler: Arc<crate::task_scheduler::TaskScheduler>,
247) {
248 register_task_internal(
249 registry,
250 llm_client,
251 agent_registry,
252 workspace,
253 mcp_managers,
254 mcp_bindings,
255 parent_context,
256 subagent_tracker,
257 Some(task_scheduler),
258 );
259}
260
261#[allow(clippy::too_many_arguments)]
262fn register_task_internal(
263 registry: &Arc<ToolRegistry>,
264 llm_client: Arc<dyn crate::llm::LlmClient>,
265 agent_registry: Arc<crate::subagent::AgentRegistry>,
266 workspace: String,
267 mcp_managers: Vec<Arc<crate::mcp::manager::McpManager>>,
268 mcp_bindings: Vec<Arc<crate::mcp::McpBinding>>,
269 parent_context: Option<crate::child_run::ChildRunContext>,
270 subagent_tracker: Option<Arc<crate::subagent_task_tracker::InMemorySubagentTaskTracker>>,
271 task_scheduler: Option<Arc<crate::task_scheduler::TaskScheduler>>,
272) {
273 use crate::tools::task::{ParallelTaskTool, TaskExecutor, TaskTool};
274 let mut executor =
275 TaskExecutor::with_mcp_managers(agent_registry, llm_client, workspace, mcp_managers)
276 .with_projected_mcp_bindings(mcp_bindings);
277 if let Some(ctx) = parent_context {
278 executor = executor.with_parent_context(ctx);
279 }
280 if let Some(tracker) = subagent_tracker {
281 executor = executor.with_subagent_tracker(tracker);
282 }
283 if let Some(task_scheduler) = task_scheduler {
284 executor = executor.with_task_scheduler(task_scheduler, false);
287 }
288 let executor = Arc::new(executor);
289 registry.register_builtin(Arc::new(TaskTool::new(Arc::clone(&executor))));
290 registry.register_builtin(Arc::new(ParallelTaskTool::new(Arc::clone(&executor))));
291}
292
293pub(crate) fn register_skill(
295 registry: &Arc<ToolRegistry>,
296 llm_client: Arc<dyn crate::llm::LlmClient>,
297 skill_registry: Arc<crate::skills::SkillRegistry>,
298 tool_executor: Arc<crate::tools::ToolExecutor>,
299 base_config: crate::agent::AgentConfig,
300) {
301 use crate::tools::skill::{SearchSkillsTool, SkillTool};
302 registry.register_builtin(Arc::new(SearchSkillsTool::new(Arc::clone(&skill_registry))));
303 registry.register_builtin(Arc::new(SkillTool::new_registry_bound(
304 skill_registry,
305 llm_client,
306 tool_executor,
307 base_config,
308 )));
309}
310
311pub fn register_generate_object(
316 registry: &Arc<ToolRegistry>,
317 llm_client: Arc<dyn crate::llm::LlmClient>,
318) {
319 registry.register_builtin(Arc::new(generate_object::GenerateObjectTool::new(
320 llm_client,
321 )));
322}
323
324#[cfg(test)]
325mod tests {
326 use super::safe_http_source_url;
327
328 #[test]
329 fn safe_source_url_removes_credentials_query_and_fragment() {
330 assert_eq!(
331 safe_http_source_url(
332 "HTTPS://user:password@Example.COM/report?access_token=secret#section"
333 )
334 .as_deref(),
335 Some("https://example.com/report")
336 );
337 assert!(safe_http_source_url("file:///tmp/source").is_none());
338 }
339}