use std::collections::BTreeMap;
use serde_json::{json, Map, Value};
use crate::canonical::{ContentKind, Usage};
#[derive(Default)]
pub(crate) struct AnthAcc {
pub(super) open: BTreeMap<u32, OpenBlock>,
pub(super) content: Vec<Value>,
pub(super) usage: Usage,
pub(super) stop_reason: Option<String>,
pub(super) stop_details: Option<Value>,
}
pub(super) enum OpenBlock {
Text(String),
Tool {
ty: &'static str,
id: String,
name: String,
args: String,
},
Thinking {
text: String,
signature: String,
},
Redacted(String),
ServerResult(Value),
Skip,
}
pub(super) fn open_block(kind: &ContentKind) -> (OpenBlock, Option<Value>) {
match kind {
ContentKind::Text {} => (
OpenBlock::Text(String::new()),
Some(json!({"type": "text", "text": ""})),
),
ContentKind::ToolUse { id, name } => tool("tool_use", id, name),
ContentKind::ServerToolUse { id, name } => tool("server_tool_use", id, name),
ContentKind::Thinking { .. } => (
OpenBlock::Thinking {
text: String::new(),
signature: String::new(),
},
Some(json!({"type": "thinking", "thinking": "", "signature": ""})),
),
ContentKind::RedactedThinking { data } => (
OpenBlock::Redacted(data.clone()),
Some(json!({"type": "redacted_thinking", "data": data})),
),
ContentKind::ServerToolResult {
kind,
tool_use_id,
content,
} => {
let cb = json!({"type": kind, "tool_use_id": tool_use_id, "content": content});
(OpenBlock::ServerResult(cb.clone()), Some(cb))
}
_ => (OpenBlock::Skip, None),
}
}
fn tool(ty: &'static str, id: &str, name: &str) -> (OpenBlock, Option<Value>) {
(
OpenBlock::Tool {
ty,
id: id.to_owned(),
name: name.to_owned(),
args: String::new(),
},
Some(json!({"type": ty, "id": id, "name": name, "input": {}})),
)
}
pub(super) fn finish_block(block: OpenBlock) -> Option<Value> {
Some(match block {
OpenBlock::Text(text) => json!({"type": "text", "text": text}),
OpenBlock::Tool { ty, id, name, args } => {
json!({"type": ty, "id": id, "name": name, "input": parse_args(&args)})
}
OpenBlock::Thinking { text, signature } => {
json!({"type": "thinking", "thinking": text, "signature": signature})
}
OpenBlock::Redacted(data) => json!({"type": "redacted_thinking", "data": data}),
OpenBlock::ServerResult(v) => v,
OpenBlock::Skip => return None,
})
}
fn parse_args(args: &str) -> Value {
if args.is_empty() {
return Value::Object(Map::new());
}
serde_json::from_str(args).unwrap_or(Value::Null)
}
pub(super) fn merge_usage(acc: &mut Usage, u: &Usage) {
if u.input_tokens.is_some() {
acc.input_tokens = u.input_tokens;
}
if u.output_tokens.is_some() {
acc.output_tokens = u.output_tokens;
}
if u.cache_read_tokens.is_some() {
acc.cache_read_tokens = u.cache_read_tokens;
}
if u.cache_write_tokens.is_some() {
acc.cache_write_tokens = u.cache_write_tokens;
}
}