From 0e563b50c508bd50638642909ecce6b632e32953 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 11 Jul 2026 12:28:02 +0800 Subject: [PATCH] fix(cache): surface unsupported breakpoint counts Warn when caller-provided cache breakpoints already exceed the supported total of four while preserving the original markers and upgrading their TTLs. Clarify that automatic injection is governed by the remaining breakpoint budget, and add regression coverage proving excess caller markers are never deleted or reordered. --- src-tauri/src/proxy/cache_injector.rs | 35 +++++++++++++++++++++++++++ src-tauri/src/proxy/forwarder.rs | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/proxy/cache_injector.rs b/src-tauri/src/proxy/cache_injector.rs index e2b0464ad..a6bf8c9c9 100644 --- a/src-tauri/src/proxy/cache_injector.rs +++ b/src-tauri/src/proxy/cache_injector.rs @@ -13,6 +13,15 @@ pub fn inject(body: &mut Value, config: &OptimizerConfig) { let existing = count_existing(body); + if existing > 4 { + // Existing markers are caller-owned. Do not silently delete or reorder + // them; surface the invalid/unsupported total and leave validation to + // the upstream provider. + log::warn!( + "[OPT] cache: existing breakpoint count {existing} exceeds the supported total of 4; preserving caller input" + ); + } + // 升级已有断点的 TTL upgrade_existing_ttl(body, &config.cache_ttl); @@ -288,6 +297,32 @@ mod tests { .is_some()); } + #[test] + fn test_more_than_four_existing_breakpoints_are_preserved() { + let mut body = json!({ + "model": "test", + "tools": [ + {"name": "t1", "cache_control": {"type": "ephemeral"}}, + {"name": "t2", "cache_control": {"type": "ephemeral"}} + ], + "system": [ + {"type": "text", "text": "s1", "cache_control": {"type": "ephemeral"}}, + {"type": "text", "text": "s2", "cache_control": {"type": "ephemeral"}} + ], + "messages": [{"role": "user", "content": [ + {"type": "text", "text": "m1", "cache_control": {"type": "ephemeral"}}, + {"type": "text", "text": "m2"} + ]}] + }); + + inject(&mut body, &default_config()); + + assert_eq!(count_existing(&body), 5); + assert!(body["messages"][0]["content"][1] + .get("cache_control") + .is_none()); + } + #[test] fn test_system_string_converted_to_array() { let mut body = json!({ diff --git a/src-tauri/src/proxy/forwarder.rs b/src-tauri/src/proxy/forwarder.rs index ab6ab134e..5d62fed39 100644 --- a/src-tauri/src/proxy/forwarder.rs +++ b/src-tauri/src/proxy/forwarder.rs @@ -1450,7 +1450,7 @@ impl RequestForwarder { // configured TTL rather than silently forcing 5m on this conversion path. // otherwise system/tools/history are re-sent at full price every round, // inflating cost and first-token latency. The injector handles the - // string→array `system` conversion and the 4-breakpoint cap. + // string→array `system` conversion and the new-breakpoint budget. super::cache_injector::inject( &mut anthropic_body, &super::types::OptimizerConfig {