huskies: merge 839
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
//! Human-readable formatting of raw agent log entries.
|
||||
|
||||
/// Format a single log entry as a human-readable text line.
|
||||
///
|
||||
/// `timestamp` is an ISO 8601 string; `event` is the flattened `AgentEvent`
|
||||
/// value (has `type`, `agent_name`, etc. at the top level).
|
||||
///
|
||||
/// Returns `None` for entries that should be skipped (raw streaming noise,
|
||||
/// trivial status changes, empty output, etc.).
|
||||
pub fn format_log_entry_as_text(timestamp: &str, event: &serde_json::Value) -> Option<String> {
|
||||
let agent_name = event
|
||||
.get("agent_name")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("?");
|
||||
// Extract HH:MM:SS from ISO 8601 "2026-04-10T12:48:02.123456789+00:00"
|
||||
let ts_short = if timestamp.len() >= 19 {
|
||||
×tamp[11..19]
|
||||
} else {
|
||||
timestamp
|
||||
};
|
||||
let pfx = format!("[{ts_short}][{agent_name}]");
|
||||
|
||||
match event.get("type").and_then(|v| v.as_str()) {
|
||||
Some("output") => {
|
||||
let text = event
|
||||
.get("text")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.trim();
|
||||
if text.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(format!("{pfx} {text}"))
|
||||
}
|
||||
}
|
||||
Some("error") => {
|
||||
let msg = event
|
||||
.get("message")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("(unknown error)");
|
||||
Some(format!("{pfx} ERROR: {msg}"))
|
||||
}
|
||||
Some("done") => Some(format!("{pfx} DONE")),
|
||||
Some("status") => {
|
||||
// Skip trivial running/started noise
|
||||
let status = event.get("status").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
match status {
|
||||
"running" | "started" => None,
|
||||
_ => Some(format!("{pfx} STATUS: {status}")),
|
||||
}
|
||||
}
|
||||
Some("agent_json") => {
|
||||
let data = event.get("data")?;
|
||||
match data.get("type").and_then(|v| v.as_str()) {
|
||||
Some("assistant") => {
|
||||
let mut parts: Vec<String> = Vec::new();
|
||||
if let Some(arr) = data.pointer("/message/content").and_then(|v| v.as_array()) {
|
||||
for item in arr {
|
||||
match item.get("type").and_then(|v| v.as_str()) {
|
||||
Some("text") => {
|
||||
let text = item
|
||||
.get("text")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.trim();
|
||||
if !text.is_empty() {
|
||||
parts.push(format!("{pfx} {text}"));
|
||||
}
|
||||
}
|
||||
Some("tool_use") => {
|
||||
let name =
|
||||
item.get("name").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
let input = item
|
||||
.get("input")
|
||||
.map(|v| serde_json::to_string(v).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
let display = if input.len() > 200 {
|
||||
format!("{}...", &input[..200])
|
||||
} else {
|
||||
input
|
||||
};
|
||||
parts.push(format!("{pfx} TOOL: {name}({display})"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if parts.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(parts.join("\n"))
|
||||
}
|
||||
}
|
||||
Some("user") => {
|
||||
let mut parts: Vec<String> = Vec::new();
|
||||
if let Some(arr) = data.pointer("/message/content").and_then(|v| v.as_array()) {
|
||||
for item in arr {
|
||||
if item.get("type").and_then(|v| v.as_str()) != Some("tool_result") {
|
||||
continue;
|
||||
}
|
||||
let content_str = match item.get("content") {
|
||||
Some(serde_json::Value::String(s)) => s.clone(),
|
||||
Some(v) => v.to_string(),
|
||||
None => String::new(),
|
||||
};
|
||||
let display = if content_str.len() > 500 {
|
||||
format!(
|
||||
"{}... [{} chars total]",
|
||||
&content_str[..500],
|
||||
content_str.len()
|
||||
)
|
||||
} else {
|
||||
content_str
|
||||
};
|
||||
if !display.trim().is_empty() {
|
||||
parts.push(format!("{pfx} RESULT: {display}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if parts.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(parts.join("\n"))
|
||||
}
|
||||
}
|
||||
_ => None, // Skip stream_event, system init, etc.
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user