1pub type CompactStr = compact_str::CompactString;
21
22pub mod tool_names {
29 pub const CODE_SEARCH: &str = "code_search";
31 pub const EXEC_COMMAND: &str = "exec_command";
33}
34
35pub const fn canonical_tool_name(name: &str) -> &str {
39 name
40}
41
42pub 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
69pub const NETWORK_ERROR_PATTERNS: &[&str] = &[
71 "connection",
72 "timeout",
73 "network",
74 "http",
75 "ssl",
76 "tls",
77 "dns",
78 "proxy",
79];
80
81pub const DEFAULT_VEC_CAPACITY: usize = 32;
83pub const DEFAULT_HASHMAP_CAPACITY: usize = 16;
84pub const DEFAULT_STRING_CAPACITY: usize = 256;
85
86pub 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
92pub const MAX_FILE_SIZE_FOR_PROCESSING: usize = 100 * 1024 * 1024; pub const MAX_CONTEXT_LINES: usize = 20;
95pub const MAX_OUTPUT_TOKENS: usize = 4000;
96
97pub fn empty_object_schema() -> Value {
100 serde_json::json!({"type": "object"})
101}
102
103use hashbrown::HashMap;
108use serde::{Deserialize, Serialize};
109use serde_json::Value;
110use std::fmt;
111use std::time::SystemTime;
112
113#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
115pub enum ResultCompleteness {
116 Complete,
118 Partial,
120 Truncated,
122 Empty,
124}
125
126impl ResultCompleteness {
127 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#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct ResultMetadata {
147 #[serde(default = "default_confidence")]
149 pub confidence: f32,
150
151 #[serde(default = "default_relevance")]
153 pub relevance: f32,
154
155 pub completeness: ResultCompleteness,
157
158 #[serde(default)]
160 pub result_count: usize,
161
162 #[serde(default)]
164 pub false_positive_likelihood: f32,
165
166 #[serde(default)]
168 pub content_types: Vec<String>,
169
170 #[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 #[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 #[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 #[inline]
222 pub fn empty() -> Self {
223 Self {
224 completeness: ResultCompleteness::Empty,
225 result_count: 0,
226 confidence: 1.0, ..Default::default()
228 }
229 }
230
231 pub fn error() -> Self {
233 Self {
234 confidence: 0.2,
235 completeness: ResultCompleteness::Empty,
236 ..Default::default()
237 }
238 }
239
240 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 for ct in &other.content_types {
248 if !self.content_types.contains(ct) {
249 self.content_types.push(ct.clone());
250 }
251 }
252
253 self.tool_metrics.extend(
255 other
256 .tool_metrics
257 .iter()
258 .map(|(k, v)| (k.clone(), v.clone())),
259 );
260 }
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct EnhancedToolResult {
266 pub value: Value,
268
269 pub metadata: ResultMetadata,
271
272 pub timestamp: u64,
274
275 pub tool_name: CompactStr,
277
278 #[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 #[inline]
316 pub fn is_useful(&self) -> bool {
317 self.metadata.quality_score() > 0.3
318 }
319
320 #[inline]
322 pub fn is_high_quality(&self) -> bool {
323 self.metadata.quality_score() > 0.7
324 }
325
326 #[allow(clippy::cast_sign_loss)] 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
356pub trait ResultScorer {
358 fn score(&self, result: &Value) -> ResultMetadata;
360
361 fn tool_name(&self) -> &str;
363}