refactor: improve readability of streaming parser and JSON filter

Agent: carmack

Changes:
- streaming_parser.rs: Unified find_first/last_tool_call_start into single
  find_tool_call_start with SearchDirection enum, reducing duplication.
  Simplified is_json_invalidated from 45 to 20 lines with clearer logic.
  Fixed redundant !escape_next check in find_complete_json_object_end.

- filter_json.rs: Simplified check_tool_pattern from 40 to 24 lines.
  Replaced repetitive prefix checks with loop over ["t", "to", "too", "tool"].
  Reduced trailing return statements with direct expression returns.

- ui_writer_impl.rs: Added ansi module for duration color constants.
  Simplified duration_color function by removing redundant comments.

- language_prompts.rs: Fixed test assertions to match actual prompt content
  ("obvious, readable Racket" instead of "RACKET-SPECIFIC GUIDANCE").

All 174+ tests pass. No behavior changes.
This commit is contained in:
Dhanji R. Prasanna
2026-01-15 12:22:32 +05:30
parent 0ae1a13cdb
commit a84fead03b
4 changed files with 105 additions and 138 deletions

View File

@@ -106,64 +106,44 @@ fn check_tool_pattern(buffer: &str) -> Option<bool> {
if !buffer.starts_with('{') {
return Some(false);
}
let after_brace = &buffer[1..];
// Skip leading whitespace after {
let trimmed = after_brace.trim_start();
let trimmed = buffer[1..].trim_start();
// Need at least `"tool":"` = 8 chars after whitespace
if trimmed.len() < 8 {
// Not enough data yet - but check for early rejection
if trimmed.starts_with('"') {
let after_quote = &trimmed[1..];
// If we have chars after the quote, check if it starts with 't'
if !after_quote.is_empty() && !after_quote.starts_with('t') {
return Some(false); // Definitely not "tool
}
if after_quote.len() >= 2 && !after_quote.starts_with("to") {
return Some(false);
}
if after_quote.len() >= 3 && !after_quote.starts_with("too") {
return Some(false);
}
if after_quote.len() >= 4 && !after_quote.starts_with("tool") {
return Some(false);
// Early rejection: check progressive prefix of "tool
if let Some(after_quote) = trimmed.strip_prefix('"') {
// Check each prefix of "tool" we have so far
for (i, expected) in ["t", "to", "too", "tool"].iter().enumerate() {
if after_quote.len() > i && !after_quote.starts_with(expected) {
return Some(false);
}
}
} else if !trimmed.is_empty() && !trimmed.starts_with('"') {
// First non-whitespace char after { is not " - not a tool call
return Some(false);
}
return None; // Need more data
return None;
}
// We have enough data - check the full pattern
// Must be: "tool" followed by optional whitespace, :, optional whitespace, "
// Full pattern check: "tool" : "
if !trimmed.starts_with("\"tool\"") {
return Some(false);
}
let after_tool = trimmed[6..].trim_start(); // 6 = len of "tool"
let after_tool = trimmed[6..].trim_start();
if after_tool.is_empty() {
return None; // Need more data
return None;
}
if !after_tool.starts_with(':') {
return Some(false);
}
let after_colon = after_tool[1..].trim_start();
if after_colon.is_empty() {
return None; // Need more data
return None;
}
if after_colon.starts_with('"') {
return Some(true); // Confirmed tool call!
}
Some(false) // Has : but not followed by "
Some(after_colon.starts_with('"'))
}
/// Filters JSON tool calls from streaming LLM content.

View File

@@ -224,7 +224,7 @@ mod tests {
fn test_carmack_racket_prompt_embedded() {
let prompt = get_agent_language_prompt("carmack", "racket");
assert!(prompt.is_some());
assert!(prompt.unwrap().contains("RACKET-SPECIFIC GUIDANCE"));
assert!(prompt.unwrap().contains("obvious, readable Racket"));
}
#[test]
@@ -242,6 +242,6 @@ mod tests {
let prompts = get_agent_language_prompts_for_workspace(temp_dir.path(), "carmack");
assert!(prompts.is_some());
let content = prompts.unwrap();
assert!(content.contains("RACKET-SPECIFIC GUIDANCE"));
assert!(content.contains("obvious, readable Racket"));
}
}

View File

@@ -8,6 +8,13 @@ use termimad::MadSkin;
/// Padding width for tool names in compact display (longest tool: "str_replace" = 11 chars)
const TOOL_NAME_PADDING: usize = 11;
/// ANSI escape codes
mod ansi {
pub const YELLOW: &str = "\x1b[33m";
pub const ORANGE: &str = "\x1b[38;5;208m";
pub const RED: &str = "\x1b[31m";
}
/// ANSI color codes for tool names
const TOOL_COLOR_NORMAL: &str = "\x1b[32m";
const TOOL_COLOR_NORMAL_BOLD: &str = "\x1b[1;32m";
@@ -129,30 +136,27 @@ pub struct ConsoleUiWriter {
/// ANSI color code for duration display based on elapsed time.
/// Returns empty string for fast operations, yellow/orange/red for slower ones.
fn duration_color(duration_str: &str) -> &'static str {
// Format: "500ms", "1.5s", "2m 30.0s"
if duration_str.ends_with("ms") {
return ""; // Sub-second: no color
return "";
}
if let Some(m_pos) = duration_str.find('m') {
// Contains minutes (e.g., "2m 30.0s")
if let Ok(minutes) = duration_str[..m_pos].trim().parse::<u32>() {
return match minutes {
5.. => "\x1b[31m", // Red: >= 5 minutes
1.. => "\x1b[38;5;208m", // Orange: 1-4 minutes
5.. => ansi::RED,
1.. => ansi::ORANGE,
_ => "",
};
}
} else if let Some(s_value) = duration_str.strip_suffix('s') {
// Seconds only (e.g., "1.5s")
if let Ok(seconds) = s_value.trim().parse::<f64>() {
if seconds >= 1.0 {
return "\x1b[33m"; // Yellow: >= 1 second
return ansi::YELLOW;
}
}
}
"" // Default: no color
""
}
impl ConsoleUiWriter {