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