Skip to main content

vtcode_core/tools/registry/
registration.rs

1use super::ToolRegistry;
2use crate::config::types::CapabilityLevel;
3use crate::tool_policy::ToolPolicy;
4use crate::tools::tool_intent::ToolBehavior;
5use crate::tools::traits::Tool;
6use futures::future::BoxFuture;
7use serde_json::Value;
8use std::path::PathBuf;
9use std::sync::Arc;
10
11pub type ToolExecutorFn = for<'a> fn(&'a ToolRegistry, Value) -> BoxFuture<'a, anyhow::Result<Value>>;
12pub type NativeCgpToolFactory = Arc<
13    dyn for<'a> Fn(&'a ToolRegistration, PathBuf, super::cgp_facade::CgpRuntimeMode) -> Arc<dyn Tool> + Send + Sync,
14>;
15
16use std::fmt;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub enum ToolCatalogSource {
20    Builtin,
21    Mcp,
22    #[default]
23    Dynamic,
24}
25
26/// Trusted registration metadata, never inferred from model-facing tool names.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum ToolNetworkAccess {
29    Local,
30    Network,
31    #[default]
32    Unknown,
33}
34
35#[derive(Debug, Clone, Default)]
36pub struct ToolRegistrationSpec {
37    network_access: ToolNetworkAccess,
38    description: Option<String>,
39    parameter_schema: Option<Value>,
40    config_schema: Option<Value>,
41    state_schema: Option<Value>,
42    prompt_path: Option<String>,
43    default_permission: Option<ToolPolicy>,
44    allowlist: Vec<String>,
45    denylist: Vec<String>,
46    aliases: Vec<String>,
47    server_hint: Option<String>,
48    behavior: Option<ToolBehavior>,
49}
50
51impl ToolRegistrationSpec {
52    pub fn network_access(&self) -> ToolNetworkAccess {
53        self.network_access
54    }
55    pub fn with_description(mut self, description: impl Into<String>) -> Self {
56        self.description = Some(description.into());
57        self
58    }
59
60    pub fn with_parameter_schema(mut self, schema: Value) -> Self {
61        self.parameter_schema = Some(schema);
62        self
63    }
64
65    pub fn with_config_schema(mut self, schema: Value) -> Self {
66        self.config_schema = Some(schema);
67        self
68    }
69
70    pub fn with_state_schema(mut self, schema: Value) -> Self {
71        self.state_schema = Some(schema);
72        self
73    }
74
75    pub fn with_prompt_path(mut self, path: impl Into<String>) -> Self {
76        self.prompt_path = Some(path.into());
77        self
78    }
79
80    pub fn with_permission(mut self, permission: ToolPolicy) -> Self {
81        self.default_permission = Some(permission);
82        self
83    }
84
85    pub fn with_allowlist(mut self, patterns: impl IntoIterator<Item = impl Into<String>>) -> Self {
86        self.allowlist.extend(patterns.into_iter().map(Into::into));
87        self
88    }
89
90    pub fn with_denylist(mut self, patterns: impl IntoIterator<Item = impl Into<String>>) -> Self {
91        self.denylist.extend(patterns.into_iter().map(Into::into));
92        self
93    }
94
95    pub fn with_aliases(mut self, aliases: impl IntoIterator<Item = impl Into<String>>) -> Self {
96        self.aliases.extend(aliases.into_iter().map(Into::into));
97        self
98    }
99
100    pub fn with_server_hint(mut self, hint: impl Into<String>) -> Self {
101        self.server_hint = Some(hint.into());
102        self
103    }
104
105    pub fn with_behavior(mut self, behavior: ToolBehavior) -> Self {
106        self.behavior = Some(behavior);
107        self
108    }
109
110    pub fn description(&self) -> Option<&str> {
111        self.description.as_deref()
112    }
113
114    pub fn parameter_schema(&self) -> Option<&Value> {
115        self.parameter_schema.as_ref()
116    }
117
118    pub fn config_schema(&self) -> Option<&Value> {
119        self.config_schema.as_ref()
120    }
121
122    pub fn state_schema(&self) -> Option<&Value> {
123        self.state_schema.as_ref()
124    }
125
126    pub fn prompt_path(&self) -> Option<&str> {
127        self.prompt_path.as_deref()
128    }
129
130    pub fn default_permission(&self) -> Option<ToolPolicy> {
131        self.default_permission.clone()
132    }
133
134    pub fn allowlist(&self) -> &[String] {
135        &self.allowlist
136    }
137
138    pub fn denylist(&self) -> &[String] {
139        &self.denylist
140    }
141
142    pub fn aliases(&self) -> &[String] {
143        &self.aliases
144    }
145
146    pub fn server_hint(&self) -> Option<&str> {
147        self.server_hint.as_deref()
148    }
149
150    pub fn behavior(&self) -> Option<ToolBehavior> {
151        self.behavior
152    }
153}
154
155#[derive(Clone)]
156pub enum ToolHandler {
157    RegistryFn(ToolExecutorFn),
158    TraitObject(Arc<dyn Tool>),
159}
160
161impl fmt::Debug for ToolHandler {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        match self {
164            ToolHandler::RegistryFn(_) => write!(f, "ToolHandler::RegistryFn"),
165            ToolHandler::TraitObject(_) => write!(f, "ToolHandler::TraitObject"),
166        }
167    }
168}
169
170#[derive(Clone)]
171pub struct ToolRegistration {
172    name: Arc<str>,
173    capability: CapabilityLevel,
174    catalog_source: ToolCatalogSource,
175    uses_pty: bool,
176    expose_in_llm: bool,
177    deprecated: bool,
178    deprecation_message: Option<String>,
179    cgp_wrapped: bool,
180    handler: ToolHandler,
181    metadata: ToolRegistrationSpec,
182    native_cgp_factory: Option<NativeCgpToolFactory>,
183}
184
185impl fmt::Debug for ToolRegistration {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        f.debug_struct("ToolRegistration")
188            .field("name", &self.name)
189            .field("capability", &self.capability)
190            .field("catalog_source", &self.catalog_source)
191            .field("uses_pty", &self.uses_pty)
192            .field("expose_in_llm", &self.expose_in_llm)
193            .field("deprecated", &self.deprecated)
194            .field("deprecation_message", &self.deprecation_message)
195            .field("cgp_wrapped", &self.cgp_wrapped)
196            .field("handler", &self.handler)
197            .field("metadata", &self.metadata)
198            .field("has_native_cgp_factory", &self.native_cgp_factory.is_some())
199            .finish()
200    }
201}
202
203impl ToolRegistration {
204    pub fn with_network_access(mut self, access: ToolNetworkAccess) -> Self {
205        self.metadata.network_access = access;
206        self
207    }
208    pub fn new(
209        name: impl Into<Arc<str>>,
210        capability: CapabilityLevel,
211        uses_pty: bool,
212        executor: ToolExecutorFn,
213    ) -> Self {
214        Self {
215            name: name.into(),
216            capability,
217            catalog_source: ToolCatalogSource::Dynamic,
218            uses_pty,
219            expose_in_llm: true,
220            deprecated: false,
221            deprecation_message: None,
222            cgp_wrapped: false,
223            handler: ToolHandler::RegistryFn(executor),
224            metadata: ToolRegistrationSpec::default(),
225            native_cgp_factory: None,
226        }
227    }
228
229    pub fn from_tool(name: impl Into<Arc<str>>, capability: CapabilityLevel, tool: Arc<dyn Tool>) -> Self {
230        let mut metadata = ToolRegistrationSpec::default().with_description(tool.description());
231        if let Some(schema) = tool.parameter_schema() {
232            metadata = metadata.with_parameter_schema(schema);
233        }
234        if let Some(schema) = tool.config_schema() {
235            metadata = metadata.with_config_schema(schema);
236        }
237        if let Some(schema) = tool.state_schema() {
238            metadata = metadata.with_state_schema(schema);
239        }
240        if let Some(path) = tool.prompt_path() {
241            metadata = metadata.with_prompt_path(path.into_owned());
242        }
243        metadata = metadata.with_permission(tool.default_permission());
244        if let Some(patterns) = tool.allow_patterns() {
245            metadata = metadata.with_allowlist(patterns.iter().copied());
246        }
247        if let Some(patterns) = tool.deny_patterns() {
248            metadata = metadata.with_denylist(patterns.iter().copied());
249        }
250
251        Self::from_tool_with_metadata(name, capability, tool, metadata)
252    }
253
254    pub fn from_tool_with_metadata(
255        name: impl Into<Arc<str>>,
256        capability: CapabilityLevel,
257        tool: Arc<dyn Tool>,
258        metadata: ToolRegistrationSpec,
259    ) -> Self {
260        Self {
261            name: name.into(),
262            capability,
263            catalog_source: ToolCatalogSource::Dynamic,
264            uses_pty: false,
265            expose_in_llm: true,
266            deprecated: false,
267            deprecation_message: None,
268            cgp_wrapped: false,
269            handler: ToolHandler::TraitObject(tool),
270            metadata,
271            native_cgp_factory: None,
272        }
273    }
274
275    pub fn from_tool_instance<T>(name: impl Into<Arc<str>>, capability: CapabilityLevel, tool: T) -> Self
276    where
277        T: Tool + 'static,
278    {
279        Self::from_tool(name, capability, Arc::new(tool))
280    }
281
282    /// Register a tool wrapped with a CGP runtime context.
283    ///
284    /// Wraps an existing `Arc<dyn Tool>` in a CGP `ToolFacade` with the
285    /// specified runtime context's approval, metadata, sandbox, logging,
286    /// cache, and retry providers.
287    ///
288    /// Prefer `wrap_native_tool_interactive()` or `wrap_native_tool_ci()` when
289    /// the caller still owns the concrete tool instance. Use this bridge when the
290    /// tool already has genuine shared ownership.
291    ///
292    /// # Example
293    /// ```rust,ignore
294    /// use vtcode_core::components::{InteractiveCtx, ToolBridgeCtx, wrap_tool_interactive};
295    ///
296    /// let tool: Arc<dyn Tool> = Arc::new(MyTool);
297    /// let reg = ToolRegistration::from_cgp_tool(
298    ///     "my_tool",
299    ///     CapabilityLevel::Basic,
300    ///     wrap_tool_interactive(tool, workspace_root),
301    /// );
302    /// ```
303    pub fn from_cgp_tool<Ctx>(
304        name: impl Into<Arc<str>>,
305        capability: CapabilityLevel,
306        facade: crate::components::ToolFacade<Ctx>,
307    ) -> Self
308    where
309        crate::components::ToolFacade<Ctx>: Tool + 'static,
310    {
311        Self::from_tool_instance(name, capability, facade).with_cgp_wrapped(true)
312    }
313
314    pub fn with_llm_visibility(mut self, expose: bool) -> Self {
315        self.expose_in_llm = expose;
316        self
317    }
318
319    pub fn with_catalog_source(mut self, catalog_source: ToolCatalogSource) -> Self {
320        self.catalog_source = catalog_source;
321        self
322    }
323
324    pub fn with_pty(mut self, uses_pty: bool) -> Self {
325        self.uses_pty = uses_pty;
326        self
327    }
328
329    pub fn with_deprecated(mut self, deprecated: bool) -> Self {
330        self.deprecated = deprecated;
331        self
332    }
333
334    pub fn with_deprecation_message(mut self, message: impl Into<String>) -> Self {
335        self.deprecation_message = Some(message.into());
336        self
337    }
338
339    pub fn with_cgp_wrapped(mut self, wrapped: bool) -> Self {
340        self.cgp_wrapped = wrapped;
341        self
342    }
343
344    pub fn with_handler(mut self, handler: ToolHandler) -> Self {
345        self.handler = handler;
346        self
347    }
348
349    pub fn with_native_cgp_factory(mut self, factory: NativeCgpToolFactory) -> Self {
350        self.native_cgp_factory = Some(factory);
351        self
352    }
353
354    pub fn name(&self) -> &str {
355        self.name.as_ref()
356    }
357
358    /// Returns the capability level of this tool.
359    pub fn capability(&self) -> CapabilityLevel {
360        self.capability
361    }
362
363    /// Returns the catalog source of this tool.
364    pub fn catalog_source(&self) -> ToolCatalogSource {
365        self.catalog_source
366    }
367
368    /// Returns whether this tool uses a PTY.
369    pub fn uses_pty(&self) -> bool {
370        self.uses_pty
371    }
372
373    /// Returns whether this tool is visible to the LLM.
374    pub fn expose_in_llm(&self) -> bool {
375        self.expose_in_llm
376    }
377
378    /// Returns whether this tool is deprecated.
379    pub fn is_deprecated(&self) -> bool {
380        self.deprecated
381    }
382
383    /// Returns the deprecation message if this tool is deprecated.
384    pub fn deprecation_message(&self) -> Option<&str> {
385        self.deprecation_message.as_deref()
386    }
387
388    /// Returns whether this tool is CGP-wrapped.
389    pub fn is_cgp_wrapped(&self) -> bool {
390        self.cgp_wrapped
391    }
392
393    /// Returns the tool handler implementation.
394    pub fn handler(&self) -> ToolHandler {
395        self.handler.clone()
396    }
397
398    /// Returns the native CGP factory if set.
399    pub fn native_cgp_factory(&self) -> Option<NativeCgpToolFactory> {
400        self.native_cgp_factory.clone()
401    }
402
403    /// Returns a reference to the tool registration metadata.
404    pub fn metadata(&self) -> &ToolRegistrationSpec {
405        &self.metadata
406    }
407
408    /// Returns the parameter JSON schema (delegates to metadata).
409    pub fn parameter_schema(&self) -> Option<&Value> {
410        self.metadata.parameter_schema()
411    }
412
413    /// Returns the configuration JSON schema (delegates to metadata).
414    pub fn config_schema(&self) -> Option<&Value> {
415        self.metadata.config_schema()
416    }
417
418    /// Returns the state JSON schema (delegates to metadata).
419    pub fn state_schema(&self) -> Option<&Value> {
420        self.metadata.state_schema()
421    }
422
423    /// Returns the prompt file path (delegates to metadata).
424    pub fn prompt_path(&self) -> Option<&str> {
425        self.metadata.prompt_path()
426    }
427
428    /// Returns the default permission policy (delegates to metadata).
429    pub fn default_permission(&self) -> Option<ToolPolicy> {
430        self.metadata.default_permission()
431    }
432
433    /// Replaces the tool registration metadata.
434    pub fn with_metadata(mut self, metadata: ToolRegistrationSpec) -> Self {
435        self.metadata = metadata;
436        self
437    }
438
439    /// Sets the tool description (delegates to metadata).
440    pub fn with_description(mut self, description: impl Into<String>) -> Self {
441        self.metadata = self.metadata.with_description(description);
442        self
443    }
444
445    /// Sets the prompt file path (delegates to metadata).
446    pub fn with_prompt_path(mut self, path: impl Into<String>) -> Self {
447        self.metadata = self.metadata.with_prompt_path(path);
448        self
449    }
450
451    /// Sets the parameter JSON schema (delegates to metadata).
452    pub fn with_parameter_schema(mut self, schema: Value) -> Self {
453        self.metadata = self.metadata.with_parameter_schema(schema);
454        self
455    }
456
457    /// Sets the configuration JSON schema (delegates to metadata).
458    pub fn with_config_schema(mut self, schema: Value) -> Self {
459        self.metadata = self.metadata.with_config_schema(schema);
460        self
461    }
462
463    /// Sets the state JSON schema (delegates to metadata).
464    pub fn with_state_schema(mut self, schema: Value) -> Self {
465        self.metadata = self.metadata.with_state_schema(schema);
466        self
467    }
468
469    /// Sets the default permission policy (delegates to metadata).
470    pub fn with_permission(mut self, permission: ToolPolicy) -> Self {
471        self.metadata = self.metadata.with_permission(permission);
472        self
473    }
474
475    /// Adds glob patterns to the allowlist (delegates to metadata).
476    pub fn with_allowlist(mut self, patterns: impl IntoIterator<Item = impl Into<String>>) -> Self {
477        self.metadata = self.metadata.with_allowlist(patterns);
478        self
479    }
480
481    /// Adds glob patterns to the denylist (delegates to metadata).
482    pub fn with_denylist(mut self, patterns: impl IntoIterator<Item = impl Into<String>>) -> Self {
483        self.metadata = self.metadata.with_denylist(patterns);
484        self
485    }
486
487    /// Adds alternative names for the tool (delegates to metadata).
488    pub fn with_aliases(mut self, aliases: impl IntoIterator<Item = impl Into<String>>) -> Self {
489        self.metadata = self.metadata.with_aliases(aliases);
490        self
491    }
492
493    /// Sets the MCP server hint (delegates to metadata).
494    pub fn with_server_hint(mut self, hint: impl Into<String>) -> Self {
495        self.metadata = self.metadata.with_server_hint(hint);
496        self
497    }
498
499    /// Sets the tool behavior classification (delegates to metadata).
500    pub fn with_behavior(mut self, behavior: ToolBehavior) -> Self {
501        self.metadata = self.metadata.with_behavior(behavior);
502        self
503    }
504}