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] = &["connection", "timeout", "network", "http", "ssl", "tls", "dns", "proxy"];
71
72/// Default capacity hints for common collections
73pub const DEFAULT_VEC_CAPACITY: usize = 32;
74pub const DEFAULT_HASHMAP_CAPACITY: usize = 16;
75pub const DEFAULT_STRING_CAPACITY: usize = 256;
76
77/// Context optimization constants following AGENTS.md guidelines
78pub const MAX_SEARCH_RESULTS: usize = 5;
79pub const MAX_LIST_ITEMS_SUMMARY: usize = 5;
80pub const OVERFLOW_INDICATOR_PREFIX: &str = "[+]";
81pub const OVERFLOW_INDICATOR_SUFFIX: &str = "more items]";
82
83/// Common tool operation limits
84pub const MAX_FILE_SIZE_FOR_PROCESSING: usize = 100 * 1024 * 1024; // 100MB
85pub const MAX_CONTEXT_LINES: usize = 20;
86pub const MAX_OUTPUT_TOKENS: usize = 4000;
87
88/// Reusable empty JSON object schema `{"type": "object"}` for tool parameter definitions.
89/// Used by tools that accept no parameters or only optional parameters.
90pub fn empty_object_schema() -> Value {
91    serde_json::json!({"type": "object"})
92}
93
94// ---------------------------------------------------------------------------
95// Tool result metadata types
96// ---------------------------------------------------------------------------
97
98use hashbrown::HashMap;
99use serde::{Deserialize, Serialize};
100use serde_json::Value;
101use std::fmt;
102use std::time::SystemTime;
103
104/// Result completeness level
105#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
106pub enum ResultCompleteness {
107    /// Full result with no truncation
108    Complete,
109    /// Partial result (more data exists but not shown)
110    Partial,
111    /// Result truncated due to size limits
112    Truncated,
113    /// Empty result (no matches)
114    Empty,
115}
116
117impl ResultCompleteness {
118    /// Deprecated: prefer using the `Display` impl; `ToString` is derived from Display.
119    pub fn to_static_str(&self) -> &'static str {
120        match self {
121            Self::Complete => "complete",
122            Self::Partial => "partial",
123            Self::Truncated => "truncated",
124            Self::Empty => "empty",
125        }
126    }
127}
128
129impl fmt::Display for ResultCompleteness {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        f.write_str(self.to_static_str())
132    }
133}
134
135/// Quality metadata for tool results
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct ResultMetadata {
138    /// Confidence that result is correct (0.0-1.0)
139    #[serde(default = "default_confidence")]
140    pub confidence: f32,
141
142    /// Relevance to current task (0.0-1.0)
143    #[serde(default = "default_relevance")]
144    pub relevance: f32,
145
146    /// Result completeness level
147    pub completeness: ResultCompleteness,
148
149    /// Count of matches/results
150    #[serde(default)]
151    pub result_count: usize,
152
153    /// Likelihood of false positives (0.0-1.0)
154    #[serde(default)]
155    pub false_positive_likelihood: f32,
156
157    /// Detected content types (code, docs, config, binary, etc.)
158    #[serde(default)]
159    pub content_types: Vec<String>,
160
161    /// Tool-specific metrics (lines matched, execution time, etc.)
162    #[serde(default)]
163    pub tool_metrics: HashMap<String, Value>,
164}
165
166fn default_confidence() -> f32 {
167    0.5
168}
169
170fn default_relevance() -> f32 {
171    0.5
172}
173
174impl Default for ResultMetadata {
175    fn default() -> Self {
176        Self {
177            confidence: 0.5,
178            relevance: 0.5,
179            completeness: ResultCompleteness::Complete,
180            result_count: 0,
181            false_positive_likelihood: 0.1,
182            content_types: vec![],
183            tool_metrics: HashMap::new(),
184        }
185    }
186}
187
188impl ResultMetadata {
189    /// Overall quality score (0.0-1.0)
190    #[inline]
191    pub fn quality_score(&self) -> f32 {
192        let weighted = (self.confidence * 0.4) + (self.relevance * 0.4) + (self.false_positive_likelihood * -0.2);
193        weighted.clamp(0.0, 1.0)
194    }
195
196    /// Create metadata for a successful tool execution
197    #[inline]
198    pub fn success(confidence: f32, relevance: f32) -> Self {
199        Self {
200            confidence: confidence.clamp(0.0, 1.0),
201            relevance: relevance.clamp(0.0, 1.0),
202            completeness: ResultCompleteness::Complete,
203            result_count: 1,
204            false_positive_likelihood: 0.05,
205            ..Default::default()
206        }
207    }
208
209    /// Create metadata for empty results
210    #[inline]
211    pub fn empty() -> Self {
212        Self {
213            completeness: ResultCompleteness::Empty,
214            result_count: 0,
215            confidence: 1.0, // High confidence in "no results"
216            ..Default::default()
217        }
218    }
219
220    /// Create metadata for error/inconclusive results
221    pub fn error() -> Self {
222        Self {
223            confidence: 0.2,
224            completeness: ResultCompleteness::Empty,
225            ..Default::default()
226        }
227    }
228
229    /// Merge with another metadata (for combining results)
230    pub fn merge(&mut self, other: &ResultMetadata) {
231        self.result_count += other.result_count;
232        self.confidence = (self.confidence + other.confidence) / 2.0;
233        self.relevance = (self.relevance + other.relevance) / 2.0;
234
235        // Merge content types
236        for ct in &other.content_types {
237            if !self.content_types.contains(ct) {
238                self.content_types.push(ct.clone());
239            }
240        }
241
242        // Merge tool metrics - use extend to avoid double clone
243        self.tool_metrics
244            .extend(other.tool_metrics.iter().map(|(k, v)| (k.clone(), v.clone())));
245    }
246}
247
248/// Enhanced tool result with metadata
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct EnhancedToolResult {
251    /// The actual tool result
252    pub value: Value,
253
254    /// Quality metadata
255    pub metadata: ResultMetadata,
256
257    /// When result was produced
258    pub timestamp: u64,
259
260    /// Tool name that produced this
261    pub tool_name: CompactStr,
262
263    /// Whether this was from cache
264    #[serde(default)]
265    pub from_cache: bool,
266}
267
268impl EnhancedToolResult {
269    pub fn new(value: Value, metadata: ResultMetadata, tool_name: impl Into<CompactStr>) -> Self {
270        Self {
271            value,
272            metadata,
273            timestamp: SystemTime::now()
274                .duration_since(SystemTime::UNIX_EPOCH)
275                .unwrap_or_default()
276                .as_secs(),
277            tool_name: tool_name.into(),
278            from_cache: false,
279        }
280    }
281
282    pub fn from_cache(value: Value, metadata: ResultMetadata, tool_name: impl Into<CompactStr>) -> Self {
283        Self {
284            value,
285            metadata,
286            timestamp: SystemTime::now()
287                .duration_since(SystemTime::UNIX_EPOCH)
288                .unwrap_or_default()
289                .as_secs(),
290            tool_name: tool_name.into(),
291            from_cache: true,
292        }
293    }
294
295    /// Whether this result is useful enough to include
296    #[inline]
297    pub fn is_useful(&self) -> bool {
298        self.metadata.quality_score() > 0.3
299    }
300
301    /// Whether this result is high quality
302    #[inline]
303    pub fn is_high_quality(&self) -> bool {
304        self.metadata.quality_score() > 0.7
305    }
306
307    /// Convert to a message-friendly format
308    #[allow(clippy::cast_sign_loss)] // quality_score is always 0.0-1.0
309    pub fn to_summary(&self) -> String {
310        let quality = ((self.metadata.quality_score() * 100.0).round().max(0.0) as u32).min(100);
311        match self.metadata.completeness {
312            ResultCompleteness::Complete => {
313                format!("{} found {} results (confidence: {}%)", self.tool_name, self.metadata.result_count, quality)
314            }
315            ResultCompleteness::Partial => {
316                format!(
317                    "{} found {} results (truncated, confidence: {}%)",
318                    self.tool_name, self.metadata.result_count, quality
319                )
320            }
321            ResultCompleteness::Empty => {
322                format!("{} found no results", self.tool_name)
323            }
324            ResultCompleteness::Truncated => {
325                format!("{} found results (truncated due to size, confidence: {}%)", self.tool_name, quality)
326            }
327        }
328    }
329}
330
331/// Trait for scoring tool results
332pub trait ResultScorer {
333    /// Score a tool result and return metadata
334    fn score(&self, result: &Value) -> ResultMetadata;
335
336    /// Tool name this scorer handles
337    fn tool_name(&self) -> &str;
338}