Skip to main content

vtcode_commons/
tool_types.rs

1//! Shared runtime types for the VT Code tool system.
2//!
3//! This module provides types shared between the LLM and tools subsystems,
4//! breaking the circular dependency that would otherwise exist between them.
5//!
6//! # Overview
7//!
8//! The key types here are:
9//! - [`CompactStr`] - stack-allocated string for short tool names
10//! - [`EnhancedToolResult`] - tool result with quality metadata
11//! - [`ResultMetadata`] - quality/confidence scoring for tool results
12//! - [`tool_names`] - tool name constants used across subsystems
13
14// ---------------------------------------------------------------------------
15// CompactStr type alias
16// ---------------------------------------------------------------------------
17
18/// Compact inline string -- stack-allocated for strings up to 24 bytes.
19/// Drop-in replacement for `String` with zero heap allocation for short strings.
20pub type CompactStr = compact_str::CompactString;
21
22// ---------------------------------------------------------------------------
23// Tool name constants (canonical names used across subsystems)
24// ---------------------------------------------------------------------------
25
26/// Canonical tool name constants used by both LLM and tools subsystems.
27/// These match the values defined in `vtcode-config::constants::tools`.
28pub mod tool_names {
29    /// Advanced bounded source search tool
30    pub const CODE_SEARCH: &str = "code_search";
31    /// Shell command execution tool
32    pub const EXEC_COMMAND: &str = "exec_command";
33}
34
35/// Use direct tool name without alias resolution.
36/// Alias resolution is now handled by the tool registry inventory
37/// which maintains a mapping of aliases to canonical tool names.
38pub const fn canonical_tool_name(name: &str) -> &str {
39    name
40}
41
42// ---------------------------------------------------------------------------
43// Operational constants shared across subsystems
44// ---------------------------------------------------------------------------
45
46/// Standard error patterns used for error detection across tools
47pub const ERROR_DETECTION_PATTERNS: &[&str] = &[
48    "error",
49    "failed",
50    "exception",
51    "permission denied",
52    "not found",
53    "no such file",
54    "cannot",
55    "could not",
56    "panic",
57    "crash",
58    "unhandled",
59    "fatal",
60    "timeout",
61    "connection refused",
62    "access denied",
63    "stack trace",
64    "traceback",
65    "abort",
66    "terminate",
67];
68
69/// Network-related error patterns for more specific error detection
70pub const NETWORK_ERROR_PATTERNS: &[&str] = &[
71    "connection",
72    "timeout",
73    "network",
74    "http",
75    "ssl",
76    "tls",
77    "dns",
78    "proxy",
79];
80
81/// Default capacity hints for common collections
82pub const DEFAULT_VEC_CAPACITY: usize = 32;
83pub const DEFAULT_HASHMAP_CAPACITY: usize = 16;
84pub const DEFAULT_STRING_CAPACITY: usize = 256;
85
86/// Context optimization constants following AGENTS.md guidelines
87pub const MAX_SEARCH_RESULTS: usize = 5;
88pub const MAX_LIST_ITEMS_SUMMARY: usize = 5;
89pub const OVERFLOW_INDICATOR_PREFIX: &str = "[+]";
90pub const OVERFLOW_INDICATOR_SUFFIX: &str = "more items]";
91
92/// Common tool operation limits
93pub const MAX_FILE_SIZE_FOR_PROCESSING: usize = 100 * 1024 * 1024; // 100MB
94pub const MAX_CONTEXT_LINES: usize = 20;
95pub const MAX_OUTPUT_TOKENS: usize = 4000;
96
97/// Reusable empty JSON object schema `{"type": "object"}` for tool parameter definitions.
98/// Used by tools that accept no parameters or only optional parameters.
99pub fn empty_object_schema() -> Value {
100    serde_json::json!({"type": "object"})
101}
102
103// ---------------------------------------------------------------------------
104// Tool result metadata types
105// ---------------------------------------------------------------------------
106
107use hashbrown::HashMap;
108use serde::{Deserialize, Serialize};
109use serde_json::Value;
110use std::fmt;
111use std::time::SystemTime;
112
113/// Result completeness level
114#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
115pub enum ResultCompleteness {
116    /// Full result with no truncation
117    Complete,
118    /// Partial result (more data exists but not shown)
119    Partial,
120    /// Result truncated due to size limits
121    Truncated,
122    /// Empty result (no matches)
123    Empty,
124}
125
126impl ResultCompleteness {
127    /// Deprecated: prefer using the `Display` impl; `ToString` is derived from Display.
128    pub fn to_static_str(&self) -> &'static str {
129        match self {
130            Self::Complete => "complete",
131            Self::Partial => "partial",
132            Self::Truncated => "truncated",
133            Self::Empty => "empty",
134        }
135    }
136}
137
138impl fmt::Display for ResultCompleteness {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        f.write_str(self.to_static_str())
141    }
142}
143
144/// Quality metadata for tool results
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct ResultMetadata {
147    /// Confidence that result is correct (0.0-1.0)
148    #[serde(default = "default_confidence")]
149    pub confidence: f32,
150
151    /// Relevance to current task (0.0-1.0)
152    #[serde(default = "default_relevance")]
153    pub relevance: f32,
154
155    /// Result completeness level
156    pub completeness: ResultCompleteness,
157
158    /// Count of matches/results
159    #[serde(default)]
160    pub result_count: usize,
161
162    /// Likelihood of false positives (0.0-1.0)
163    #[serde(default)]
164    pub false_positive_likelihood: f32,
165
166    /// Detected content types (code, docs, config, binary, etc.)
167    #[serde(default)]
168    pub content_types: Vec<String>,
169
170    /// Tool-specific metrics (lines matched, execution time, etc.)
171    #[serde(default)]
172    pub tool_metrics: HashMap<String, Value>,
173}
174
175fn default_confidence() -> f32 {
176    0.5
177}
178
179fn default_relevance() -> f32 {
180    0.5
181}
182
183impl Default for ResultMetadata {
184    fn default() -> Self {
185        Self {
186            confidence: 0.5,
187            relevance: 0.5,
188            completeness: ResultCompleteness::Complete,
189            result_count: 0,
190            false_positive_likelihood: 0.1,
191            content_types: vec![],
192            tool_metrics: HashMap::new(),
193        }
194    }
195}
196
197impl ResultMetadata {
198    /// Overall quality score (0.0-1.0)
199    #[inline]
200    pub fn quality_score(&self) -> f32 {
201        let weighted = (self.confidence * 0.4)
202            + (self.relevance * 0.4)
203            + (self.false_positive_likelihood * -0.2);
204        weighted.clamp(0.0, 1.0)
205    }
206
207    /// Create metadata for a successful tool execution
208    #[inline]
209    pub fn success(confidence: f32, relevance: f32) -> Self {
210        Self {
211            confidence: confidence.clamp(0.0, 1.0),
212            relevance: relevance.clamp(0.0, 1.0),
213            completeness: ResultCompleteness::Complete,
214            result_count: 1,
215            false_positive_likelihood: 0.05,
216            ..Default::default()
217        }
218    }
219
220    /// Create metadata for empty results
221    #[inline]
222    pub fn empty() -> Self {
223        Self {
224            completeness: ResultCompleteness::Empty,
225            result_count: 0,
226            confidence: 1.0, // High confidence in "no results"
227            ..Default::default()
228        }
229    }
230
231    /// Create metadata for error/inconclusive results
232    pub fn error() -> Self {
233        Self {
234            confidence: 0.2,
235            completeness: ResultCompleteness::Empty,
236            ..Default::default()
237        }
238    }
239
240    /// Merge with another metadata (for combining results)
241    pub fn merge(&mut self, other: &ResultMetadata) {
242        self.result_count += other.result_count;
243        self.confidence = (self.confidence + other.confidence) / 2.0;
244        self.relevance = (self.relevance + other.relevance) / 2.0;
245
246        // Merge content types
247        for ct in &other.content_types {
248            if !self.content_types.contains(ct) {
249                self.content_types.push(ct.clone());
250            }
251        }
252
253        // Merge tool metrics - use extend to avoid double clone
254        self.tool_metrics
255            .extend(other.tool_metrics.iter().map(|(k, v)| (k.clone(), v.clone())));
256    }
257}
258
259/// Enhanced tool result with metadata
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct EnhancedToolResult {
262    /// The actual tool result
263    pub value: Value,
264
265    /// Quality metadata
266    pub metadata: ResultMetadata,
267
268    /// When result was produced
269    pub timestamp: u64,
270
271    /// Tool name that produced this
272    pub tool_name: CompactStr,
273
274    /// Whether this was from cache
275    #[serde(default)]
276    pub from_cache: bool,
277}
278
279impl EnhancedToolResult {
280    pub fn new(value: Value, metadata: ResultMetadata, tool_name: impl Into<CompactStr>) -> Self {
281        Self {
282            value,
283            metadata,
284            timestamp: SystemTime::now()
285                .duration_since(SystemTime::UNIX_EPOCH)
286                .unwrap_or_default()
287                .as_secs(),
288            tool_name: tool_name.into(),
289            from_cache: false,
290        }
291    }
292
293    pub fn from_cache(
294        value: Value,
295        metadata: ResultMetadata,
296        tool_name: impl Into<CompactStr>,
297    ) -> Self {
298        Self {
299            value,
300            metadata,
301            timestamp: SystemTime::now()
302                .duration_since(SystemTime::UNIX_EPOCH)
303                .unwrap_or_default()
304                .as_secs(),
305            tool_name: tool_name.into(),
306            from_cache: true,
307        }
308    }
309
310    /// Whether this result is useful enough to include
311    #[inline]
312    pub fn is_useful(&self) -> bool {
313        self.metadata.quality_score() > 0.3
314    }
315
316    /// Whether this result is high quality
317    #[inline]
318    pub fn is_high_quality(&self) -> bool {
319        self.metadata.quality_score() > 0.7
320    }
321
322    /// Convert to a message-friendly format
323    #[allow(clippy::cast_sign_loss)] // quality_score is always 0.0-1.0
324    pub fn to_summary(&self) -> String {
325        let quality = ((self.metadata.quality_score() * 100.0).round().max(0.0) as u32).min(100);
326        match self.metadata.completeness {
327            ResultCompleteness::Complete => {
328                format!(
329                    "{} found {} results (confidence: {}%)",
330                    self.tool_name, self.metadata.result_count, quality
331                )
332            }
333            ResultCompleteness::Partial => {
334                format!(
335                    "{} found {} results (truncated, confidence: {}%)",
336                    self.tool_name, self.metadata.result_count, quality
337                )
338            }
339            ResultCompleteness::Empty => {
340                format!("{} found no results", self.tool_name)
341            }
342            ResultCompleteness::Truncated => {
343                format!(
344                    "{} found results (truncated due to size, confidence: {}%)",
345                    self.tool_name, quality
346                )
347            }
348        }
349    }
350}
351
352/// Trait for scoring tool results
353pub trait ResultScorer {
354    /// Score a tool result and return metadata
355    fn score(&self, result: &Value) -> ResultMetadata;
356
357    /// Tool name this scorer handles
358    fn tool_name(&self) -> &str;
359}