Fix empty-status backward-compat bug, add From<&StageUsage> for Usage

- Fix: empty-string status from old logs now defaults to "success"
  instead of rendering as red/error
- Add From<&StageUsage> for fabro_llm::Usage to centralize conversion
- Simplify usage aggregation: replace collect+reduce+unwrap with
  direct .reduce() on the iterator

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-15 14:14:17 -04:00
parent a52bdda2ca
commit 7de9fe04a2
3 changed files with 22 additions and 23 deletions

View file

@ -214,7 +214,10 @@ pub fn format_event_pretty(line: &str, styles: &fabro_util::terminal::Styles) ->
"WorkflowRunCompleted" => {
let duration = format_duration_ms(envelope.get("duration_ms"));
let status_str = str_field(&envelope, "status").unwrap_or("success");
let status_str = match str_field(&envelope, "status") {
Some(s) if !s.is_empty() => s,
_ => "success",
};
let status_upper = status_str.to_uppercase();
let status_style = match status_str {
"success" | "partial_success" => &styles.bold_green,

View file

@ -2144,28 +2144,10 @@ impl WorkflowRunEngine {
.cloned()
.unwrap_or_else(Outcome::success);
let run_usage: Option<fabro_llm::types::Usage> = {
let usages: Vec<fabro_llm::types::Usage> = node_outcomes
.values()
.filter_map(|o| {
let u = o.usage.as_ref()?;
Some(fabro_llm::types::Usage {
input_tokens: u.input_tokens,
output_tokens: u.output_tokens,
total_tokens: u.input_tokens + u.output_tokens,
cache_read_tokens: u.cache_read_tokens,
cache_write_tokens: u.cache_write_tokens,
reasoning_tokens: u.reasoning_tokens,
raw: None,
})
})
.collect();
if usages.is_empty() {
None
} else {
Some(usages.into_iter().reduce(|a, b| a + b).unwrap())
}
};
let run_usage: Option<fabro_llm::types::Usage> = node_outcomes
.values()
.filter_map(|o| o.usage.as_ref().map(fabro_llm::types::Usage::from))
.reduce(|a, b| a + b);
self.services
.emitter

View file

@ -61,6 +61,20 @@ pub struct StageUsage {
pub cost: Option<f64>,
}
impl From<&StageUsage> for fabro_llm::types::Usage {
fn from(u: &StageUsage) -> Self {
Self {
input_tokens: u.input_tokens,
output_tokens: u.output_tokens,
total_tokens: u.input_tokens + u.output_tokens,
cache_read_tokens: u.cache_read_tokens,
cache_write_tokens: u.cache_write_tokens,
reasoning_tokens: u.reasoning_tokens,
raw: None,
}
}
}
/// Structured failure information carried through the pipeline.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FailureDetail {