use super::{Block, EntryKind, Usage};
const INPUT_SUMMARY_CAP: usize = 200;
pub(super) fn parse_model(model_id: &str, raw: &[u8]) -> EntryKind {
match serde_json::from_slice::<serde_json::Value>(raw) {
Ok(value) => EntryKind::Model {
model_id: model_id.to_string(),
blocks: blocks_from_value(&value),
usage: usage_from_value(&value),
},
Err(_) => EntryKind::Raw,
}
}
fn usage_from_value(value: &serde_json::Value) -> Usage {
value
.get("usage")
.and_then(serde_json::Value::as_object)
.into_iter()
.flatten()
.filter_map(|(counter, v)| Some((counter.clone(), v.as_u64()?)))
.collect()
}
fn blocks_from_value(value: &serde_json::Value) -> Vec<Block> {
if let Some(arr) = block_array(value) {
return arr.iter().filter_map(block_from_value).collect();
}
match value.get("content") {
Some(serde_json::Value::String(s)) => vec![Block::Text(s.clone())],
_ => Vec::new(),
}
}
fn block_array(value: &serde_json::Value) -> Option<&Vec<serde_json::Value>> {
value
.as_array()
.or_else(|| value.get("content")?.as_array())
}
fn block_from_value(v: &serde_json::Value) -> Option<Block> {
match v.get("type").and_then(|t| t.as_str())? {
"text" => Some(Block::Text(str_field(v, "text"))),
"thinking" => Some(Block::Thinking(str_field(v, "thinking"))),
"tool_use" => Some(Block::ToolUse {
id: str_field(v, "id"),
name: str_field(v, "name"),
input_summary: summarize_input(v.get("input")),
}),
_ => None,
}
}
fn summarize_input(input: Option<&serde_json::Value>) -> String {
let Some(v) = input else {
return String::new();
};
let compact = v.to_string();
if compact.chars().count() > INPUT_SUMMARY_CAP {
let head: String = compact.chars().take(INPUT_SUMMARY_CAP).collect();
format!("{head}…")
} else {
compact
}
}
pub(super) fn parse_tool_result(raw: &[u8]) -> Option<EntryKind> {
let value: serde_json::Value = serde_json::from_slice(raw).ok()?;
let block = find_tool_result(&value)?;
Some(EntryKind::ToolResult {
tool_use_id: str_field(block, "tool_use_id"),
content: tool_result_content(block.get("content")),
is_error: block
.get("is_error")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false),
})
}
fn find_tool_result(value: &serde_json::Value) -> Option<&serde_json::Value> {
if value.get("tool_use_id").is_some() {
return Some(value);
}
block_array(value)?
.iter()
.find(|b| b.get("tool_use_id").is_some())
}
fn tool_result_content(content: Option<&serde_json::Value>) -> String {
match content {
None | Some(serde_json::Value::Null) => String::new(),
Some(serde_json::Value::String(s)) => s.clone(),
Some(serde_json::Value::Array(arr)) => arr.iter().map(content_piece).collect(),
Some(other) => other.to_string(),
}
}
fn content_piece(v: &serde_json::Value) -> String {
if v.get("type").and_then(|t| t.as_str()) == Some("text") {
str_field(v, "text")
} else {
v.to_string()
}
}
fn str_field(v: &serde_json::Value, key: &str) -> String {
v.get(key)
.and_then(|x| x.as_str())
.unwrap_or_default()
.to_string()
}