vtcode_acp/tooling/
catalog.rs1use hashbrown::HashMap;
2use serde_json::Value;
3use std::path::Path;
4use vtcode_core::config::constants::tools;
5use vtcode_core::llm::provider::ToolDefinition;
6use vtcode_core::tools::tool_intent;
7
8use super::schemas::{build_list_files_definition, build_read_file_definition};
9use super::titles::render_title;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub enum SupportedTool {
14 ReadFile,
15 ListFiles,
16}
17
18impl SupportedTool {
19 pub fn kind(&self) -> crate::acp::ToolKind {
20 match self {
21 Self::ReadFile => crate::acp::ToolKind::Read,
22 Self::ListFiles => crate::acp::ToolKind::Search,
23 }
24 }
25
26 pub fn default_title(&self) -> &'static str {
27 match self {
28 Self::ReadFile => "Read file",
29 Self::ListFiles => "List files",
30 }
31 }
32
33 pub fn function_name(&self) -> &'static str {
34 match self {
35 Self::ReadFile => tools::READ_FILE,
36 Self::ListFiles => tools::LIST_FILES,
37 }
38 }
39
40 pub fn sort_key(&self) -> u8 {
41 match self {
42 Self::ReadFile => 0,
43 Self::ListFiles => 1,
44 }
45 }
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum ToolDescriptor {
50 Acp(SupportedTool),
51 Local,
52}
53
54impl ToolDescriptor {
55 pub fn kind(self) -> crate::acp::ToolKind {
56 match self {
57 Self::Acp(tool) => tool.kind(),
58 Self::Local => crate::acp::ToolKind::Other,
59 }
60 }
61}
62
63struct ToolRegistryEntry {
64 tool: SupportedTool,
65 definition: ToolDefinition,
66}
67
68pub struct AcpToolRegistry {
69 entries: Vec<ToolRegistryEntry>,
70 local_definitions: Vec<ToolDefinition>,
71 mapping: HashMap<String, ToolDescriptor>,
72}
73
74impl AcpToolRegistry {
75 pub fn new(
76 workspace_root: &Path,
77 read_file_enabled: bool,
78 list_files_enabled: bool,
79 local_definitions: Vec<ToolDefinition>,
80 ) -> Self {
81 let mut entries = Vec::with_capacity(5);
82 let mut mapping = HashMap::with_capacity(10);
83
84 if read_file_enabled {
85 push_registry_entry(
86 &mut entries,
87 &mut mapping,
88 SupportedTool::ReadFile,
89 build_read_file_definition(workspace_root),
90 );
91 }
92 if list_files_enabled {
93 push_registry_entry(
94 &mut entries,
95 &mut mapping,
96 SupportedTool::ListFiles,
97 build_list_files_definition(workspace_root),
98 );
99 }
100
101 entries.sort_unstable_by_key(|entry| entry.tool.sort_key());
102
103 Self {
104 entries,
105 local_definitions,
106 mapping,
107 }
108 }
109
110 pub fn registered_tools(&self) -> Vec<SupportedTool> {
111 self.entries.iter().map(|entry| entry.tool).collect()
112 }
113
114 pub fn definitions_for(
115 &self,
116 enabled_tools: &[SupportedTool],
117 include_local: bool,
118 ) -> Vec<ToolDefinition> {
119 let mut definitions = Vec::with_capacity(self.entries.len());
120 for entry in &self.entries {
121 if enabled_tools.contains(&entry.tool) {
122 definitions.push(entry.definition.clone());
123 }
124 }
125
126 if include_local {
127 definitions.extend(self.local_definitions.iter().cloned());
128 }
129
130 definitions
131 }
132
133 pub fn render_title(
134 &self,
135 descriptor: ToolDescriptor,
136 function_name: &str,
137 args: &Value,
138 ) -> String {
139 render_title(descriptor, function_name, args)
140 }
141
142 pub fn tool_kind(&self, function_name: &str) -> crate::acp::ToolKind {
143 self.tool_kind_for_call(function_name, None)
144 }
145
146 pub fn tool_kind_for_call(
147 &self,
148 function_name: &str,
149 args: Option<&Value>,
150 ) -> crate::acp::ToolKind {
151 match function_name {
152 tools::UNIFIED_SEARCH => crate::acp::ToolKind::Search,
153 tools::UNIFIED_EXEC => crate::acp::ToolKind::Execute,
154 tools::UNIFIED_FILE => unified_file_tool_kind(args),
155 tools::READ_FILE => crate::acp::ToolKind::Read,
156 tools::GREP_FILE | tools::LIST_FILES => crate::acp::ToolKind::Search,
157 tools::RUN_PTY_CMD
158 | tools::EXEC_PTY_CMD
159 | tools::EXEC_COMMAND
160 | tools::EXECUTE_CODE
161 | tools::SHELL => crate::acp::ToolKind::Execute,
162 tools::WRITE_FILE
163 | tools::CREATE_FILE
164 | tools::EDIT_FILE
165 | tools::APPLY_PATCH
166 | tools::SEARCH_REPLACE
167 | tools::FILE_OP
168 | tools::COPY_FILE => crate::acp::ToolKind::Edit,
169 tools::DELETE_FILE => crate::acp::ToolKind::Delete,
170 tools::MOVE_FILE => crate::acp::ToolKind::Move,
171 tools::WEB_FETCH | tools::FETCH_URL | tools::FETCH => crate::acp::ToolKind::Fetch,
172 tools::THINK => crate::acp::ToolKind::Think,
173 _ => crate::acp::ToolKind::Other,
174 }
175 }
176
177 pub fn lookup(&self, function_name: &str) -> Option<ToolDescriptor> {
178 self.mapping.get(function_name).copied().or_else(|| {
179 self.local_definitions
180 .iter()
181 .any(|definition| definition.function_name() == function_name)
182 .then_some(ToolDescriptor::Local)
183 })
184 }
185
186 pub fn has_local_tools(&self) -> bool {
187 !self.local_definitions.is_empty()
188 }
189}
190
191fn unified_file_tool_kind(args: Option<&Value>) -> crate::acp::ToolKind {
192 let Some(action) = args.and_then(tool_intent::unified_file_action) else {
193 return crate::acp::ToolKind::Other;
194 };
195
196 match action {
197 action if action.eq_ignore_ascii_case("read") => crate::acp::ToolKind::Read,
198 action if action.eq_ignore_ascii_case("delete") => crate::acp::ToolKind::Delete,
199 action if action.eq_ignore_ascii_case("move") => crate::acp::ToolKind::Move,
200 action
201 if action.eq_ignore_ascii_case("write")
202 || action.eq_ignore_ascii_case("create")
203 || action.eq_ignore_ascii_case("edit")
204 || action.eq_ignore_ascii_case("patch")
205 || action.eq_ignore_ascii_case("copy") =>
206 {
207 crate::acp::ToolKind::Edit
208 }
209 _ => crate::acp::ToolKind::Other,
210 }
211}
212
213fn push_registry_entry(
214 entries: &mut Vec<ToolRegistryEntry>,
215 mapping: &mut HashMap<String, ToolDescriptor>,
216 tool: SupportedTool,
217 definition: ToolDefinition,
218) {
219 mapping.insert(
220 definition.function_name().to_string(),
221 ToolDescriptor::Acp(tool),
222 );
223 entries.push(ToolRegistryEntry { tool, definition });
224}