use super::*;
pub(super) fn bash_card(input: &ToolCardInput<'_>) -> CardDraft {
let detail = input.detail;
let mut status = input_status(input);
if status == CardStatus::Failed && tool_was_canceled(detail) {
status = CardStatus::Canceled;
}
let timed_out = detail
.and_then(|detail| detail.metadata.get("timed_out"))
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
let exit_code = detail
.and_then(|detail| detail.metadata.get("exit_code"))
.and_then(serde_json::Value::as_i64);
let command = input
.bash_command
.map(sanitize_display_text)
.unwrap_or_default();
let title = bash_title(status, timed_out, exit_code);
let intent = detail
.and_then(|detail| detail.params.get(crate::tool_contract::arg_key::INTENT))
.and_then(serde_json::Value::as_str)
.map(str::trim)
.map(sanitized_single_line)
.filter(|intent| !intent.is_empty());
let mut body_rows = intent.iter().cloned().collect::<Vec<_>>();
if !command.is_empty() {
body_rows.push(command);
}
let mut display_lines = Vec::new();
let notices = super::super::bash_notices::bash_execution_notices(detail);
body_rows.extend(notices.iter().map(|notice| {
format!(
"{} • {}",
notice.label,
notice.value.as_deref().unwrap_or("not reported")
)
}));
let status_row = match status {
CardStatus::Canceled => Some("canceled"),
CardStatus::Failed if notices.iter().all(|notice| notice.value.is_none()) => {
Some("command failed")
}
_ => None,
};
if let Some(status_row) = status_row {
display_lines.push(TextLine::plain(status_row));
body_rows.push(status_row.to_owned());
}
CardDraft {
role: ToolCardRole::Bash,
status,
title,
metadata: bash_jev_metadata(detail),
body: body_rows.join("\n"),
body_lines: display_lines,
activity_rows: Vec::new(),
}
}
fn bash_title(status: CardStatus, timed_out: bool, exit_code: Option<i64>) -> String {
if timed_out {
return "Bash • timed out".to_string();
}
if matches!(status, CardStatus::Success | CardStatus::Failed)
&& let Some(exit_code) = exit_code
{
format!("Bash • exit {exit_code}")
} else if status == CardStatus::Canceled {
"Bash • canceled".to_string()
} else {
"Bash".to_string()
}
}