use super::*;
fn varint(mut v: u64) -> Vec<u8> {
let mut out = Vec::new();
loop {
let b = (v & 0x7f) as u8;
v >>= 7;
if v == 0 {
out.push(b);
break;
}
out.push(b | 0x80);
}
out
}
fn field(num: u64, payload: &[u8]) -> Vec<u8> {
let mut out = varint((num << 3) | 2);
out.extend(varint(payload.len() as u64));
out.extend_from_slice(payload);
out
}
fn b64(bytes: &[u8]) -> String {
const AL: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::new();
for chunk in bytes.chunks(3) {
let b = [
chunk[0],
*chunk.get(1).unwrap_or(&0),
*chunk.get(2).unwrap_or(&0),
];
let n = (u32::from(b[0]) << 16) | (u32::from(b[1]) << 8) | u32::from(b[2]);
out.push(AL[(n >> 18) as usize & 63] as char);
out.push(AL[(n >> 12) as usize & 63] as char);
out.push(if chunk.len() > 1 {
AL[(n >> 6) as usize & 63] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
AL[n as usize & 63] as char
} else {
'='
});
}
out
}
fn tagged_signature(tag: &str) -> String {
let mut outer = vec![0x08, 0x02];
outer.extend(field(2, &field(1, &field(8, tag.as_bytes()))));
b64(&outer)
}
#[test]
fn narration_tag_decodes_and_classifies() {
let n = tagged_signature("narration");
assert_eq!(
thinking_signature_tag(Some(&n)).as_deref(),
Some("narration")
);
assert_eq!(
thinking_block_class(Some(&n)),
Class::AgentThinkingNarration
);
let t = tagged_signature("thinking");
assert_eq!(
thinking_signature_tag(Some(&t)).as_deref(),
Some("thinking")
);
assert_eq!(thinking_block_class(Some(&t)), Class::AgentThinking);
let f = tagged_signature("future-tag");
assert_eq!(
thinking_signature_tag(Some(&f)).as_deref(),
Some("future-tag")
);
assert_eq!(thinking_block_class(Some(&f)), Class::AgentThinking);
}
#[test]
fn every_degradation_path_falls_to_plain_thinking() {
let cases: Vec<Option<String>> = vec![
None,
Some(String::new()),
Some("!!!not-base64!!!".to_string()),
Some(b64(&[0xff; 16])), Some(b64(&field(2, &field(3, b"x")))), Some(b64(&field(2, &field(1, &field(9, b"narration"))))), Some(b64(&[0x80])), Some(b64(&[0x12, 0x64, 0x01, 0x02])), Some(b64(&field(2, &field(1, &field(8, &[0xff, 0xfe]))))), ];
for sig in &cases {
assert_eq!(thinking_signature_tag(sig.as_deref()), None, "{sig:?}");
assert_eq!(
thinking_block_class(sig.as_deref()),
Class::AgentThinking,
"{sig:?}"
);
}
}
#[test]
fn last_field_wins_at_every_level() {
let outer_repeat = {
let mut b = field(2, &field(1, &field(8, b"thinking")));
b.extend(field(2, &field(1, &field(8, b"narration"))));
b64(&b)
};
assert_eq!(
thinking_block_class(Some(&outer_repeat)),
Class::AgentThinkingNarration
);
let inner_repeat = {
let mut inner = field(8, b"narration");
inner.extend(field(8, b"thinking"));
b64(&field(2, &field(1, &inner)))
};
assert_eq!(
thinking_block_class(Some(&inner_repeat)),
Class::AgentThinking
);
}
#[test]
fn foreign_fields_skip_and_unknown_wire_aborts() {
let mut b = vec![0x08, 0x04]; b.extend([0x19, 1, 2, 3, 4, 5, 6, 7, 8]); b.extend([0x25, 9, 9, 9, 9]); b.extend(field(2, &field(1, &field(8, b"narration"))));
assert_eq!(
thinking_block_class(Some(&b64(&b))),
Class::AgentThinkingNarration
);
let mut torn = field(2, &field(1, &field(8, b"narration")));
torn.push(0x0b); assert_eq!(
thinking_block_class(Some(&b64(&torn))),
Class::AgentThinking
);
}
#[test]
fn whole_string_decode_survives_a_giant_leading_field() {
let mut b = field(15, &vec![0x2a; 60_000]);
b.extend(field(2, &field(1, &field(8, b"narration"))));
let sig = b64(&b);
assert!(sig.len() > 80_000);
assert_eq!(
thinking_block_class(Some(&sig)),
Class::AgentThinkingNarration
);
}
#[test]
fn classify_splits_thinking_records_by_signature_only() {
let sig = tagged_signature("narration");
for text in ["placeholder narration summary", ""] {
let rec: Record = serde_json::from_str(&format!(
r#"{{"type":"assistant","message":{{"role":"assistant","content":[{{"type":"thinking","thinking":"{text}","signature":"{sig}"}}]}}}}"#
))
.unwrap();
assert_eq!(
rec.classify(&ClassifyCtx::top_level()),
vec![Class::AgentThinkingNarration]
);
}
let tsig = tagged_signature("thinking");
for sig_json in [format!(r#","signature":"{tsig}""#), String::new()] {
let rec: Record = serde_json::from_str(&format!(
r#"{{"type":"assistant","message":{{"role":"assistant","content":[{{"type":"thinking","thinking":"placeholder reasoning text"{sig_json}}}]}}}}"#
))
.unwrap();
assert_eq!(
rec.classify(&ClassifyCtx::top_level()),
vec![Class::AgentThinking]
);
}
}
#[test]
fn the_byte_gate_is_sound_across_all_three_alignments() {
for pad in 0..3usize {
let mut outer = vec![0x08, 0x02];
outer.extend(field(14, &vec![0x2a; 4 + pad]));
outer.extend(field(2, &field(1, &field(8, b"narration"))));
let sig = b64(&outer);
assert_eq!(
thinking_block_class(Some(&sig)),
Class::AgentThinkingNarration,
"pad {pad}"
);
}
let mut outer = vec![0x08, 0x02];
outer.extend(field(2, &field(1, &field(8, b"narration"))));
let sig = b64(&outer);
let mid = sig.len() / 2;
let spaced = format!("{} {}", &sig[..mid], &sig[mid..]);
assert_eq!(
thinking_block_class(Some(&spaced)),
Class::AgentThinkingNarration
);
}