use std::collections::BTreeSet;
use serde_json::Value;
use super::blocks::{BlockAudience, canonical_message_to_anthropic};
use crate::canonical::{CanonicalMessage, TurnMerge, normalize_turns};
#[must_use]
pub fn upstream_messages(messages: &[CanonicalMessage]) -> Vec<Value> {
let mut out: Vec<Value> = Vec::new();
for message in normalize_turns(messages, TurnMerge::UserSide)
.iter()
.filter_map(|m| canonical_message_to_anthropic(m, BlockAudience::Upstream))
{
match out.last_mut() {
Some(last) if last.get("role") == message.get("role") => {
if let (Some(into), Some(from)) = (
last.get_mut("content").and_then(Value::as_array_mut),
message.get("content").and_then(Value::as_array),
) {
into.extend(from.iter().cloned());
}
},
_ => out.push(message),
}
}
out
}
#[must_use]
pub fn history_is_strict(messages: &Value) -> bool {
let Some(messages) = messages.as_array() else {
return false;
};
let mut previous_role: Option<&str> = None;
let mut pending: BTreeSet<&str> = BTreeSet::new();
for message in messages {
let Some(role) = message.get("role").and_then(Value::as_str) else {
return false;
};
if previous_role == Some(role) {
return false;
}
let blocks = blocks(message);
let ids = |kind: &str, key: &str| -> BTreeSet<&str> {
blocks
.iter()
.filter(|b| b.get("type").and_then(Value::as_str) == Some(kind))
.filter_map(|b| b.get(key).and_then(Value::as_str))
.collect()
};
let answered = ids("tool_result", "tool_use_id");
if role == "user" {
if answered != pending {
return false;
}
pending.clear();
} else {
if !pending.is_empty() || !answered.is_empty() {
return false;
}
pending = ids("tool_use", "id");
}
previous_role = Some(role);
}
true
}
fn blocks(message: &Value) -> &[Value] {
message
.get("content")
.and_then(Value::as_array)
.map_or(&[], Vec::as_slice)
}