Skip to main content

sgr_agent/
str_ext.rs

1use serde_json::Value;
2use std::fmt;
3
4/// UTF-8 safe string operations missing from stdlib.
5pub trait StrExt {
6    /// Truncate to max bytes at char boundary.
7    fn trunc(&self, max_bytes: usize) -> &str;
8
9    /// Truncate with ellipsis for display: `"long text…"`.
10    fn ellipsis(&self, max_bytes: usize) -> Ellipsis<'_>;
11
12    /// Single-line preview: collapse whitespace + truncate.
13    fn oneline(&self, max_bytes: usize) -> String;
14}
15
16impl StrExt for str {
17    #[inline]
18    fn trunc(&self, max_bytes: usize) -> &str {
19        &self[..self.floor_char_boundary(max_bytes)]
20    }
21
22    #[inline]
23    fn ellipsis(&self, max_bytes: usize) -> Ellipsis<'_> {
24        Ellipsis(self, max_bytes)
25    }
26
27    fn oneline(&self, max_bytes: usize) -> String {
28        let flat: String = self.split_whitespace().collect::<Vec<_>>().join(" ");
29        flat.trunc(max_bytes).to_string()
30    }
31}
32
33/// Display wrapper that appends "…" when truncated.
34pub struct Ellipsis<'a>(&'a str, usize);
35
36impl fmt::Display for Ellipsis<'_> {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        let s = self.0.trunc(self.1);
39        f.write_str(s)?;
40        if s.len() < self.0.len() {
41            f.write_str("…")?;
42        }
43        Ok(())
44    }
45}
46
47/// Parse tool call arguments JSON with llm_json repair fallback.
48/// Handles common LLM mistakes: escaped quotes, trailing commas, missing braces.
49pub fn parse_tool_args(args_str: &str) -> Value {
50    serde_json::from_str(args_str).unwrap_or_else(|_| {
51        match llm_json::repair_json(args_str, &llm_json::RepairOptions::default()) {
52            Ok(fixed) => serde_json::from_str(&fixed).unwrap_or(Value::Object(Default::default())),
53            Err(_) => Value::Object(Default::default()),
54        }
55    })
56}