use serde_json::{Map, Value};
use super::fields;
pub mod blocks;
pub use blocks::{Block, Usage};
pub(crate) const KIND: &str = "transcript";
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Transcript {
pub entries: Vec<Entry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Entry {
pub name: String,
pub raw: String,
pub kind: EntryKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EntryKind {
Delivered {
sender: String,
sender_name: Option<String>,
epitaph: Option<String>,
body: String,
},
Model {
model_id: String,
blocks: Vec<Block>,
usage: Usage,
},
ToolResult {
tool_use_id: String,
content: String,
is_error: bool,
},
Streaming { thinking: String, text: String },
Compacted {
first: u64,
last: u64,
summary: String,
},
Raw,
Unknown(String),
}
pub(crate) fn transcript(obj: &Map<String, Value>) -> Result<Transcript, String> {
Ok(Transcript {
entries: fields::rows(obj, entry)?,
})
}
fn entry(v: &Value) -> Result<Entry, String> {
let o = v.as_object().ok_or("transcript row: not an object")?;
Ok(Entry {
name: fields::text(o, "name")?,
raw: fields::text(o, "raw")?,
kind: kind(o)?,
})
}
pub fn said_by(sender: &str, sender_name: Option<&str>) -> String {
match sender_name {
Some(name) => format!("{name} ({sender})"),
None => sender.to_owned(),
}
}
fn kind(o: &Map<String, Value>) -> Result<EntryKind, String> {
Ok(match fields::text(o, "kind")?.as_str() {
"delivered" => EntryKind::Delivered {
sender: fields::text(o, "sender")?,
sender_name: fields::opt_text(o, "sender_name")?,
epitaph: fields::opt_text(o, "epitaph")?,
body: fields::text(o, "body")?,
},
"model" => EntryKind::Model {
model_id: fields::text(o, "model_id")?,
blocks: fields::list(o, "blocks", blocks::block)?,
usage: blocks::usage(o)?,
},
"tool-result" => EntryKind::ToolResult {
tool_use_id: fields::text(o, "tool_use_id")?,
content: fields::text(o, "content")?,
is_error: fields::flag(o, "is_error")?,
},
"streaming" => EntryKind::Streaming {
thinking: fields::text(o, "thinking")?,
text: fields::text(o, "text")?,
},
"compacted" => EntryKind::Compacted {
first: fields::count(o, "first")?,
last: fields::count(o, "last")?,
summary: fields::text(o, "summary")?,
},
"raw" => EntryKind::Raw,
other => EntryKind::Unknown(other.to_owned()),
})
}
#[cfg(test)]
mod tests;