1use axum::{
2 body::Body,
3 extract::State,
4 http::{Request, StatusCode},
5 response::Response,
6};
7use serde_json::Value;
8
9use super::ProxyState;
10use super::forward;
11use super::tool_kind::{self, ToolResultKind};
12use super::{cache_safety, prose};
13use crate::core::config::{HistoryMode, ProseRole};
14
15pub async fn handler(
16 State(state): State<ProxyState>,
17 req: Request<Body>,
18) -> Result<Response, StatusCode> {
19 let upstream = state.anthropic_upstream();
20 forward::forward_request(
21 State(state),
22 req,
23 &upstream,
24 "/v1/messages",
25 compress_request_body,
26 "Anthropic",
27 &[],
28 )
29 .await
30}
31
32fn compress_request_body(parsed: Value, original_size: usize) -> (Vec<u8>, usize, usize) {
33 let mut doc = parsed;
34 let mut modified = false;
35
36 let cfg = crate::core::config::Config::load();
39 let system_aggr = cfg.proxy.resolved_role_aggressiveness(ProseRole::System);
40 let user_aggr = cfg.proxy.resolved_role_aggressiveness(ProseRole::User);
41 let live_compress = cfg.proxy.live_compresses();
42 let mode = cfg.proxy.resolved_history_mode();
43 let inject_breakpoint = cfg.proxy.cache_breakpoint_enabled();
48 let align_volatile = cfg.proxy.cache_aligner_enabled();
53 let relocate_volatile = cfg.proxy.cache_align_relocate_enabled();
58 let cache_economics = cfg.proxy.cache_policy_enabled();
65 let arm = super::holdout::assign(
70 &super::holdout::anthropic_key(&doc),
71 cfg.proxy.output_holdout_fraction(),
72 );
73 if cfg.proxy.ccr_inband_enabled() {
79 modified |= super::ccr::splice_inband_in_place(&mut doc);
80 }
81 if arm == super::holdout::Arm::Treatment {
86 if let Some(effort) = cfg.proxy.resolved_effort() {
87 modified |= super::effort::apply_anthropic(&mut doc, effort);
88 }
89 if cfg.proxy.verbosity_steer_enabled() {
93 modified |= super::verbosity::apply_anthropic(&mut doc);
94 }
95 }
96 if !live_compress
101 && mode == HistoryMode::Off
102 && system_aggr.is_none()
103 && user_aggr.is_none()
104 && !modified
105 && !inject_breakpoint
106 && !align_volatile
107 && !relocate_volatile
108 && !cache_economics
109 {
110 let out = serde_json::to_vec(&doc).unwrap_or_default();
111 return (out, original_size, original_size);
112 }
113 let mut prose_segments: u64 = 0;
114
115 let cached = doc
120 .get("messages")
121 .and_then(|m| m.as_array())
122 .map_or(0, |m| super::history_prune::cached_prefix_len(m));
123
124 if cache_economics && let Some(m) = doc.get("messages").and_then(|m| m.as_array()) {
135 super::cache_attribution::record_request(m, cached);
136 }
137 let repack = cfg.proxy.repacks_cold_prefix()
142 && doc
143 .get("messages")
144 .and_then(|m| m.as_array())
145 .is_some_and(|m| {
146 super::cold_prefix::repack_decision(m, cached)
147 && (!cache_economics
148 || super::cache_policy::worth_repacking(doc.get("system"), m, cached))
149 });
150 let protect = if repack { 0 } else { cached };
153
154 if let Some(a) = system_aggr
159 && protect == 0
160 && let Some(system) = doc.get_mut("system")
161 && (repack || !prose::value_has_cache_control(system))
162 {
163 let n = prose::compress_system_value(system, a);
164 if n > 0 {
165 prose_segments += u64::from(n);
166 modified = true;
167 }
168 }
169
170 if let Some(messages) = doc.get_mut("messages").and_then(|m| m.as_array_mut()) {
171 let tool_names = tool_kind::anthropic_tool_names(messages);
174
175 let boundary = super::history_prune::prune_boundary(mode, messages.len());
179 modified |=
185 super::history_prune::prune_history_range(messages, protect, boundary, &tool_names);
186
187 for msg in messages.iter_mut() {
188 let role = msg.get("role").and_then(|r| r.as_str()).unwrap_or("");
189 if role != "user" {
190 continue;
191 }
192
193 if let Some(content) = msg.get_mut("content").and_then(|c| c.as_array_mut()) {
194 for block in content.iter_mut() {
195 if block.get("type").and_then(|t| t.as_str()) != Some("tool_result") {
196 continue;
197 }
198
199 let name = block
200 .get("tool_use_id")
201 .and_then(|v| v.as_str())
202 .and_then(|id| tool_names.get(id))
203 .map(String::as_str);
204 let kind = name.map_or(ToolResultKind::Other, tool_kind::classify_tool_name);
205
206 let excluded =
209 name.is_some_and(|n| cfg.proxy.is_tool_live_compress_excluded(n));
210 if live_compress
211 && !excluded
212 && let Some(inner_content) = block.get_mut("content")
213 {
214 modified |= compress_content_field(inner_content, name, kind);
215 }
216 }
217 }
218 }
219
220 if let Some(a) = user_aggr {
225 let end = boundary.min(messages.len());
226 let start = protect.min(end);
227 for msg in &mut messages[start..end] {
228 if msg.get("role").and_then(|r| r.as_str()) == Some("user")
229 && let Some(content) = msg.get_mut("content").and_then(|c| c.as_array_mut())
230 {
231 prose_segments += u64::from(prose::compress_text_blocks(content, a));
232 }
233 }
234 }
235 }
236
237 if prose_segments > 0 {
238 modified = true;
239 }
240 if align_volatile
246 && cached == 0
247 && let Some(system) = doc.get("system")
248 && !prose::value_has_cache_control(system)
249 && let Some(text) = super::cache_aligner::system_text(system)
250 {
251 let scan = super::cache_aligner::scan_volatile(&text);
252 cache_safety::record_volatile_system(scan.fields as u64);
253 }
254 if relocate_volatile
263 && arm == super::holdout::Arm::Treatment
264 && cached == 0
265 && doc
266 .get("system")
267 .is_some_and(|s| !prose::value_has_cache_control(s))
268 {
269 let relocated = super::cache_aligner::apply_anthropic_relocate(&mut doc);
270 if relocated > 0 {
271 modified = true;
272 cache_safety::record_volatile_relocated(relocated as u64);
273 }
274 }
275 if inject_breakpoint
284 && cached == 0
285 && doc
286 .get("system")
287 .is_some_and(|s| !prose::value_has_cache_control(s))
288 && super::cache_breakpoint::inject_anthropic_system(&mut doc)
289 {
290 modified = true;
291 cache_safety::record_breakpoint_injected();
292 }
293 if repack {
298 cache_safety::record_cold_repack();
299 }
300 cache_safety::record(prose_segments, true);
301
302 let out = serde_json::to_vec(&doc).unwrap_or_default();
303 let compressed_size = if modified { out.len() } else { original_size };
304 (out, original_size, compressed_size)
305}
306
307fn compress_content_field(
310 content: &mut Value,
311 tool_name: Option<&str>,
312 kind: ToolResultKind,
313) -> bool {
314 match content {
315 Value::String(s) => super::tool_output::compress_text(s, tool_name, kind),
316 Value::Array(arr) => {
317 let mut modified = false;
318 for item in arr.iter_mut() {
319 if item.get("type").and_then(|t| t.as_str()) == Some("text")
320 && let Some(Value::String(text)) = item.get_mut("text")
321 {
322 modified |= super::tool_output::compress_text(text, tool_name, kind);
323 }
324 }
325 modified
326 }
327 _ => false,
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::super::compress::compress_tool_result;
334 use super::*;
335
336 fn source_file_body() -> Vec<u8> {
337 let code = (0..60)
338 .map(|i| format!(" let binding_{i} = compute_value_{i}(context, options);"))
339 .collect::<Vec<_>>()
340 .join("\n");
341 let body = serde_json::json!({
342 "model": "claude-opus-4-8",
343 "messages": [
344 {
345 "role": "assistant",
346 "content": [{"type": "tool_use", "id": "toolu_1", "name": "Read", "input": {"file_path": "src/app.rs"}}]
347 },
348 {
349 "role": "user",
350 "content": [{"type": "tool_result", "tool_use_id": "toolu_1", "content": code}]
351 }
352 ]
353 });
354 serde_json::to_vec(&body).unwrap()
355 }
356
357 #[test]
358 fn read_tool_result_is_never_truncated() {
359 let bytes = source_file_body();
360 let body: Value = serde_json::from_slice(&bytes).unwrap();
361 let (out, _orig, _comp) = compress_request_body(body, bytes.len());
362 let parsed: Value = serde_json::from_slice(&out).unwrap();
363 let content = parsed["messages"][1]["content"][0]["content"]
364 .as_str()
365 .unwrap();
366 assert!(
367 content.contains("binding_59"),
368 "the full source body must survive — refactors need it intact"
369 );
370 assert!(!content.contains("lines omitted"));
371 }
372
373 fn forge_log_body(tool_name: &str) -> Value {
374 let mut log = String::new();
378 for i in 0..90 {
379 log.push_str(&format!(
380 "INFO processing item {i}: ok, latency={i}ms, queue depth normal, retries 0\n"
381 ));
382 }
383 serde_json::json!({
384 "messages": [
385 {"role": "assistant", "content": [{"type": "tool_use", "id": "f1", "name": tool_name, "input": {}}]},
386 {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "f1", "content": log}]}
387 ]
388 })
389 }
390
391 #[test]
392 fn forge_shell_tool_result_compresses() {
393 let body = forge_log_body("forge_shell");
396 let bytes = serde_json::to_vec(&body).unwrap();
397 let (_out, orig, comp) = compress_request_body(body, bytes.len());
398 assert!(comp < orig, "foreign shell output must be compressed");
399 }
400
401 #[test]
402 fn foreign_read_tool_protects_source() {
403 let code = (0..60)
406 .map(|i| format!(" let binding_{i} = compute_value_{i}(context, options);"))
407 .collect::<Vec<_>>()
408 .join("\n");
409 let body = serde_json::json!({
410 "messages": [
411 {"role": "assistant", "content": [{"type": "tool_use", "id": "r1", "name": "forge_read", "input": {"path": "src/app.rs"}}]},
412 {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "r1", "content": code}]}
413 ]
414 });
415 let bytes = serde_json::to_vec(&body).unwrap();
416 let (out, _orig, _comp) = compress_request_body(body, bytes.len());
417 let parsed: Value = serde_json::from_slice(&out).unwrap();
418 let content = parsed["messages"][1]["content"][0]["content"]
419 .as_str()
420 .unwrap();
421 assert!(
422 content.contains("binding_59"),
423 "source body must survive intact"
424 );
425 }
426
427 #[test]
428 fn compress_request_body_is_deterministic() {
429 let _lock = crate::core::data_dir::test_env_lock();
432 let bytes = serde_json::to_vec(&forge_log_body("Bash")).unwrap();
435 let a = compress_request_body(serde_json::from_slice(&bytes).unwrap(), bytes.len()).0;
436 let b = compress_request_body(serde_json::from_slice(&bytes).unwrap(), bytes.len()).0;
437 assert_eq!(a, b, "identical input must yield byte-identical output");
438 }
439
440 fn big_log() -> String {
442 (0..200)
443 .map(|i| format!("[info] processed item {i:04} ok, latency {i}ms, queue normal"))
444 .collect::<Vec<_>>()
445 .join("\n")
446 }
447
448 #[test]
449 fn inband_ccr_emit_echo_splice_round_trip() {
450 let _iso = crate::core::data_dir::isolated_data_dir();
454 crate::test_env::remove_var("LEAN_CTX_PROXY_CCR_INBAND");
455 crate::core::config::Config::update_global(|c| {
456 c.proxy.ccr_inband = Some(true);
457 })
458 .unwrap();
459
460 let log = big_log();
462 let emit = serde_json::json!({
463 "messages": [
464 {"role": "assistant", "content": [{"type": "tool_use", "id": "t1", "name": "bash", "input": {}}]},
465 {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "t1", "content": log}]}
466 ]
467 });
468 let bytes = serde_json::to_vec(&emit).unwrap();
469 let (out, _o, _c) = compress_request_body(emit, bytes.len());
470 let emitted: Value = serde_json::from_slice(&out).unwrap();
471 let stub = emitted["messages"][1]["content"][0]["content"]
472 .as_str()
473 .unwrap();
474 assert!(
475 stub.contains("<lc_expand:"),
476 "in-band stub must advertise an echo-able marker: {stub}"
477 );
478 assert!(
479 !stub.contains("/tee/proxy_"),
480 "in-band stub must not leak the unreachable local tee path: {stub}"
481 );
482
483 let start = stub.find("<lc_expand:").unwrap();
485 let end = stub[start..].find('>').unwrap() + start + 1;
486 let marker = &stub[start..end];
487
488 let echo = serde_json::json!({
491 "messages": [
492 {"role": "user", "content": [{"type": "text", "text": "look again"}]},
493 {"role": "assistant", "content": format!("revisiting that output: {marker}")}
494 ]
495 });
496 let bytes = serde_json::to_vec(&echo).unwrap();
497 let (out, _o, _c) = compress_request_body(echo, bytes.len());
498 let spliced: Value = serde_json::from_slice(&out).unwrap();
499 let assistant = spliced["messages"][1]["content"].as_str().unwrap();
500 assert!(
501 assistant.contains("processed item 0007 ok")
502 && assistant.contains("processed item 0199 ok"),
503 "the verbatim original must be spliced back in full: {assistant}"
504 );
505 assert!(
506 !assistant.contains("<lc_expand:"),
507 "the marker must be consumed by the splice"
508 );
509 }
510
511 #[test]
512 fn inband_marker_less_turn_is_byte_identical_on_or_off() {
513 let _iso = crate::core::data_dir::isolated_data_dir();
518 crate::test_env::remove_var("LEAN_CTX_PROXY_CCR_INBAND");
519 let body = serde_json::json!({
520 "messages": [
521 {"role": "user", "content": [{"type": "text", "text": "hello there"}]},
522 {"role": "assistant", "content": "hi — how can I help?"}
523 ]
524 });
525 let bytes = serde_json::to_vec(&body).unwrap();
526
527 crate::core::config::Config::update_global(|c| c.proxy.ccr_inband = Some(false)).unwrap();
528 let off = compress_request_body(body.clone(), bytes.len()).0;
529 crate::core::config::Config::update_global(|c| c.proxy.ccr_inband = Some(true)).unwrap();
530 let on = compress_request_body(body, bytes.len()).0;
531
532 assert_eq!(
533 off, on,
534 "a marker-less request must be byte-identical whether in-band is on or off"
535 );
536 }
537
538 fn big_prose() -> String {
540 let p = "You are a careful, senior software engineer. You always explain your \
541 reasoning before making changes, you prefer small reviewable diffs, and \
542 you never introduce mock data or placeholders into production code. ";
543 [p; 6].join("\n")
544 }
545
546 #[test]
547 fn system_prose_compressed_and_assistant_untouched() {
548 let _iso = crate::core::data_dir::isolated_data_dir();
549 crate::core::config::Config::update_global(|c| {
550 c.proxy.role_aggressiveness.system = Some(0.6);
551 c.proxy.role_aggressiveness.user = Some(0.6);
552 })
553 .unwrap();
554
555 let prose = big_prose();
556 let assistant_text = big_prose();
557 let body = serde_json::json!({
558 "model": "claude-opus-4-8",
559 "system": prose,
560 "messages": [
561 {"role": "user", "content": [{"type": "text", "text": prose}]},
562 {"role": "assistant", "content": assistant_text},
563 ]
564 });
565 let bytes = serde_json::to_vec(&body).unwrap();
566 let (out, _orig, _comp) = compress_request_body(body, bytes.len());
567 let parsed: Value = serde_json::from_slice(&out).unwrap();
568
569 assert!(
570 parsed["system"].as_str().unwrap().len() < prose.len(),
571 "system prose must be compressed when enabled"
572 );
573 assert_eq!(
574 parsed["messages"][1]["content"].as_str().unwrap(),
575 assistant_text,
576 "assistant turns must pass through verbatim (#710)"
577 );
578 }
579
580 #[test]
581 fn user_prose_compressed_only_in_frozen_region() {
582 let _iso = crate::core::data_dir::isolated_data_dir();
583 crate::core::config::Config::update_global(|c| {
584 c.proxy.role_aggressiveness.user = Some(0.7);
585 })
586 .unwrap();
587
588 let prose = big_prose();
589 let mut messages = Vec::new();
591 for i in 0..30 {
592 let role = if i % 2 == 0 { "user" } else { "assistant" };
593 messages.push(serde_json::json!({
594 "role": role,
595 "content": [{"type": "text", "text": prose}]
596 }));
597 }
598 let body = serde_json::json!({ "messages": messages });
599 let bytes = serde_json::to_vec(&body).unwrap();
600 let (out, _o, _c) = compress_request_body(body, bytes.len());
601 let parsed: Value = serde_json::from_slice(&out).unwrap();
602
603 let frozen_user = parsed["messages"][0]["content"][0]["text"]
604 .as_str()
605 .unwrap();
606 assert!(
607 frozen_user.len() < prose.len(),
608 "user prose in the frozen region must be compressed"
609 );
610 assert_eq!(
611 parsed["messages"][1]["content"][0]["text"]
612 .as_str()
613 .unwrap(),
614 prose,
615 "assistant prose is never compressed"
616 );
617 let live_tail_user = parsed["messages"][28]["content"][0]["text"]
618 .as_str()
619 .unwrap();
620 assert_eq!(
621 live_tail_user, prose,
622 "user prose in the live tail (>= boundary) must be preserved for quality"
623 );
624 }
625
626 #[test]
627 fn client_cached_prefix_disables_system_prose() {
628 let _iso = crate::core::data_dir::isolated_data_dir();
629 crate::core::config::Config::update_global(|c| {
630 c.proxy.role_aggressiveness.system = Some(0.9);
631 })
632 .unwrap();
633
634 let prose = big_prose();
635 let body = serde_json::json!({
636 "system": prose,
637 "messages": [
638 {"role": "user", "content": [
639 {"type": "text", "text": "hi", "cache_control": {"type": "ephemeral"}}
640 ]},
641 {"role": "assistant", "content": "ok"}
642 ]
643 });
644 let bytes = serde_json::to_vec(&body).unwrap();
645 let (out, _o, _c) = compress_request_body(body, bytes.len());
646 let parsed: Value = serde_json::from_slice(&out).unwrap();
647 assert_eq!(
648 parsed["system"].as_str().unwrap(),
649 prose,
650 "system must stay verbatim when the client caches a message prefix (#448)"
651 );
652 }
653
654 #[test]
655 fn prose_compression_is_deterministic() {
656 let _iso = crate::core::data_dir::isolated_data_dir();
657 crate::core::config::Config::update_global(|c| {
658 c.proxy.role_aggressiveness.system = Some(0.6);
659 })
660 .unwrap();
661 let prose = big_prose();
662 let mk = || serde_json::json!({"system": prose, "messages": [{"role": "user", "content": "hi"}]});
663 let (a, b) = (mk(), mk());
664 let la = serde_json::to_vec(&a).unwrap().len();
665 let lb = serde_json::to_vec(&b).unwrap().len();
666 assert_eq!(
667 compress_request_body(a, la).0,
668 compress_request_body(b, lb).0,
669 "prose compression must be byte-identical for identical input (#498)"
670 );
671 }
672
673 #[test]
674 fn bash_tool_result_still_compresses() {
675 let log = {
676 let mut s = String::from(
677 "$ git status\nOn branch main\nYour branch is up to date with 'origin/main'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n",
678 );
679 for i in 0..90 {
680 s.push_str(&format!("\tmodified: src/module_{i}/file_{i}.rs\n"));
681 }
682 s.push_str("\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
683 s
684 };
685 let body = serde_json::json!({
686 "messages": [
687 {"role": "assistant", "content": [{"type": "tool_use", "id": "t1", "name": "Bash", "input": {}}]},
688 {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "t1", "content": log}]}
689 ]
690 });
691 let bytes = serde_json::to_vec(&body).unwrap();
692 let (_out, orig, comp) = compress_request_body(body, bytes.len());
693 assert!(comp < orig, "shell output must still be compressed");
694 }
695
696 #[test]
697 fn json_envelope_tool_result_is_compressed() {
698 let _iso = crate::core::data_dir::isolated_data_dir();
699 let log = long_git_status();
700 let expected = compress_tool_result(&log, Some("Bash"));
701 let envelope = serde_json::to_string(&serde_json::json!({
702 "content": [{"type": "text", "text": log}],
703 "isError": false,
704 }))
705 .unwrap();
706 let body = serde_json::json!({
707 "messages": [
708 {"role": "assistant", "content": [{
709 "type": "tool_use",
710 "id": "t1",
711 "name": "Bash",
712 "input": {}
713 }]},
714 {"role": "user", "content": [{
715 "type": "tool_result",
716 "tool_use_id": "t1",
717 "content": envelope
718 }]}
719 ]
720 });
721 let bytes = serde_json::to_vec(&body).unwrap();
722 let (out, orig, comp) = compress_request_body(body, bytes.len());
723
724 assert!(comp < orig, "JSON envelope tool result should shrink");
725 let parsed: Value = serde_json::from_slice(&out).unwrap();
726 let content = parsed["messages"][1]["content"][0]["content"]
727 .as_str()
728 .unwrap();
729 let envelope: Value = serde_json::from_str(content).unwrap();
730 assert_eq!(envelope["content"][0]["text"].as_str().unwrap(), expected);
731 }
732
733 fn long_git_status() -> String {
734 let mut s = String::from(
735 "$ git status\nOn branch main\nYour branch is up to date with 'origin/main'.\n\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n",
736 );
737 for i in 0..80 {
738 s.push_str(&format!("\tmodified: src/module_{i}/file_{i}.rs\n"));
739 }
740 s.push_str("\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
741 s
742 }
743
744 fn cached_prefix_body(first_text: &str, prose: &str) -> (Vec<Value>, Value) {
754 let messages = vec![
755 serde_json::json!({"role": "user", "content": [
756 {"type": "text", "text": first_text, "cache_control": {"type": "ephemeral"}}
757 ]}),
758 serde_json::json!({"role": "assistant", "content": "ok"}),
759 ];
760 let body = serde_json::json!({ "system": prose, "messages": messages.clone() });
761 (messages, body)
762 }
763
764 #[test]
765 fn cold_prefix_repack_rewrites_protected_system_prose_when_enabled() {
766 let _iso = crate::core::data_dir::isolated_data_dir();
767 crate::test_env::remove_var("LEAN_CTX_PROXY_COLD_PREFIX_REPACK");
768 crate::core::config::Config::update_global(|c| {
769 c.proxy.role_aggressiveness.system = Some(0.9);
770 c.proxy.cold_prefix_repack = Some(true);
771 })
772 .unwrap();
773
774 let prose = big_prose().repeat(6);
779 let (messages, body) = cached_prefix_body("cold-repack-enabled-session", &prose);
780 super::super::cold_prefix::test_seed_last_touch(&messages, 3 * 60 * 60);
782
783 let bytes = serde_json::to_vec(&body).unwrap();
784 let (out, _o, _c) = compress_request_body(body, bytes.len());
785 let parsed: Value = serde_json::from_slice(&out).unwrap();
786 assert!(
787 parsed["system"].as_str().unwrap().len() < prose.len(),
788 "a predicted-cold prefix must let the proxy repack the otherwise-protected system prose"
789 );
790 }
791
792 #[test]
793 fn cold_prefix_repack_skipped_for_subcacheable_prefix_by_default() {
794 let _iso = crate::core::data_dir::isolated_data_dir();
800 crate::test_env::remove_var("LEAN_CTX_PROXY_COLD_PREFIX_REPACK");
801 crate::test_env::remove_var("LEAN_CTX_PROXY_CACHE_POLICY");
802 crate::core::config::Config::update_global(|c| {
803 c.proxy.role_aggressiveness.system = Some(0.9);
804 c.proxy.cold_prefix_repack = Some(true);
805 })
806 .unwrap();
807
808 let prose = big_prose();
809 let (messages, body) = cached_prefix_body("cold-repack-subcacheable-session", &prose);
810 super::super::cold_prefix::test_seed_last_touch(&messages, 3 * 60 * 60);
811
812 let bytes = serde_json::to_vec(&body).unwrap();
813 let (out, _o, _c) = compress_request_body(body, bytes.len());
814 let parsed: Value = serde_json::from_slice(&out).unwrap();
815 assert_eq!(
816 parsed["system"].as_str().unwrap(),
817 prose,
818 "the net-cost gate must skip repacking a sub-cacheable prefix (premium default)"
819 );
820 }
821
822 #[test]
823 fn cold_prefix_repack_off_by_default_keeps_prefix_protected() {
824 let _iso = crate::core::data_dir::isolated_data_dir();
825 crate::test_env::remove_var("LEAN_CTX_PROXY_COLD_PREFIX_REPACK");
826 crate::core::config::Config::update_global(|c| {
827 c.proxy.role_aggressiveness.system = Some(0.9);
828 c.proxy.cold_prefix_repack = Some(false);
829 })
830 .unwrap();
831
832 let prose = big_prose();
833 let (messages, body) = cached_prefix_body("cold-repack-disabled-session", &prose);
834 super::super::cold_prefix::test_seed_last_touch(&messages, 24 * 60 * 60);
836
837 let bytes = serde_json::to_vec(&body).unwrap();
838 let (out, _o, _c) = compress_request_body(body, bytes.len());
839 let parsed: Value = serde_json::from_slice(&out).unwrap();
840 assert_eq!(
841 parsed["system"].as_str().unwrap(),
842 prose,
843 "with repack off the cached prefix stays byte-stable regardless of idle time (#448)"
844 );
845 }
846
847 #[test]
848 fn cold_prefix_repack_protects_warm_prefix_even_when_enabled() {
849 let _iso = crate::core::data_dir::isolated_data_dir();
850 crate::test_env::remove_var("LEAN_CTX_PROXY_COLD_PREFIX_REPACK");
851 crate::core::config::Config::update_global(|c| {
852 c.proxy.role_aggressiveness.system = Some(0.9);
853 c.proxy.cold_prefix_repack = Some(true);
854 })
855 .unwrap();
856
857 let prose = big_prose();
858 let (messages, body) = cached_prefix_body("cold-repack-warm-session", &prose);
859 super::super::cold_prefix::test_seed_last_touch(&messages, 60);
861
862 let bytes = serde_json::to_vec(&body).unwrap();
863 let (out, _o, _c) = compress_request_body(body, bytes.len());
864 let parsed: Value = serde_json::from_slice(&out).unwrap();
865 assert_eq!(
866 parsed["system"].as_str().unwrap(),
867 prose,
868 "a warm prefix must stay protected even with repack enabled — only LARGE gaps trigger"
869 );
870 }
871
872 #[test]
873 fn cache_policy_attribution_is_measurement_only() {
874 let _iso = crate::core::data_dir::isolated_data_dir();
878 crate::test_env::remove_var("LEAN_CTX_PROXY_CACHE_POLICY");
879 crate::test_env::remove_var("LEAN_CTX_PROXY_COLD_PREFIX_REPACK");
880
881 let prose = big_prose();
882 let (_messages, body) = cached_prefix_body("cache-policy-measurement-session", &prose);
883 let bytes = serde_json::to_vec(&body).unwrap();
884
885 crate::core::config::Config::update_global(|c| {
886 c.proxy.cache_policy = Some(false);
887 })
888 .unwrap();
889 let (off, _o, _c) = compress_request_body(body.clone(), bytes.len());
890
891 crate::core::config::Config::update_global(|c| {
892 c.proxy.cache_policy = Some(true);
893 })
894 .unwrap();
895 let (on, _o, _c) = compress_request_body(body, bytes.len());
896
897 assert_eq!(
898 off, on,
899 "miss attribution is measurement-only: the wire bytes must not change"
900 );
901 }
902
903 #[test]
904 fn effort_control_dials_adaptive_thinking_only() {
905 let _iso = crate::core::data_dir::isolated_data_dir();
908 crate::test_env::remove_var("LEAN_CTX_PROXY_EFFORT");
909 crate::core::config::Config::update_global(|c| {
910 c.proxy.effort = Some("medium".into());
911 })
912 .unwrap();
913
914 let adaptive = serde_json::json!({
915 "model": "claude-opus-4-8",
916 "thinking": {"type": "adaptive"},
917 "messages": [{"role": "user", "content": "hi"}]
918 });
919 let bytes = serde_json::to_vec(&adaptive).unwrap();
920 let (out, _o, _c) = compress_request_body(adaptive, bytes.len());
921 assert_eq!(
922 serde_json::from_slice::<Value>(&out).unwrap()["output_config"]["effort"],
923 "medium"
924 );
925
926 let plain = serde_json::json!({
929 "model": "claude-opus-4-8",
930 "messages": [{"role": "user", "content": "hi"}]
931 });
932 let bytes = serde_json::to_vec(&plain).unwrap();
933 let (out, _o, _c) = compress_request_body(plain, bytes.len());
934 assert!(
935 serde_json::from_slice::<Value>(&out)
936 .unwrap()
937 .get("output_config")
938 .is_none()
939 );
940 }
941
942 #[test]
943 fn verbosity_steer_applies_to_treatment_skips_control() {
944 let _iso = crate::core::data_dir::isolated_data_dir();
947 crate::test_env::remove_var("LEAN_CTX_PROXY_VERBOSITY_STEER");
948 crate::test_env::remove_var("LEAN_CTX_PROXY_OUTPUT_HOLDOUT");
949 crate::test_env::remove_var("LEAN_CTX_PROXY_EFFORT");
950
951 let req = serde_json::json!({
952 "model": "claude-opus-4-8",
953 "messages": [{"role": "user", "content": "Summarize the design."}]
954 });
955 let bytes = serde_json::to_vec(&req).unwrap();
956
957 crate::core::config::Config::update_global(|c| {
959 c.proxy.verbosity_steer = Some(true);
960 c.proxy.output_holdout = Some(0.0);
961 })
962 .unwrap();
963 let (out, _o, _c) = compress_request_body(req.clone(), bytes.len());
964 let v: Value = serde_json::from_slice(&out).unwrap();
965 assert!(
966 v["messages"][0]["content"]
967 .as_str()
968 .unwrap()
969 .contains(crate::proxy::verbosity::STEER),
970 "treatment arm must receive the verbosity steer"
971 );
972
973 crate::core::config::Config::update_global(|c| {
975 c.proxy.output_holdout = Some(1.0);
976 })
977 .unwrap();
978 let (out2, _o, _c) = compress_request_body(req, bytes.len());
979 let v2: Value = serde_json::from_slice(&out2).unwrap();
980 assert!(
981 !v2["messages"][0]["content"]
982 .as_str()
983 .unwrap()
984 .contains(crate::proxy::verbosity::STEER),
985 "control arm must NOT be steered (measurement baseline)"
986 );
987 }
988
989 fn cacheable_system() -> String {
992 "You are a careful, senior software engineer who writes maintainable code. ".repeat(400)
993 }
994
995 #[test]
996 fn cache_breakpoint_injected_on_unanchored_system_when_opt_in() {
997 let _iso = crate::core::data_dir::isolated_data_dir();
1000 crate::test_env::remove_var("LEAN_CTX_PROXY_CACHE_BREAKPOINT");
1001 let body = serde_json::json!({
1002 "model": "claude-opus-4-8",
1003 "system": cacheable_system(),
1004 "messages": [{"role": "user", "content": "Refactor the parser."}]
1005 });
1006 let bytes = serde_json::to_vec(&body).unwrap();
1007
1008 crate::core::config::Config::update_global(|c| c.proxy.cache_breakpoint = Some(true))
1009 .unwrap();
1010 let (out, _o, _c) = compress_request_body(body, bytes.len());
1011 let v: Value = serde_json::from_slice(&out).unwrap();
1012
1013 assert_eq!(
1014 v["system"][0]["cache_control"]["type"], "ephemeral",
1015 "an unanchored system prompt must receive one ephemeral breakpoint"
1016 );
1017 assert!(
1018 v["system"][0]["text"]
1019 .as_str()
1020 .unwrap()
1021 .contains("senior software engineer"),
1022 "the system text must be preserved verbatim under the marker"
1023 );
1024 }
1025
1026 #[test]
1027 fn cache_breakpoint_off_by_default_is_byte_unchanged() {
1028 let _iso = crate::core::data_dir::isolated_data_dir();
1031 crate::test_env::remove_var("LEAN_CTX_PROXY_CACHE_BREAKPOINT");
1032 let body = serde_json::json!({
1033 "model": "claude-opus-4-8",
1034 "system": cacheable_system(),
1035 "messages": [{"role": "user", "content": "Refactor the parser."}]
1036 });
1037 let bytes = serde_json::to_vec(&body).unwrap();
1038 let (out, _o, _c) = compress_request_body(body, bytes.len());
1039 assert_eq!(
1040 out, bytes,
1041 "default-off must leave the request byte-identical"
1042 );
1043 }
1044
1045 #[test]
1046 fn cache_breakpoint_respects_client_anchor() {
1047 let _iso = crate::core::data_dir::isolated_data_dir();
1050 crate::test_env::remove_var("LEAN_CTX_PROXY_CACHE_BREAKPOINT");
1051 let body = serde_json::json!({
1052 "model": "claude-opus-4-8",
1053 "system": cacheable_system(),
1054 "messages": [{
1055 "role": "user",
1056 "content": [{
1057 "type": "text",
1058 "text": "hello",
1059 "cache_control": {"type": "ephemeral"}
1060 }]
1061 }]
1062 });
1063 let bytes = serde_json::to_vec(&body).unwrap();
1064 crate::core::config::Config::update_global(|c| c.proxy.cache_breakpoint = Some(true))
1065 .unwrap();
1066 let (out, _o, _c) = compress_request_body(body, bytes.len());
1067 let v: Value = serde_json::from_slice(&out).unwrap();
1068 assert!(
1069 v["system"].is_string(),
1070 "with a client anchor present, system must be left untouched (no second breakpoint)"
1071 );
1072 }
1073
1074 #[test]
1075 fn cache_aligner_measures_without_mutating_body() {
1076 let _iso = crate::core::data_dir::isolated_data_dir();
1080 crate::test_env::remove_var("LEAN_CTX_PROXY_CACHE_ALIGNER");
1081 crate::test_env::remove_var("LEAN_CTX_PROXY_CACHE_BREAKPOINT");
1082 let body = serde_json::json!({
1083 "model": "claude-opus-4-8",
1084 "system": "Today is 2026-06-22. Session 550e8400-e29b-41d4-a716-446655440000.",
1085 "messages": [{"role": "user", "content": "Hello."}]
1086 });
1087 let bytes = serde_json::to_vec(&body).unwrap();
1088 crate::core::config::Config::update_global(|c| c.proxy.cache_aligner = Some(true)).unwrap();
1089 let (out, _o, _c) = compress_request_body(body, bytes.len());
1090 assert_eq!(
1091 out, bytes,
1092 "cache-aligner telemetry must never mutate the request body"
1093 );
1094 }
1095
1096 fn cacheable_system_with_date() -> String {
1099 format!("Today is 2026-06-27. {}", cacheable_system())
1100 }
1101
1102 fn clear_relocate_env() {
1103 crate::test_env::remove_var("LEAN_CTX_PROXY_CACHE_ALIGN_RELOCATE");
1104 crate::test_env::remove_var("LEAN_CTX_PROXY_CACHE_BREAKPOINT");
1105 crate::test_env::remove_var("LEAN_CTX_PROXY_CACHE_ALIGNER");
1106 crate::test_env::remove_var("LEAN_CTX_PROXY_OUTPUT_HOLDOUT");
1107 }
1108
1109 #[test]
1110 fn cache_align_relocate_moves_volatiles_to_tail_when_opt_in() {
1111 let _iso = crate::core::data_dir::isolated_data_dir();
1115 clear_relocate_env();
1116 let body = serde_json::json!({
1117 "model": "claude-opus-4-8",
1118 "system": cacheable_system_with_date(),
1119 "messages": [{"role": "user", "content": "Refactor the parser."}]
1120 });
1121 let bytes = serde_json::to_vec(&body).unwrap();
1122 crate::core::config::Config::update_global(|c| {
1123 c.proxy.cache_align_relocate = Some(true);
1124 c.proxy.output_holdout = Some(0.0);
1125 })
1126 .unwrap();
1127 let (out, _o, _c) = compress_request_body(body, bytes.len());
1128 let v: Value = serde_json::from_slice(&out).unwrap();
1129
1130 assert!(v["system"].is_array(), "system reshaped into a block array");
1131 assert_eq!(v["system"][0]["cache_control"]["type"], "ephemeral");
1132 assert!(
1133 !v["system"][0]["text"]
1134 .as_str()
1135 .unwrap()
1136 .contains("2026-06-27"),
1137 "the volatile date must leave the cacheable prefix"
1138 );
1139 assert!(
1140 v["system"][1].get("cache_control").is_none(),
1141 "the relocated tail block stays uncached"
1142 );
1143 assert!(
1144 v["system"][1]["text"]
1145 .as_str()
1146 .unwrap()
1147 .contains("2026-06-27"),
1148 "the date must be re-stated in the tail"
1149 );
1150 }
1151
1152 #[test]
1153 fn cache_align_relocate_off_by_default_is_byte_unchanged() {
1154 let _iso = crate::core::data_dir::isolated_data_dir();
1157 clear_relocate_env();
1158 let body = serde_json::json!({
1159 "model": "claude-opus-4-8",
1160 "system": cacheable_system_with_date(),
1161 "messages": [{"role": "user", "content": "Refactor the parser."}]
1162 });
1163 let bytes = serde_json::to_vec(&body).unwrap();
1164 let (out, _o, _c) = compress_request_body(body, bytes.len());
1165 assert_eq!(
1166 out, bytes,
1167 "default-off must leave the request byte-identical"
1168 );
1169 }
1170
1171 #[test]
1172 fn cache_align_relocate_skips_control_arm() {
1173 let _iso = crate::core::data_dir::isolated_data_dir();
1176 clear_relocate_env();
1177 let body = serde_json::json!({
1178 "model": "claude-opus-4-8",
1179 "system": cacheable_system_with_date(),
1180 "messages": [{"role": "user", "content": "Refactor the parser."}]
1181 });
1182 let bytes = serde_json::to_vec(&body).unwrap();
1183 crate::core::config::Config::update_global(|c| {
1184 c.proxy.cache_align_relocate = Some(true);
1185 c.proxy.output_holdout = Some(1.0);
1186 })
1187 .unwrap();
1188 let (out, _o, _c) = compress_request_body(body, bytes.len());
1189 let v: Value = serde_json::from_slice(&out).unwrap();
1190 assert!(
1191 v["system"].is_string(),
1192 "control arm must not be relocated (measurement baseline)"
1193 );
1194 }
1195
1196 #[test]
1197 fn cache_align_relocate_respects_client_anchor() {
1198 let _iso = crate::core::data_dir::isolated_data_dir();
1201 clear_relocate_env();
1202 let body = serde_json::json!({
1203 "model": "claude-opus-4-8",
1204 "system": cacheable_system_with_date(),
1205 "messages": [{
1206 "role": "user",
1207 "content": [{
1208 "type": "text",
1209 "text": "hello",
1210 "cache_control": {"type": "ephemeral"}
1211 }]
1212 }]
1213 });
1214 let bytes = serde_json::to_vec(&body).unwrap();
1215 crate::core::config::Config::update_global(|c| {
1216 c.proxy.cache_align_relocate = Some(true);
1217 c.proxy.output_holdout = Some(0.0);
1218 })
1219 .unwrap();
1220 let (out, _o, _c) = compress_request_body(body, bytes.len());
1221 let v: Value = serde_json::from_slice(&out).unwrap();
1222 assert!(
1223 v["system"].is_string(),
1224 "with a client anchor present, system must be left untouched"
1225 );
1226 }
1227
1228 #[test]
1229 fn cache_align_relocate_composes_with_breakpoint_to_one_anchor() {
1230 let _iso = crate::core::data_dir::isolated_data_dir();
1234 clear_relocate_env();
1235 let body = serde_json::json!({
1236 "model": "claude-opus-4-8",
1237 "system": cacheable_system_with_date(),
1238 "messages": [{"role": "user", "content": "Refactor the parser."}]
1239 });
1240 let bytes = serde_json::to_vec(&body).unwrap();
1241 crate::core::config::Config::update_global(|c| {
1242 c.proxy.cache_align_relocate = Some(true);
1243 c.proxy.cache_breakpoint = Some(true);
1244 c.proxy.output_holdout = Some(0.0);
1245 })
1246 .unwrap();
1247 let (out, _o, _c) = compress_request_body(body, bytes.len());
1248 let v: Value = serde_json::from_slice(&out).unwrap();
1249 let blocks = v["system"].as_array().expect("system is a block array");
1250 assert_eq!(blocks.len(), 2, "stable block + volatile tail");
1251 assert_eq!(blocks[0]["cache_control"]["type"], "ephemeral");
1252 assert!(
1253 blocks[1].get("cache_control").is_none(),
1254 "no second breakpoint — the tail stays uncached"
1255 );
1256 }
1257}