use std::collections::HashSet;
use serde_json::Value;
pub fn history_trim_is_valid(retained: &[Value]) -> bool {
let produced = tool_use_ids(retained);
for message in retained {
for block in content_blocks(message) {
if block_type(block) == Some("tool_result") {
if let Some(id) = block.get("tool_use_id").and_then(Value::as_str) {
if !produced.contains(id) {
return false; }
}
}
}
}
true
}
fn tool_use_ids(messages: &[Value]) -> HashSet<&str> {
let mut ids = HashSet::new();
for message in messages {
for block in content_blocks(message) {
if block_type(block) == Some("tool_use") {
if let Some(id) = block.get("id").and_then(Value::as_str) {
ids.insert(id);
}
}
}
}
ids
}
fn content_blocks(message: &Value) -> &[Value] {
message
.get("content")
.and_then(Value::as_array)
.map(Vec::as_slice)
.unwrap_or(&[])
}
fn block_type(block: &Value) -> Option<&str> {
block.get("type").and_then(Value::as_str)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn tool_use(id: &str) -> Value {
json!({ "role": "assistant", "content": [ { "type": "tool_use", "id": id, "name": "search", "input": {} } ] })
}
fn tool_result(id: &str) -> Value {
json!({ "role": "user", "content": [ { "type": "tool_result", "tool_use_id": id, "content": "ok" } ] })
}
#[test]
fn matched_tool_pair_in_the_tail_is_valid() {
let retained = vec![tool_use("toolu_1"), tool_result("toolu_1")];
assert!(history_trim_is_valid(&retained));
}
#[test]
fn orphaned_tool_result_is_invalid() {
let retained = vec![tool_result("toolu_9")];
assert!(!history_trim_is_valid(&retained));
}
#[test]
fn plain_text_history_is_always_valid() {
let retained = vec![
json!({ "role": "user", "content": "hi" }),
json!({ "role": "assistant", "content": "hello" }),
];
assert!(history_trim_is_valid(&retained));
}
#[test]
fn a_result_before_its_use_in_the_tail_is_still_valid() {
let retained = vec![tool_result("toolu_2"), tool_use("toolu_2")];
assert!(history_trim_is_valid(&retained));
}
}