vtcode_core/tools/
registry_adapters.rs1use std::path::PathBuf;
9
10use vtcode_commons::PathScope;
11
12#[cfg(feature = "policies")]
13use crate::config::PtyConfig;
14#[cfg(feature = "policies")]
15use crate::tool_policy::ToolPolicyManager;
16#[cfg(feature = "policies")]
17use crate::tools::registry::ToolRegistry;
18#[cfg(feature = "policies")]
19use anyhow::{Context, Error, Result};
20#[cfg(feature = "policies")]
21use vtcode_commons::{ErrorFormatter, ErrorReporter, PathResolver, TelemetrySink, WorkspacePaths};
22
23#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum RegistryEvent {
27 PolicyPathResolved { scope: PathScope, config_path: PathBuf },
29 TelemetryFailure { message: String },
32 AdapterError { message: String },
34}
35
36#[cfg(feature = "policies")]
39pub struct RegistryBuilder<'a, Paths, Telemetry, Reporter, Formatter>
40where
41 Paths: WorkspacePaths + ?Sized,
42 Telemetry: TelemetrySink<RegistryEvent> + ?Sized,
43 Reporter: ErrorReporter + ?Sized,
44 Formatter: ErrorFormatter + ?Sized,
45{
46 workspace_paths: &'a Paths,
47 telemetry: &'a Telemetry,
48 error_reporter: &'a Reporter,
49 error_formatter: &'a Formatter,
50 policy_manager: Option<ToolPolicyManager>,
51 policy_path: Option<PathBuf>,
52 pty_config: PtyConfig,
53}
54
55#[cfg(feature = "policies")]
56impl<'a, Paths, Telemetry, Reporter, Formatter> RegistryBuilder<'a, Paths, Telemetry, Reporter, Formatter>
57where
58 Paths: WorkspacePaths + ?Sized,
59 Telemetry: TelemetrySink<RegistryEvent> + ?Sized,
60 Reporter: ErrorReporter + ?Sized,
61 Formatter: ErrorFormatter + ?Sized,
62{
63 pub fn new(
66 workspace_paths: &'a Paths,
67 telemetry: &'a Telemetry,
68 error_reporter: &'a Reporter,
69 error_formatter: &'a Formatter,
70 ) -> Self {
71 Self {
72 workspace_paths,
73 telemetry,
74 error_reporter,
75 error_formatter,
76 policy_manager: None,
77 policy_path: None,
78 pty_config: PtyConfig::default(),
79 }
80 }
81
82 pub fn with_pty_config(mut self, config: PtyConfig) -> Self {
84 self.pty_config = config;
85 self
86 }
87
88 pub fn with_policy_manager(mut self, manager: ToolPolicyManager) -> Self {
90 self.policy_manager = Some(manager);
91 self
92 }
93
94 pub fn with_policy_path<P>(mut self, path: P) -> Self
96 where
97 P: Into<PathBuf>,
98 {
99 self.policy_path = Some(path.into());
100 self
101 }
102
103 pub async fn build(mut self) -> Result<ToolRegistry> {
105 let workspace_root = self.workspace_paths.workspace_root().to_path_buf();
106 let policy_manager = match self.policy_manager.take() {
107 Some(manager) => manager,
108 None => {
109 let config_path = self
110 .policy_path
111 .clone()
112 .unwrap_or_else(|| self.workspace_paths.resolve_config("tool-policy.json"));
113 self.record_event(RegistryEvent::PolicyPathResolved {
114 scope: self.workspace_paths.scope_for_path(&config_path),
115 config_path: config_path.clone(),
116 });
117
118 match ToolPolicyManager::new_with_config_path(&config_path)
119 .await
120 .with_context(|| format!("failed to initialize tool policy manager at {}", config_path.display()))
121 {
122 Ok(manager) => manager,
123 Err(err) => {
124 self.report_error(&err);
125 return Err(err);
126 }
127 }
128 }
129 };
130
131 Ok(ToolRegistry::new_with_custom_policy_and_config(workspace_root, self.pty_config, policy_manager).await)
132 }
133
134 fn record_event(&self, event: RegistryEvent) {
135 if let Err(err) = self.telemetry.record(&event) {
136 self.handle_error(err.context("failed to record registry adapter telemetry event"));
137 }
138 }
139
140 fn report_error(&self, error: &Error) {
141 let message = self.error_formatter.format_error(error).into_owned();
142 let _ = self.error_reporter.capture(error);
143 let _ = self.telemetry.record(&RegistryEvent::AdapterError { message });
144 }
145
146 fn handle_error(&self, error: Error) {
147 let message = self.error_formatter.format_error(&error).into_owned();
148 let _ = self.error_reporter.capture(&error);
149 let _ = self.telemetry.record(&RegistryEvent::TelemetryFailure { message });
150 }
151}
152
153#[cfg(all(test, feature = "policies"))]
154mod tests {
155 use super::*;
156 use assert_fs::TempDir;
157 use vtcode_commons::{DisplayErrorFormatter, MemoryErrorReporter, MemoryTelemetry, StaticWorkspacePaths};
158
159 #[tokio::test]
160 async fn builds_registry_with_workspace_paths() {
161 let temp = TempDir::new().expect("tempdir");
162 let workspace_root = temp.path().join("workspace");
163 let config_dir = temp.path().join("config");
164 std::fs::create_dir_all(&workspace_root).expect("workspace");
165 std::fs::create_dir_all(&config_dir).expect("config");
166
167 let paths = StaticWorkspacePaths::new(workspace_root.clone(), config_dir.clone());
168 let telemetry = MemoryTelemetry::new();
169 let reporter = MemoryErrorReporter::new();
170 let formatter = DisplayErrorFormatter;
171
172 let builder = RegistryBuilder::new(&paths, &telemetry, &reporter, &formatter);
173 let registry = builder.build().await.expect("registry");
174
175 assert!(registry.has_tool(crate::config::constants::tools::CODE_SEARCH).await);
176
177 let events = telemetry.take();
178 assert!(matches!(events.as_slice(), [RegistryEvent::PolicyPathResolved { scope: PathScope::Config, .. }]));
179
180 let policy_file = config_dir.join("tool-policy.json");
181 assert!(policy_file.exists(), "policy file should be created");
182 assert!(reporter.take().is_empty(), "no errors expected");
183 }
184}