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