use serde_json::Value;
use super::super::{Doc, Orphan, StepDetail, StepSummary, StepsView, Tail, ToolIo, Wound};
use crate::boundary::codec::fields::{
bool_of, bytes_of, list_of, opt_str_of, opt_val, pick, str_of, u64_of, usize_of,
};
use crate::budgets::BudgetSpend;
use crate::git_tree::Framing;
use crate::login::auth::AuthFailure;
const FRAMINGS: [(&str, Framing); 4] = [
("complete", Framing::Complete),
("failed", Framing::Failed),
("killed", Framing::Killed),
("in_flight", Framing::InFlight),
];
#[derive(Clone, Copy)]
enum WoundKind {
None,
NoResponse,
OutputLimit,
Refused,
}
const WOUNDS: [(&str, WoundKind); 4] = [
("none", WoundKind::None),
("no_response", WoundKind::NoResponse),
("output_limit", WoundKind::OutputLimit),
("refused", WoundKind::Refused),
];
pub(crate) fn wound(o: &serde_json::Map<String, Value>) -> Result<Wound, String> {
let row = opt_str_of(o, "auth_row")?;
Ok(
match (pick(o, "wound", &WOUNDS)?, opt_str_of(o, "wound_reason")?) {
(WoundKind::None, _) => Wound::None,
(WoundKind::NoResponse, None) => Wound::Mute,
(WoundKind::NoResponse, Some(reason)) => Wound::Spoke(reason),
(WoundKind::OutputLimit, _) => Wound::OutputLimit,
(WoundKind::Refused, _) => Wound::Refused(match row {
None => AuthFailure::Unrouted,
Some(row) => AuthFailure::Row(row),
}),
},
)
}
const TAILS: [(&str, Option<Tail>); 3] = [
("none", None),
("mail", Some(Tail::Mail)),
("tool_window", Some(Tail::ToolWindow)),
];
pub(crate) fn steps(obj: &serde_json::Map<String, Value>) -> Result<StepsView, String> {
let orphan = match (
pick(obj, "orphan", &TAILS)?,
opt_str_of(obj, "orphan_reason")?,
) {
(None, _) => Orphan::None,
(Some(tail), None) => Orphan::Mute(tail),
(Some(tail), Some(reason)) => Orphan::Spoke(tail, reason),
};
Ok(StepsView {
steps: list_of(obj, "rows", step_row)?,
orphan,
})
}
fn step_row(v: &Value) -> Result<StepSummary, String> {
let o = v.as_object().ok_or("step row: not an object")?;
Ok(StepSummary {
seq: str_of(o, "seq")?,
framing: pick(o, "framing", &FRAMINGS)?,
attempts: usize_of(o, "attempts")?,
tokens: spend(o.get("tokens").ok_or("step row: missing tokens")?)?,
cost: opt_val(o, "cost", crate::boundary::reply::cost::cost_of)?,
commit: opt_str_of(o, "commit")?,
started_at: opt_str_of(o, "started_at")?,
ended_at: opt_str_of(o, "ended_at")?,
wound: wound(o)?,
})
}
pub(crate) fn spend(v: &Value) -> Result<BudgetSpend, String> {
let o = v.as_object().ok_or("spend: not an object")?;
Ok(BudgetSpend {
input_tokens: u64_of(o, "input")?,
output_tokens: u64_of(o, "output")?,
cache_read_tokens: u64_of(o, "cache_read")?,
cache_write_tokens: u64_of(o, "cache_write")?,
})
}
pub(crate) fn detail(obj: &serde_json::Map<String, Value>) -> Result<StepDetail, String> {
Ok(StepDetail {
seq: str_of(obj, "seq")?,
meta: doc(obj.get("meta").ok_or("step: missing meta")?)?,
request: doc(obj.get("request").ok_or("step: missing request")?)?,
staging: doc(obj.get("staging").ok_or("step: missing staging")?)?,
response: list_of(obj, "response", doc)?,
tools: list_of(obj, "tools", tool)?,
stderr: opt_val(obj, "stderr", crate::files_view::wire::preview_of)?,
driver: opt_val(obj, "driver", crate::files_view::wire::preview_of)?,
})
}
fn doc(v: &Value) -> Result<Doc, String> {
let o = v.as_object().ok_or("doc: not an object")?;
match str_of(o, "kind")?.as_str() {
"json" => Ok(Doc::Json {
value: o.get("value").ok_or("doc: missing value")?.clone(),
raw: bytes_of(o, "raw")?,
}),
"absent" => Ok(Doc::Absent),
"unparsed" => Ok(Doc::Unparsed(bytes_of(o, "raw")?)),
other => Err(format!("doc: unknown kind {other:?}")),
}
}
fn tool(v: &Value) -> Result<ToolIo, String> {
let o = v.as_object().ok_or("tool: not an object")?;
Ok(ToolIo {
tool_id: str_of(o, "tool_id")?,
input: doc(o.get("input").ok_or("tool: missing input")?)?,
output: doc(o.get("output").ok_or("tool: missing output")?)?,
is_error: bool_of(o, "is_error")?,
})
}