vtcode_core/tools/registry/
availability_facade.rs1use crate::config::ToolDocumentationMode;
4use crate::config::constants::tools;
5use crate::config::types::CapabilityLevel;
6use crate::tools::handlers::{SessionSurface, SessionToolsConfig, ToolModelCapabilities};
7use crate::tools::names::canonical_tool_name;
8use serde_json::Value;
9
10use super::ToolRegistry;
11use crate::tools::mcp::legacy_mcp_tool_name;
12
13impl ToolRegistry {
14 fn resolve_fallback_seed_tool(&self, failed_tool: &str) -> String {
15 if let Ok(resolved) = self.resolve_public_tool_name_sync(failed_tool) {
16 return resolved;
17 }
18
19 let lower = failed_tool.trim().to_ascii_lowercase();
20 match lower.as_str() {
21 "exec_code" => tools::EXEC_COMMAND.to_string(),
22 "list_dir" | "list_directory" => lower,
23 _ => {
24 if let Some((_, suffix)) = lower.rsplit_once('.')
25 && let Ok(resolved) = self.resolve_public_tool_name_sync(suffix)
26 {
27 return resolved;
28 }
29 lower
30 }
31 }
32 }
33
34 pub async fn suggest_fallback_tool(&self, failed_tool: &str) -> Option<String> {
36 let available = self.available_tools().await;
37 let failed = canonical_tool_name(failed_tool);
38 let failed_name = failed;
39 let seed = self.resolve_fallback_seed_tool(failed_name);
40
41 if seed != failed_name && available.iter().any(|tool| tool == &seed) {
42 return Some(seed);
43 }
44
45 let candidates: &[&str] = match seed.as_str() {
46 tools::EXEC_COMMAND => &[],
47 tools::APPLY_PATCH => &[tools::EXEC_COMMAND],
48 tools::TASK_TRACKER => &[],
51 _ => &[],
53 };
54
55 for candidate in candidates {
56 if *candidate != failed_name && available.iter().any(|tool| tool == candidate) {
57 return Some((*candidate).to_string());
58 }
59 }
60 None
61 }
62
63 pub async fn available_tools(&self) -> Vec<String> {
65 if let Some(cache) = self.cached_available_tools.try_read()
67 && let Some(tools) = cache.as_ref()
68 {
69 return tools.clone();
70 }
71
72 let tools = self
73 .public_tool_names(SessionSurface::Interactive, CapabilityLevel::CodeSearch)
74 .await;
75
76 if let Some(mut cache) = self.cached_available_tools.try_write() {
78 *cache = Some(tools.clone());
79 }
80
81 tools
82 }
83
84 pub async fn get_tool_schema(&self, tool_name: &str) -> Option<Value> {
86 if is_removed_default_public_tool(tool_name) {
87 return None;
88 }
89
90 let wrap_schema = |requested_name: &str, description: &str, schema: &Value| {
91 if schema.get("properties").is_some() && schema.get("name").is_none() {
93 serde_json::json!({
94 "name": requested_name,
95 "description": description,
96 "parameters": schema
97 })
98 } else {
99 schema.clone()
100 }
101 };
102
103 if let Some(entry) = self
104 .schema_for_public_name(
105 tool_name,
106 SessionToolsConfig::full_public(
107 SessionSurface::Interactive,
108 CapabilityLevel::CodeSearch,
109 ToolDocumentationMode::Full,
110 ToolModelCapabilities::default(),
111 )
112 .with_planning_active(self.is_planning_active()),
113 )
114 .await
115 {
116 return Some(wrap_schema(entry.name.as_str(), entry.description.as_str(), &entry.parameters));
117 }
118
119 if let Some(registration) = self.inventory.get_registration(tool_name) {
121 let description = if registration.name() == tools::TASK_TRACKER {
122 crate::tools::handlers::task_tracker::task_tracker_description_for_workflow(self.is_planning_active())
123 } else {
124 registration.metadata().description().unwrap_or("")
125 };
126 let schema = if registration.name() == tools::TASK_TRACKER {
127 crate::tools::handlers::task_tracker::task_tracker_parameter_schema_for_workflow(
128 self.is_planning_active(),
129 )
130 } else {
131 registration.parameter_schema()?.clone()
132 };
133 return Some(wrap_schema(tool_name, description, &schema));
134 }
135
136 None
137 }
138
139 pub async fn has_tool(&self, name: &str) -> bool {
147 if is_removed_default_public_tool(name) {
148 return false;
149 }
150
151 if self.resolve_public_tool_name_sync(name).is_ok() {
152 return true;
153 }
154
155 if self.inventory.has_tool(name) {
157 return true;
158 }
159
160 if let Some(tool_name) = legacy_mcp_tool_name(name) {
162 if self.has_mcp_tool(tool_name).await {
163 return true;
164 }
165
166 if let Some(resolved_name) = self.resolve_mcp_tool_alias(tool_name).await
168 && resolved_name != tool_name
169 {
170 return true;
171 }
172 }
173
174 false
175 }
176}
177
178fn is_removed_default_public_tool(tool_name: &str) -> bool {
179 let lower = tool_name.trim().to_ascii_lowercase();
180 matches!(
181 lower.as_str(),
182 tools::UNIFIED_EXEC
183 | tools::UNIFIED_FILE
184 | tools::UNIFIED_SEARCH
185 | tools::LIST_FILES
186 | tools::READ_FILE
187 | tools::WRITE_FILE
188 | tools::EDIT_FILE
189 | tools::CREATE_FILE
190 | tools::DELETE_FILE
191 | tools::MOVE_FILE
192 | tools::COPY_FILE
193 | tools::SEARCH_REPLACE
194 | tools::FILE_OP
195 )
196}