vtcode_core/tools/registry/
legacy.rs1use anyhow::{Context, Result, anyhow};
2use regex::Regex;
3use serde_json::{Value, json};
4use shell_words::split;
5
6use crate::config::constants::tools;
7use crate::config::loader::ConfigManager;
8use crate::tools::grep_search::GrepSearchResult;
9use crate::tools::types::EditInput;
10
11use super::ToolRegistry;
12use super::utils;
13
14impl ToolRegistry {
15 pub async fn read_file(&mut self, args: Value) -> Result<Value> {
16 self.execute_tool(tools::READ_FILE, args).await
17 }
18
19 pub async fn write_file(&mut self, args: Value) -> Result<Value> {
20 self.execute_tool(tools::WRITE_FILE, args).await
21 }
22
23 pub async fn edit_file(&mut self, args: Value) -> Result<Value> {
24 let input: EditInput = serde_json::from_value(args).context("invalid edit_file args")?;
25
26 let read_args = json!({
27 "path": input.path,
28 "max_lines": 1000000
29 });
30
31 let read_result = self.file_ops_tool.read_file(read_args).await?;
32 let current_content = read_result["content"]
33 .as_str()
34 .ok_or_else(|| anyhow!("Failed to read file content"))?;
35
36 let mut replacement_occurred = false;
37 let mut new_content = current_content.to_string();
38
39 if current_content.contains(&input.old_str) {
40 new_content = current_content.replace(&input.old_str, &input.new_str);
41 replacement_occurred = new_content != current_content;
42 }
43
44 if !replacement_occurred {
45 let normalized_content = utils::normalize_whitespace(current_content);
46 let normalized_old_str = utils::normalize_whitespace(&input.old_str);
47
48 if normalized_content.contains(&normalized_old_str) {
49 let old_lines: Vec<&str> = input.old_str.lines().collect();
50 let content_lines: Vec<&str> = current_content.lines().collect();
51
52 for i in 0..=(content_lines.len().saturating_sub(old_lines.len())) {
53 let window = &content_lines[i..i + old_lines.len()];
54 if utils::lines_match(window, &old_lines) {
55 let before = content_lines[..i].join("\n");
56 let after = content_lines[i + old_lines.len()..].join("\n");
57 let replacement_lines: Vec<&str> = input.new_str.lines().collect();
58
59 new_content =
60 format!("{}\n{}\n{}", before, replacement_lines.join("\n"), after);
61 replacement_occurred = true;
62 break;
63 }
64 }
65 }
66 }
67
68 if !replacement_occurred {
69 let content_preview = if current_content.len() > 500 {
70 format!(
71 "{}...{}",
72 ¤t_content[..250],
73 ¤t_content[current_content.len().saturating_sub(250)..]
74 )
75 } else {
76 current_content.to_string()
77 };
78
79 return Err(anyhow!(
80 "Could not find text to replace in file.\n\nExpected to replace:\n{}\n\nFile content preview:\n{}",
81 input.old_str,
82 content_preview
83 ));
84 }
85
86 let write_args = json!({
87 "path": input.path,
88 "content": new_content,
89 "mode": "overwrite"
90 });
91
92 self.file_ops_tool.write_file(write_args).await
93 }
94
95 pub async fn delete_file(&mut self, _args: Value) -> Result<Value> {
96 Err(anyhow!("delete_file not yet implemented in modular system"))
97 }
98
99 pub async fn rp_search(&mut self, args: Value) -> Result<Value> {
100 self.execute_tool(tools::GREP_SEARCH, args).await
101 }
102
103 pub fn last_rp_search_result(&self) -> Option<GrepSearchResult> {
104 self.grep_search.last_result()
105 }
106
107 pub async fn list_files(&mut self, args: Value) -> Result<Value> {
108 self.execute_tool(tools::LIST_FILES, args).await
109 }
110
111 pub async fn run_terminal_cmd(&mut self, args: Value) -> Result<Value> {
112 let cfg = ConfigManager::load()
113 .or_else(|_| ConfigManager::load_from_workspace("."))
114 .or_else(|_| ConfigManager::load_from_file("vtcode.toml"))
115 .map(|cm| cm.config().clone())
116 .unwrap_or_default();
117
118 let mut args = args;
119 if let Some(cmd_str) = args.get("command").and_then(|v| v.as_str()) {
120 let parts = split(cmd_str).context("failed to parse command string")?;
121 if parts.is_empty() {
122 return Err(anyhow!("command cannot be empty"));
123 }
124 if let Some(map) = args.as_object_mut() {
125 map.insert("command".to_string(), json!(parts));
126 }
127 }
128
129 let cmd_text = if let Some(cmd_val) = args.get("command") {
130 if cmd_val.is_array() {
131 cmd_val
132 .as_array()
133 .unwrap()
134 .iter()
135 .filter_map(|v| v.as_str())
136 .collect::<Vec<_>>()
137 .join(" ")
138 } else {
139 cmd_val.as_str().unwrap_or("").to_string()
140 }
141 } else {
142 String::new()
143 };
144
145 let mut deny_regex = cfg.commands.deny_regex.clone();
146 if let Ok(extra) = std::env::var("VTCODE_COMMANDS_DENY_REGEX") {
147 deny_regex.extend(extra.split(',').map(|s| s.trim().to_string()));
148 }
149 for pat in &deny_regex {
150 if Regex::new(pat)
151 .ok()
152 .map(|re| re.is_match(&cmd_text))
153 .unwrap_or(false)
154 {
155 return Err(anyhow!("Command denied by regex policy: {}", pat));
156 }
157 }
158 let mut deny_glob = cfg.commands.deny_glob.clone();
159 if let Ok(extra) = std::env::var("VTCODE_COMMANDS_DENY_GLOB") {
160 deny_glob.extend(extra.split(',').map(|s| s.trim().to_string()));
161 }
162 for pat in &deny_glob {
163 let re = format!("^{}$", regex::escape(pat).replace(r"\\*", ".*"));
164 if Regex::new(&re)
165 .ok()
166 .map(|re| re.is_match(&cmd_text))
167 .unwrap_or(false)
168 {
169 return Err(anyhow!("Command denied by glob policy: {}", pat));
170 }
171 }
172 let mut deny_list = cfg.commands.deny_list.clone();
173 if let Ok(extra) = std::env::var("VTCODE_COMMANDS_DENY_LIST") {
174 deny_list.extend(extra.split(',').map(|s| s.trim().to_string()));
175 }
176 for d in &deny_list {
177 if cmd_text.starts_with(d) {
178 return Err(anyhow!("Command denied by policy: {}", d));
179 }
180 }
181
182 let mut allow_regex = cfg.commands.allow_regex.clone();
183 if let Ok(extra) = std::env::var("VTCODE_COMMANDS_ALLOW_REGEX") {
184 allow_regex.extend(extra.split(',').map(|s| s.trim().to_string()));
185 }
186 let mut allow_glob = cfg.commands.allow_glob.clone();
187 if let Ok(extra) = std::env::var("VTCODE_COMMANDS_ALLOW_GLOB") {
188 allow_glob.extend(extra.split(',').map(|s| s.trim().to_string()));
189 }
190 let mut allow_ok = allow_regex.is_empty() && allow_glob.is_empty();
191 if !allow_ok {
192 if allow_regex.iter().any(|pat| {
193 Regex::new(pat)
194 .ok()
195 .map(|re| re.is_match(&cmd_text))
196 .unwrap_or(false)
197 }) {
198 allow_ok = true;
199 }
200 if !allow_ok
201 && allow_glob.iter().any(|pat| {
202 let re = format!("^{}$", regex::escape(pat).replace(r"\\*", ".*"));
203 Regex::new(&re)
204 .ok()
205 .map(|re| re.is_match(&cmd_text))
206 .unwrap_or(false)
207 })
208 {
209 allow_ok = true;
210 }
211 }
212 if !allow_ok {
213 let mut allow_list = cfg.commands.allow_list.clone();
214 if let Ok(extra) = std::env::var("VTCODE_COMMANDS_ALLOW_LIST") {
215 allow_list.extend(extra.split(',').map(|s| s.trim().to_string()));
216 }
217 if !allow_list.is_empty() {
218 allow_ok = allow_list.iter().any(|p| cmd_text.starts_with(p));
219 }
220 }
221 if !allow_ok {
222 return Err(anyhow!("Command not allowed by policy"));
223 }
224
225 if args.get("cwd").is_none() {
226 if let Some(m) = args.as_object_mut() {
227 m.insert(
228 "cwd".to_string(),
229 json!(self.workspace_root.display().to_string()),
230 );
231 }
232 }
233
234 if args.get("mode").is_none() {
235 if let Some(m) = args.as_object_mut() {
236 m.insert("mode".to_string(), json!("pty"));
237 }
238 }
239
240 self.execute_tool(tools::RUN_TERMINAL_CMD, args).await
241 }
242}