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
255 .extend(other.tool_metrics.iter().map(|(k, v)| (k.clone(), v.clone())));
256 }
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct EnhancedToolResult {
262 pub value: Value,
264
265 pub metadata: ResultMetadata,
267
268 pub timestamp: u64,
270
271 pub tool_name: CompactStr,
273
274 #[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 #[inline]
312 pub fn is_useful(&self) -> bool {
313 self.metadata.quality_score() > 0.3
314 }
315
316 #[inline]
318 pub fn is_high_quality(&self) -> bool {
319 self.metadata.quality_score() > 0.7
320 }
321
322 #[allow(clippy::cast_sign_loss)] 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
352pub trait ResultScorer {
354 fn score(&self, result: &Value) -> ResultMetadata;
356
357 fn tool_name(&self) -> &str;
359}