1pub type CompactStr = compact_str::CompactString;
21
22pub mod tool_names {
29 pub const UNIFIED_SEARCH: &str = "unified_search";
31 pub const UNIFIED_EXEC: &str = "unified_exec";
33 pub const RUN_PTY_CMD: &str = "run_pty_cmd";
35}
36
37pub const fn canonical_tool_name(name: &str) -> &str {
41 name
42}
43
44pub 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
71pub const NETWORK_ERROR_PATTERNS: &[&str] = &[
73 "connection",
74 "timeout",
75 "network",
76 "http",
77 "ssl",
78 "tls",
79 "dns",
80 "proxy",
81];
82
83pub const DEFAULT_VEC_CAPACITY: usize = 32;
85pub const DEFAULT_HASHMAP_CAPACITY: usize = 16;
86pub const DEFAULT_STRING_CAPACITY: usize = 256;
87
88pub 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
94pub const MAX_FILE_SIZE_FOR_PROCESSING: usize = 100 * 1024 * 1024; pub const MAX_CONTEXT_LINES: usize = 20;
97pub const MAX_OUTPUT_TOKENS: usize = 4000;
98
99pub fn empty_object_schema() -> Value {
102 serde_json::json!({"type": "object"})
103}
104
105use hashbrown::HashMap;
110use serde::{Deserialize, Serialize};
111use serde_json::Value;
112use std::fmt;
113use std::time::SystemTime;
114
115#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
117pub enum ResultCompleteness {
118 Complete,
120 Partial,
122 Truncated,
124 Empty,
126}
127
128impl ResultCompleteness {
129 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#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct ResultMetadata {
149 #[serde(default = "default_confidence")]
151 pub confidence: f32,
152
153 #[serde(default = "default_relevance")]
155 pub relevance: f32,
156
157 pub completeness: ResultCompleteness,
159
160 #[serde(default)]
162 pub result_count: usize,
163
164 #[serde(default)]
166 pub false_positive_likelihood: f32,
167
168 #[serde(default)]
170 pub content_types: Vec<String>,
171
172 #[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 #[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 #[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 #[inline]
224 pub fn empty() -> Self {
225 Self {
226 completeness: ResultCompleteness::Empty,
227 result_count: 0,
228 confidence: 1.0, ..Default::default()
230 }
231 }
232
233 pub fn error() -> Self {
235 Self {
236 confidence: 0.2,
237 completeness: ResultCompleteness::Empty,
238 ..Default::default()
239 }
240 }
241
242 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 for ct in &other.content_types {
250 if !self.content_types.contains(ct) {
251 self.content_types.push(ct.clone());
252 }
253 }
254
255 self.tool_metrics.extend(
257 other
258 .tool_metrics
259 .iter()
260 .map(|(k, v)| (k.clone(), v.clone())),
261 );
262 }
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct EnhancedToolResult {
268 pub value: Value,
270
271 pub metadata: ResultMetadata,
273
274 pub timestamp: u64,
276
277 pub tool_name: CompactStr,
279
280 #[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 #[inline]
318 pub fn is_useful(&self) -> bool {
319 self.metadata.quality_score() > 0.3
320 }
321
322 #[inline]
324 pub fn is_high_quality(&self) -> bool {
325 self.metadata.quality_score() > 0.7
326 }
327
328 #[allow(clippy::cast_sign_loss)] 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
358pub trait ResultScorer {
360 fn score(&self, result: &Value) -> ResultMetadata;
362
363 fn tool_name(&self) -> &str;
365}