use serde_json::{Map, Value, json};
use super::{Doc, Orphan, StepDetail, StepSummary, StepsView, Tail, ToolIo, Wound};
use crate::budgets::BudgetSpend;
use crate::git_tree::Framing;
pub(crate) mod decode;
pub(crate) fn steps(view: &StepsView) -> Value {
let mut map = Map::new();
map.insert("ok".to_owned(), json!(true));
map.insert("kind".to_owned(), json!("steps"));
map.insert(
"rows".to_owned(),
Value::Array(view.steps.iter().map(step_row).collect()),
);
map.insert("orphan".to_owned(), json!(orphan_token(&view.orphan)));
if let Orphan::Spoke(_, reason) = &view.orphan {
map.insert("orphan_reason".to_owned(), json!(reason));
}
Value::Object(map)
}
fn orphan_token(orphan: &Orphan) -> &'static str {
match orphan {
Orphan::None => "none",
Orphan::Mute(tail) | Orphan::Spoke(tail, _) => match tail {
Tail::Mail => "mail",
Tail::ToolWindow => "tool_window",
},
}
}
fn step_row(step: &StepSummary) -> Value {
let mut map = Map::new();
map.insert("seq".to_owned(), json!(step.seq));
map.insert("framing".to_owned(), json!(framing_token(step.framing)));
map.insert("attempts".to_owned(), json!(step.attempts));
map.insert("tokens".to_owned(), spend_value(&step.tokens));
for (key, value) in [
("commit", step.commit.as_ref()),
("started_at", step.started_at.as_ref()),
("ended_at", step.ended_at.as_ref()),
] {
if let Some(value) = value {
map.insert(key.to_owned(), json!(value));
}
}
wound_fields(&step.wound, &mut map);
Value::Object(map)
}
pub(crate) fn wound_fields(wound: &Wound, map: &mut Map<String, Value>) {
map.insert("wound".to_owned(), json!(wound_token(wound)));
match wound {
Wound::Spoke(reason) => {
map.insert("wound_reason".to_owned(), json!(reason));
}
Wound::Refused(auth) => {
if let Some(row) = auth.row() {
map.insert("auth_row".to_owned(), json!(row));
}
}
Wound::None | Wound::Mute | Wound::OutputLimit => {}
}
}
fn wound_token(wound: &Wound) -> &'static str {
match wound {
Wound::None => "none",
Wound::Mute | Wound::Spoke(_) => "no_response",
Wound::OutputLimit => "output_limit",
Wound::Refused(_) => "refused",
}
}
fn framing_token(framing: Framing) -> &'static str {
match framing {
Framing::Complete => "complete",
Framing::Failed => "failed",
Framing::Killed => "killed",
}
}
pub(crate) fn spend_value(spend: &BudgetSpend) -> Value {
json!({
"input": spend.input_tokens, "output": spend.output_tokens,
"cache_read": spend.cache_read_tokens, "cache_write": spend.cache_write_tokens,
"total": spend.total_tokens(),
})
}
pub(crate) fn detail(detail: &StepDetail) -> Value {
let mut map = Map::new();
for (key, value) in [
("ok", json!(true)),
("kind", json!("step")),
("seq", json!(detail.seq)),
("meta", doc_value(&detail.meta)),
("request", doc_value(&detail.request)),
("staging", doc_value(&detail.staging)),
(
"response",
Value::Array(detail.response.iter().map(doc_value).collect()),
),
(
"tools",
Value::Array(detail.tools.iter().map(tool_value).collect()),
),
] {
map.insert(key.to_owned(), value);
}
for (key, log) in [("stderr", &detail.stderr), ("driver", &detail.driver)] {
if let Some(preview) = log {
map.insert(
key.to_owned(),
crate::files_view::wire::preview_value(preview),
);
}
}
Value::Object(map)
}
fn doc_value(doc: &Doc) -> Value {
match doc {
Doc::Json { value, raw } => json!({
"kind": "json", "value": value,
"raw": String::from_utf8_lossy(raw),
}),
Doc::Absent => json!({ "kind": "absent" }),
Doc::Unparsed(raw) => json!({
"kind": "unparsed", "note": super::UNPARSED,
"raw": String::from_utf8_lossy(raw),
}),
}
}
fn tool_value(tool: &ToolIo) -> Value {
json!({
"tool_id": tool.tool_id, "is_error": tool.is_error,
"input": doc_value(&tool.input), "output": doc_value(&tool.output),
})
}