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 semantic code 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.extend(
255            other
256                .tool_metrics
257                .iter()
258                .map(|(k, v)| (k.clone(), v.clone())),
259        );
260    }
261}
262
263/// Enhanced tool result with metadata
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct EnhancedToolResult {
266    /// The actual tool result
267    pub value: Value,
268
269    /// Quality metadata
270    pub metadata: ResultMetadata,
271
272    /// When result was produced
273    pub timestamp: u64,
274
275    /// Tool name that produced this
276    pub tool_name: CompactStr,
277
278    /// Whether this was from cache
279    #[serde(default)]
280    pub from_cache: bool,
281}
282
283impl EnhancedToolResult {
284    pub fn new(value: Value, metadata: ResultMetadata, tool_name: impl Into<CompactStr>) -> Self {
285        Self {
286            value,
287            metadata,
288            timestamp: SystemTime::now()
289                .duration_since(SystemTime::UNIX_EPOCH)
290                .unwrap_or_default()
291                .as_secs(),
292            tool_name: tool_name.into(),
293            from_cache: false,
294        }
295    }
296
297    pub fn from_cache(
298        value: Value,
299        metadata: ResultMetadata,
300        tool_name: impl Into<CompactStr>,
301    ) -> Self {
302        Self {
303            value,
304            metadata,
305            timestamp: SystemTime::now()
306                .duration_since(SystemTime::UNIX_EPOCH)
307                .unwrap_or_default()
308                .as_secs(),
309            tool_name: tool_name.into(),
310            from_cache: true,
311        }
312    }
313
314    /// Whether this result is useful enough to include
315    #[inline]
316    pub fn is_useful(&self) -> bool {
317        self.metadata.quality_score() > 0.3
318    }
319
320    /// Whether this result is high quality
321    #[inline]
322    pub fn is_high_quality(&self) -> bool {
323        self.metadata.quality_score() > 0.7
324    }
325
326    /// Convert to a message-friendly format
327    #[allow(clippy::cast_sign_loss)] // quality_score is always 0.0-1.0
328    pub fn to_summary(&self) -> String {
329        let quality = ((self.metadata.quality_score() * 100.0).round().max(0.0) as u32).min(100);
330        match self.metadata.completeness {
331            ResultCompleteness::Complete => {
332                format!(
333                    "{} found {} results (confidence: {}%)",
334                    self.tool_name, self.metadata.result_count, quality
335                )
336            }
337            ResultCompleteness::Partial => {
338                format!(
339                    "{} found {} results (truncated, confidence: {}%)",
340                    self.tool_name, self.metadata.result_count, quality
341                )
342            }
343            ResultCompleteness::Empty => {
344                format!("{} found no results", self.tool_name)
345            }
346            ResultCompleteness::Truncated => {
347                format!(
348                    "{} found results (truncated due to size, confidence: {}%)",
349                    self.tool_name, quality
350                )
351            }
352        }
353    }
354}
355
356/// Trait for scoring tool results
357pub trait ResultScorer {
358    /// Score a tool result and return metadata
359    fn score(&self, result: &Value) -> ResultMetadata;
360
361    /// Tool name this scorer handles
362    fn tool_name(&self) -> &str;
363}