use std::path::PathBuf;
use vtcode_commons::PathScope;
#[cfg(feature = "policies")]
use crate::config::PtyConfig;
#[cfg(feature = "policies")]
use crate::tool_policy::ToolPolicyManager;
#[cfg(feature = "policies")]
use crate::tools::registry::ToolRegistry;
#[cfg(feature = "policies")]
use anyhow::{Context, Error, Result};
#[cfg(feature = "policies")]
use vtcode_commons::{ErrorFormatter, ErrorReporter, PathResolver, TelemetrySink, WorkspacePaths};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RegistryEvent {
PolicyPathResolved { scope: PathScope, config_path: PathBuf },
TelemetryFailure { message: String },
AdapterError { message: String },
}
#[cfg(feature = "policies")]
pub struct RegistryBuilder<'a, Paths, Telemetry, Reporter, Formatter>
where
Paths: WorkspacePaths + ?Sized,
Telemetry: TelemetrySink<RegistryEvent> + ?Sized,
Reporter: ErrorReporter + ?Sized,
Formatter: ErrorFormatter + ?Sized,
{
workspace_paths: &'a Paths,
telemetry: &'a Telemetry,
error_reporter: &'a Reporter,
error_formatter: &'a Formatter,
policy_manager: Option<ToolPolicyManager>,
policy_path: Option<PathBuf>,
pty_config: PtyConfig,
}
#[cfg(feature = "policies")]
impl<'a, Paths, Telemetry, Reporter, Formatter> RegistryBuilder<'a, Paths, Telemetry, Reporter, Formatter>
where
Paths: WorkspacePaths + ?Sized,
Telemetry: TelemetrySink<RegistryEvent> + ?Sized,
Reporter: ErrorReporter + ?Sized,
Formatter: ErrorFormatter + ?Sized,
{
pub fn new(
workspace_paths: &'a Paths,
telemetry: &'a Telemetry,
error_reporter: &'a Reporter,
error_formatter: &'a Formatter,
) -> Self {
Self {
workspace_paths,
telemetry,
error_reporter,
error_formatter,
policy_manager: None,
policy_path: None,
pty_config: PtyConfig::default(),
}
}
pub fn with_pty_config(mut self, config: PtyConfig) -> Self {
self.pty_config = config;
self
}
pub fn with_policy_manager(mut self, manager: ToolPolicyManager) -> Self {
self.policy_manager = Some(manager);
self
}
pub fn with_policy_path<P>(mut self, path: P) -> Self
where
P: Into<PathBuf>,
{
self.policy_path = Some(path.into());
self
}
pub async fn build(mut self) -> Result<ToolRegistry> {
let workspace_root = self.workspace_paths.workspace_root().to_path_buf();
let policy_manager = match self.policy_manager.take() {
Some(manager) => manager,
None => {
let config_path = self
.policy_path
.clone()
.unwrap_or_else(|| self.workspace_paths.resolve_config("tool-policy.json"));
self.record_event(RegistryEvent::PolicyPathResolved {
scope: self.workspace_paths.scope_for_path(&config_path),
config_path: config_path.clone(),
});
match ToolPolicyManager::new_with_config_path(&config_path)
.await
.with_context(|| format!("failed to initialize tool policy manager at {}", config_path.display()))
{
Ok(manager) => manager,
Err(err) => {
self.report_error(&err);
return Err(err);
}
}
}
};
Ok(ToolRegistry::new_with_custom_policy_and_config(workspace_root, self.pty_config, policy_manager).await)
}
fn record_event(&self, event: RegistryEvent) {
if let Err(err) = self.telemetry.record(&event) {
self.handle_error(err.context("failed to record registry adapter telemetry event"));
}
}
fn report_error(&self, error: &Error) {
let message = self.error_formatter.format_error(error).into_owned();
let _ = self.error_reporter.capture(error);
let _ = self.telemetry.record(&RegistryEvent::AdapterError { message });
}
fn handle_error(&self, error: Error) {
let message = self.error_formatter.format_error(&error).into_owned();
let _ = self.error_reporter.capture(&error);
let _ = self.telemetry.record(&RegistryEvent::TelemetryFailure { message });
}
}
#[cfg(all(test, feature = "policies"))]
mod tests {
use super::*;
use assert_fs::TempDir;
use vtcode_commons::{DisplayErrorFormatter, MemoryErrorReporter, MemoryTelemetry, StaticWorkspacePaths};
#[tokio::test]
async fn builds_registry_with_workspace_paths() {
let temp = TempDir::new().expect("tempdir");
let workspace_root = temp.path().join("workspace");
let config_dir = temp.path().join("config");
std::fs::create_dir_all(&workspace_root).expect("workspace");
std::fs::create_dir_all(&config_dir).expect("config");
let paths = StaticWorkspacePaths::new(workspace_root.clone(), config_dir.clone());
let telemetry = MemoryTelemetry::new();
let reporter = MemoryErrorReporter::new();
let formatter = DisplayErrorFormatter;
let builder = RegistryBuilder::new(&paths, &telemetry, &reporter, &formatter);
let registry = builder.build().await.expect("registry");
assert!(registry.has_tool(crate::config::constants::tools::CODE_SEARCH).await);
let events = telemetry.take();
assert!(matches!(events.as_slice(), [RegistryEvent::PolicyPathResolved { scope: PathScope::Config, .. }]));
let policy_file = config_dir.join("tool-policy.json");
assert!(policy_file.exists(), "policy file should be created");
assert!(reporter.take().is_empty(), "no errors expected");
}
}