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