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::UNIFIED_EXEC.to_string(),
22 "list_dir" | "list_directory" => tools::UNIFIED_SEARCH.to_string(),
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::UNIFIED_SEARCH => &[tools::UNIFIED_FILE],
47 tools::UNIFIED_EXEC => &[tools::UNIFIED_SEARCH],
48 tools::UNIFIED_FILE | tools::APPLY_PATCH => &[tools::UNIFIED_SEARCH],
49 tools::TASK_TRACKER | tools::PLAN_TASK_TRACKER => &[],
52 _ => &[],
54 };
55
56 for candidate in candidates {
57 if *candidate != failed_name && available.iter().any(|tool| tool == candidate) {
58 return Some((*candidate).to_string());
59 }
60 }
61 None
62 }
63
64 pub async fn available_tools(&self) -> Vec<String> {
66 if let Ok(cache) = self.cached_available_tools.try_read()
68 && let Some(tools) = cache.as_ref()
69 {
70 return tools.clone();
71 }
72
73 let tools = self
74 .public_tool_names(SessionSurface::Interactive, CapabilityLevel::CodeSearch)
75 .await;
76
77 if let Ok(mut cache) = self.cached_available_tools.try_write() {
79 *cache = Some(tools.clone());
80 }
81
82 tools
83 }
84
85 pub async fn get_tool_schema(&self, tool_name: &str) -> Option<Value> {
87 let wrap_schema = |requested_name: &str, description: &str, schema: &Value| {
88 if schema.get("properties").is_some() && schema.get("name").is_none() {
90 serde_json::json!({
91 "name": requested_name,
92 "description": description,
93 "parameters": schema
94 })
95 } else {
96 schema.clone()
97 }
98 };
99
100 if let Some(entry) = self
101 .schema_for_public_name(
102 tool_name,
103 SessionToolsConfig::full_public(
104 SessionSurface::Interactive,
105 CapabilityLevel::CodeSearch,
106 ToolDocumentationMode::Full,
107 ToolModelCapabilities::default(),
108 ),
109 )
110 .await
111 {
112 return Some(wrap_schema(
113 entry.name.as_str(),
114 entry.description.as_str(),
115 &entry.parameters,
116 ));
117 }
118
119 if let Some(registration) = self.inventory.get_registration(tool_name)
121 && let Some(schema) = registration.parameter_schema()
122 {
123 return Some(wrap_schema(
124 tool_name,
125 registration.metadata().description().unwrap_or(""),
126 schema,
127 ));
128 }
129
130 None
131 }
132
133 pub async fn has_tool(&self, name: &str) -> bool {
141 if self.resolve_public_tool_name_sync(name).is_ok() {
142 return true;
143 }
144
145 if self.inventory.has_tool(name) {
147 return true;
148 }
149
150 if let Some(tool_name) = legacy_mcp_tool_name(name) {
152 if self.has_mcp_tool(tool_name).await {
153 return true;
154 }
155
156 if let Some(resolved_name) = self.resolve_mcp_tool_alias(tool_name).await
158 && resolved_name != tool_name
159 {
160 return true;
161 }
162 }
163
164 false
165 }
166}