use std::io::stderr;
use std::time::Duration;
use embacle::types::{ChatMessage, ChatRequest, LlmProvider};
use embacle::CopilotHeadlessRunner;
use tokio::time::timeout;
use tracing::Level;
use tracing_subscriber::fmt;
const CHARS_PER_TOKEN: usize = 4;
fn prefix_of(approx_tokens: usize, mutate: bool) -> String {
let unit = "The coach grounds every plan in the athlete's own recorded sessions, \
citing each by name, date and one measured field. ";
let mut s = String::with_capacity(approx_tokens * CHARS_PER_TOKEN + unit.len());
while s.len() < approx_tokens * CHARS_PER_TOKEN {
s.push_str(unit);
}
if mutate {
let at = 2_000.min(s.len().saturating_sub(unit.len()));
s.replace_range(at..at + 5, "XXXXX");
}
s
}
fn request(prefix: &str) -> ChatRequest {
ChatRequest {
messages: vec![
ChatMessage::system(prefix),
ChatMessage::user("Reply with the single word: pong."),
],
model: None,
temperature: Some(0.0),
max_tokens: Some(16),
stream: false,
tools: None,
tool_choice: None,
top_p: None,
stop: None,
response_format: None,
turn_id: None,
mcp_servers: Vec::new(),
}
}
#[tokio::main]
async fn main() {
fmt()
.with_max_level(Level::DEBUG)
.with_target(true)
.with_writer(stderr)
.init();
let runner = CopilotHeadlessRunner::from_env();
let arms: [(&str, usize, bool); 5] = [
("floor", 32, false),
("s10", 10_000, false),
("s20", 20_000, false),
("s40", 40_000, false),
("vary", 20_000, true),
];
println!("=== ACP cache boundary probe ===");
println!("Does the cached prefix follow OUR stable head, or sit at a fixed vendor offset?\n");
println!(
"{:<7} {:>10} {:>12} {:>12} {:>12}",
"arm", "prefix~tok", "t1_write", "t2_read", "t2_write"
);
let mut results: Vec<(String, usize, u64)> = Vec::new();
for (label, tokens, mutate) in arms {
let first = prefix_of(tokens, false);
let second = prefix_of(tokens, mutate);
let mut t1_write = 0_u64;
let mut t2_read = 0_u64;
let mut t2_write = 0_u64;
for (turn, body) in [(1_u8, &first), (2_u8, &second)] {
match timeout(Duration::from_mins(3), runner.complete(&request(body))).await {
Ok(Ok(resp)) => {
let u = resp.usage;
let read = u.as_ref().and_then(|u| u.cached_read_tokens).unwrap_or(0);
let write = u.as_ref().and_then(|u| u.cached_write_tokens).unwrap_or(0);
if turn == 1 {
t1_write = u64::from(write);
} else {
t2_read = u64::from(read);
t2_write = u64::from(write);
}
}
Ok(Err(e)) => println!(" {label} turn {turn}: error {e}"),
Err(_) => println!(" {label} turn {turn}: timed out"),
}
}
println!("{label:<7} {tokens:>10} {t1_write:>12} {t2_read:>12} {t2_write:>12}");
results.push((label.to_owned(), tokens, t2_read));
}
println!("\n--- reading the result ---");
let floor = results.first().map_or(0, |r| r.2);
let s20 = results.iter().find(|r| r.0 == "s20").map_or(0, |r| r.2);
let s40 = results.iter().find(|r| r.0 == "s40").map_or(0, |r| r.2);
let vary = results.iter().find(|r| r.0 == "vary").map_or(0, |r| r.2);
println!("vendor floor (tiny prefix): {floor}");
println!(
"s20 above floor: {}",
s20.saturating_sub(floor)
);
println!(
"s40 above floor: {}",
s40.saturating_sub(floor)
);
println!(
"vary above floor: {}",
vary.saturating_sub(floor)
);
println!(
"\nIf s40 > s20 > floor, the boundary FOLLOWS our prefix and prompt-ordering work pays.\n\
If s10 == s20 == s40, it is FIXED and no reordering can move it.\n\
If vary << s20, an early-varying byte truncates the cached head — which is the\n\
one lever available to us, since the protocol offers no cache_control at all."
);
}