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