Files
Aether/crates/aether-provider/transport/src/claude_code/mimicry.rs
T
dalamudx c1aa5d618d feat(claude-code): 为 claude_code provider 补全 Claude Code 请求体特征
非 Claude Code 客户端(如 pi)经 OAuth 的 claude_code provider 转发时,
只有请求头被伪装成 Claude Code,请求体仍是客户端原样,被上游以
429 rate_limit_error 拒绝(响应无 ratelimit 额度头,并非真实限流)。

参考 sub2api 的 OAuth 请求体伪装,在传输层补齐请求体:
- system 重写为计费头 + 身份句 + 通用提示词三块(Fable 仅保留前两块)
- 原 system 迁入 messages 开头,避免丢失客户端指令
- 缺失时补 metadata.user_id,device/session id 基于 key 稳定派生
- 缺失时补 tools/temperature/max_tokens,并限制 cache_control 不超过 4 个
- 已带计费块且有 metadata.user_id 的真实 Claude Code 请求原样放行,
  重复应用幂等

接入点覆盖跨格式路径(apply_transport_request_body_semantics)和
原生 claude:messages 同格式路径,仅作用于 provider_type=claude_code。
2026-09-29 18:35:23 +08:00

675 lines
26 KiB
Rust

use serde_json::{json, Map, Value};
use sha2::{Digest, Sha256};
use super::profile::current_claude_code_transport_identity_profile;
use crate::snapshot::GatewayProviderTransportSnapshot;
const BILLING_HEADER_PREFIX: &str = "x-anthropic-billing-header";
const BILLING_ENTRYPOINT_MARKER: &str = "cc_entrypoint=";
const CLAUDE_CODE_SYSTEM_PROMPT: &str = "You are Claude Code, Anthropic's official CLI for Claude.";
const FINGERPRINT_SALT: &str = "59cf53e54c78";
const FINGERPRINT_CHAR_INDICES: [usize; 3] = [4, 7, 20];
const DEFAULT_MAX_TOKENS: u64 = 128_000;
const MAX_CACHE_CONTROL_BLOCKS: usize = 4;
const SYSTEM_INSTRUCTIONS_ACK: &str = "Understood. I will follow these instructions.";
const DEVICE_ID_SEED_NAMESPACE: &str = "aether-claude-code-device";
const CLAUDE_CODE_PROMPT_PREFIXES: [&str; 4] = [
"You are Claude Code, Anthropic's official CLI for Claude",
"You are a Claude agent, built on Anthropic's Claude Agent SDK",
"You are a file search specialist for Claude Code",
"You are a helpful AI assistant tasked with summarizing conversations",
];
/// Tool-agnostic part of the real Claude Code system prompt. It brings the system block
/// count and size close to genuine CLI traffic without injecting tool-specific instructions.
const CLAUDE_CODE_SYSTEM_PROMPT_EXPANSION: &str = r#"You are an interactive agent that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
IMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.
IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
# Tone and style
- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
- Your responses should be short and concise.
- When referencing specific functions or pieces of code include the pattern file_path:line_number to allow the user to easily navigate to the source code location.
- When referencing GitHub issues or pull requests, use the owner/repo#123 format (e.g. anthropics/claude-code#100) so they render as clickable links.
- Do not use a colon before tool calls. Your tool calls may not be shown directly in the output, so text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period."#;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClaudeCodeBodyMimicryContext<'a> {
pub key_id: &'a str,
pub device_id: Option<&'a str>,
pub account_uuid: Option<&'a str>,
}
/// Applies Claude Code body mimicry for a claude_code provider transport (claude:messages only).
/// Returns whether the body was changed.
pub fn apply_claude_code_body_mimicry_for_transport(
provider_request_body: &mut Value,
transport: &GatewayProviderTransportSnapshot,
provider_api_format: &str,
) -> bool {
if !transport
.provider
.provider_type
.trim()
.eq_ignore_ascii_case("claude_code")
|| !aether_ai_formats::normalize_api_format_alias(provider_api_format)
.eq_ignore_ascii_case("claude:messages")
{
return false;
}
let auth_config = transport
.key
.decrypted_auth_config
.as_deref()
.and_then(|raw| serde_json::from_str::<Value>(raw).ok());
let auth_config_str = |field: &str| {
auth_config
.as_ref()
.and_then(|config| config.get(field))
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
};
let device_id = auth_config_str("device_id");
let account_uuid = auth_config_str("account_uuid");
apply_claude_code_body_mimicry(
provider_request_body,
ClaudeCodeBodyMimicryContext {
key_id: transport.key.id.as_str(),
device_id: device_id.as_deref(),
account_uuid: account_uuid.as_deref(),
},
)
}
/// Makes a non-Claude-Code request body look like genuine Claude Code traffic so it is
/// accepted together with the Claude Code identity headers on OAuth credentials.
/// The operation is idempotent: a body that already carries the billing block and
/// `metadata.user_id` is treated as genuine and left untouched.
pub fn apply_claude_code_body_mimicry(
body: &mut Value,
context: ClaudeCodeBodyMimicryContext<'_>,
) -> bool {
let before = body.clone();
let Some(object) = body.as_object_mut() else {
return false;
};
if is_genuine_claude_code_body(object) {
return false;
}
let profile = *current_claude_code_transport_identity_profile();
let model = object
.get("model")
.and_then(Value::as_str)
.unwrap_or_default()
.to_ascii_lowercase();
let first_user_text = first_user_text(object);
rewrite_system_and_migrate_instructions(
object,
profile.billing_cli_version(),
&first_user_text,
model.contains("fable"),
);
inject_metadata_user_id(object, &context, &first_user_text);
fill_fixed_fields(object, &model);
enforce_cache_control_limit(object);
*body != before
}
fn is_genuine_claude_code_body(body: &Map<String, Value>) -> bool {
let has_user_id = body
.get("metadata")
.and_then(|metadata| metadata.get("user_id"))
.and_then(Value::as_str)
.is_some_and(|value| !value.trim().is_empty());
has_user_id
&& body
.get("system")
.and_then(Value::as_array)
.is_some_and(|system| {
system.iter().any(|block| {
block
.get("text")
.and_then(Value::as_str)
.is_some_and(|text| {
text.starts_with(BILLING_HEADER_PREFIX)
&& text.contains(BILLING_ENTRYPOINT_MARKER)
})
})
})
}
fn has_claude_code_prefix(text: &str) -> bool {
let text = text.trim_start();
CLAUDE_CODE_PROMPT_PREFIXES
.iter()
.any(|prefix| text.starts_with(prefix))
}
fn first_user_text(body: &Map<String, Value>) -> String {
let Some(message) = body
.get("messages")
.and_then(Value::as_array)
.and_then(|messages| {
messages
.iter()
.find(|message| message.get("role").and_then(Value::as_str) == Some("user"))
})
else {
return String::new();
};
match message.get("content") {
Some(Value::String(text)) => text.clone(),
Some(Value::Array(blocks)) => blocks
.iter()
.find(|block| block.get("type").and_then(Value::as_str) == Some("text"))
.and_then(|block| block.get("text"))
.and_then(Value::as_str)
.unwrap_or_default()
.to_string(),
_ => String::new(),
}
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
}
/// Three hex chars derived from the first user text, matching the Claude Code CLI
/// billing attribution fingerprint. Indexing is by byte, out-of-range yields `'0'`.
fn claude_code_fingerprint(first_user_text: &str, cli_version: &str) -> String {
let bytes = first_user_text.as_bytes();
let picked = FINGERPRINT_CHAR_INDICES
.iter()
.map(|index| bytes.get(*index).copied().unwrap_or(b'0'))
.collect::<Vec<_>>();
let mut hasher = Sha256::new();
hasher.update(FINGERPRINT_SALT.as_bytes());
hasher.update(&picked);
hasher.update(cli_version.as_bytes());
hex(&hasher.finalize())[..3].to_string()
}
fn billing_header_text(first_user_text: &str, cli_version: &str) -> String {
format!(
"{BILLING_HEADER_PREFIX}: cc_version={cli_version}.{}; cc_entrypoint=cli;",
claude_code_fingerprint(first_user_text, cli_version)
)
}
/// Returns the joined original system text plus the cache_control of its last block that has one.
fn extract_system_text(system: Option<&Value>) -> (String, Option<Value>) {
match system {
Some(Value::String(text)) => (text.clone(), None),
Some(Value::Array(blocks)) => {
let mut parts = Vec::new();
let mut cache_control = None;
for block in blocks {
if let Some(text) = block.get("text").and_then(Value::as_str) {
if !text.trim().is_empty() {
parts.push(text.to_string());
}
}
if let Some(value) = block.get("cache_control").filter(|value| !value.is_null()) {
cache_control = Some(value.clone());
}
}
(parts.join("\n\n"), cache_control)
}
_ => (String::new(), None),
}
}
fn rewrite_system_and_migrate_instructions(
body: &mut Map<String, Value>,
cli_version: &str,
first_user_text: &str,
identity_only: bool,
) {
let (original_text, original_cache_control) = extract_system_text(body.get("system"));
let mut system = vec![
json!({"type": "text", "text": billing_header_text(first_user_text, cli_version)}),
json!({"type": "text", "text": CLAUDE_CODE_SYSTEM_PROMPT}),
];
if !identity_only {
system.push(json!({
"type": "text",
"text": CLAUDE_CODE_SYSTEM_PROMPT_EXPANSION,
"cache_control": {"type": "ephemeral", "ttl": "5m"},
}));
}
body.insert("system".to_string(), Value::Array(system));
let original_text = original_text.trim();
if original_text.is_empty()
|| original_text == CLAUDE_CODE_SYSTEM_PROMPT
|| has_claude_code_prefix(original_text)
{
return;
}
let mut instruction_block = json!({
"type": "text",
"text": format!("[System Instructions]\n{original_text}"),
});
if let Some(cache_control) = original_cache_control {
instruction_block["cache_control"] = cache_control;
}
let mut messages = vec![
json!({"role": "user", "content": [instruction_block]}),
json!({"role": "assistant", "content": [{"type": "text", "text": SYSTEM_INSTRUCTIONS_ACK}]}),
];
if let Some(Value::Array(original)) = body.remove("messages") {
messages.extend(original);
}
body.insert("messages".to_string(), Value::Array(messages));
}
fn stable_device_id(context: &ClaudeCodeBodyMimicryContext<'_>) -> String {
if let Some(device_id) = context.device_id.filter(|value| !value.is_empty()) {
return device_id.to_string();
}
let mut hasher = Sha256::new();
hasher.update(format!("{DEVICE_ID_SEED_NAMESPACE}::{}", context.key_id).as_bytes());
hex(&hasher.finalize())
}
/// Session id that stays stable while a conversation grows: seeded by key and first user text.
fn stable_session_id(key_id: &str, first_user_text: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(format!("{key_id}::{first_user_text}").as_bytes());
let digest = hasher.finalize();
let mut bytes = [0u8; 16];
bytes.copy_from_slice(&digest[..16]);
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
format!(
"{}-{}-{}-{}-{}",
hex(&bytes[0..4]),
hex(&bytes[4..6]),
hex(&bytes[6..8]),
hex(&bytes[8..10]),
hex(&bytes[10..16]),
)
}
fn inject_metadata_user_id(
body: &mut Map<String, Value>,
context: &ClaudeCodeBodyMimicryContext<'_>,
first_user_text: &str,
) {
let has_user_id = body
.get("metadata")
.and_then(|metadata| metadata.get("user_id"))
.and_then(Value::as_str)
.is_some_and(|value| !value.trim().is_empty());
if has_user_id {
return;
}
// Built by hand to keep the key order of the real CLI (serde_json sorts keys).
let json_string = |value: &str| Value::String(value.to_string()).to_string();
let user_id = format!(
"{{\"device_id\":{},\"account_uuid\":{},\"session_id\":{}}}",
json_string(&stable_device_id(context)),
json_string(context.account_uuid.unwrap_or_default()),
json_string(&stable_session_id(context.key_id, first_user_text)),
);
match body.get_mut("metadata").and_then(Value::as_object_mut) {
Some(metadata) => {
metadata.insert("user_id".to_string(), Value::String(user_id));
}
None => {
body.insert("metadata".to_string(), json!({"user_id": user_id}));
}
}
}
fn is_opus_5_5(model: &str) -> bool {
model.contains("opus-5-5") || model.contains("opus-5.5")
}
fn is_signed_thinking_5_5(model: &str) -> bool {
is_opus_5_5(model) || model.contains("sonnet-5-5") || model.contains("sonnet-5.5")
}
fn fill_fixed_fields(body: &mut Map<String, Value>, model: &str) {
body.entry("tools".to_string()).or_insert_with(|| json!([]));
if !body.contains_key("temperature") && !is_opus_5_5(model) {
body.insert("temperature".to_string(), json!(1));
}
body.entry("max_tokens".to_string())
.or_insert_with(|| json!(DEFAULT_MAX_TOKENS));
let tools_empty = body
.get("tools")
.and_then(Value::as_array)
.is_none_or(Vec::is_empty);
if tools_empty && !is_signed_thinking_5_5(model) {
body.remove("tool_choice");
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum CacheControlSlot {
Tool(usize),
Message(usize, usize),
System(usize),
}
fn cache_control_slots(body: &Map<String, Value>) -> Vec<CacheControlSlot> {
let has = |value: &Value| value.get("cache_control").is_some_and(|cc| !cc.is_null());
let mut slots = Vec::new();
let collect = |key: &str, make: &dyn Fn(usize) -> CacheControlSlot, slots: &mut Vec<_>| {
if let Some(items) = body.get(key).and_then(Value::as_array) {
for (index, item) in items.iter().enumerate() {
if has(item) {
slots.push(make(index));
}
}
}
};
collect("tools", &CacheControlSlot::Tool, &mut slots);
if let Some(messages) = body.get("messages").and_then(Value::as_array) {
for (message_index, message) in messages.iter().enumerate() {
if let Some(blocks) = message.get("content").and_then(Value::as_array) {
for (block_index, block) in blocks.iter().enumerate() {
if has(block) {
slots.push(CacheControlSlot::Message(message_index, block_index));
}
}
}
}
}
collect("system", &CacheControlSlot::System, &mut slots);
slots
}
fn remove_cache_control(body: &mut Map<String, Value>, slot: &CacheControlSlot) {
let target = match slot {
CacheControlSlot::Tool(index) => body
.get_mut("tools")
.and_then(|tools| tools.get_mut(*index)),
CacheControlSlot::Message(message, block) => body
.get_mut("messages")
.and_then(|messages| messages.get_mut(*message))
.and_then(|message| message.get_mut("content"))
.and_then(|content| content.get_mut(*block)),
CacheControlSlot::System(index) => body
.get_mut("system")
.and_then(|system| system.get_mut(*index)),
};
if let Some(object) = target.and_then(Value::as_object_mut) {
object.remove("cache_control");
}
}
/// Anthropic accepts at most 4 cache breakpoints. Drop the excess in the same order the
/// upstream-friendly proxy does: tools (last first), messages (first first), system (last first).
fn enforce_cache_control_limit(body: &mut Map<String, Value>) {
let slots = cache_control_slots(body);
if slots.len() <= MAX_CACHE_CONTROL_BLOCKS {
return;
}
let mut excess = slots.len() - MAX_CACHE_CONTROL_BLOCKS;
let tools = slots
.iter()
.filter(|slot| matches!(slot, CacheControlSlot::Tool(_)))
.rev();
let messages = slots
.iter()
.filter(|slot| matches!(slot, CacheControlSlot::Message(..)));
let system = slots
.iter()
.filter(|slot| matches!(slot, CacheControlSlot::System(_)))
.rev();
for slot in tools.chain(messages).chain(system) {
if excess == 0 {
break;
}
remove_cache_control(body, slot);
excess -= 1;
}
}
#[cfg(test)]
mod tests {
use serde_json::{json, Value};
use super::{
apply_claude_code_body_mimicry, claude_code_fingerprint, stable_session_id,
ClaudeCodeBodyMimicryContext, CLAUDE_CODE_SYSTEM_PROMPT,
};
const CONTEXT: ClaudeCodeBodyMimicryContext<'static> = ClaudeCodeBodyMimicryContext {
key_id: "key-1",
device_id: None,
account_uuid: Some("acct-1"),
};
fn pi_body() -> Value {
json!({
"model": "claude-sonnet-5-5",
"max_tokens": 4096,
"stream": true,
"system": [{
"type": "text",
"text": "You are an expert coding assistant operating inside pi.",
"cache_control": {"type": "ephemeral"}
}],
"messages": [{"role": "user", "content": "hello world, please help"}],
"tools": [{"name": "fabric_exec", "input_schema": {"type": "object"}}],
"thinking": {"type": "adaptive"}
})
}
#[test]
fn rewrites_system_into_claude_code_blocks_and_migrates_original_instructions() {
let mut body = pi_body();
assert!(apply_claude_code_body_mimicry(&mut body, CONTEXT));
let system = body["system"].as_array().unwrap();
assert_eq!(system.len(), 3);
let billing = system[0]["text"].as_str().unwrap();
assert!(billing.starts_with("x-anthropic-billing-header: cc_version=2.1.161."));
assert!(billing.ends_with("; cc_entrypoint=cli;"));
assert!(system[0].get("cache_control").is_none());
assert_eq!(system[1]["text"], CLAUDE_CODE_SYSTEM_PROMPT);
assert!(system[1].get("cache_control").is_none());
assert_eq!(system[2]["cache_control"]["type"], "ephemeral");
assert_eq!(system[2]["cache_control"]["ttl"], "5m");
let messages = body["messages"].as_array().unwrap();
assert_eq!(messages.len(), 3);
assert_eq!(
messages[0]["content"][0]["text"],
"[System Instructions]\nYou are an expert coding assistant operating inside pi."
);
assert_eq!(
messages[0]["content"][0]["cache_control"]["type"],
"ephemeral"
);
assert_eq!(messages[1]["role"], "assistant");
assert_eq!(messages[2]["content"], "hello world, please help");
}
#[test]
fn fingerprint_uses_original_first_user_text_not_migrated_instructions() {
let mut body = pi_body();
apply_claude_code_body_mimicry(&mut body, CONTEXT);
let expected = claude_code_fingerprint("hello world, please help", "2.1.161");
assert!(body["system"][0]["text"]
.as_str()
.unwrap()
.contains(&format!("cc_version=2.1.161.{expected};")));
}
#[test]
fn fingerprint_is_three_hex_chars_and_pads_short_text_with_zero() {
let fp = claude_code_fingerprint("", "2.1.161");
assert_eq!(fp.len(), 3);
assert!(fp.chars().all(|c| c.is_ascii_hexdigit()));
assert_eq!(fp, claude_code_fingerprint("abc", "2.1.161"));
assert_ne!(
claude_code_fingerprint("0123456789012345678901234", "2.1.161"),
claude_code_fingerprint("", "2.1.161")
);
}
#[test]
fn injects_metadata_user_id_in_cli_key_order_with_stable_ids() {
let mut first = pi_body();
let mut second = pi_body();
apply_claude_code_body_mimicry(&mut first, CONTEXT);
apply_claude_code_body_mimicry(&mut second, CONTEXT);
let user_id = first["metadata"]["user_id"].as_str().unwrap();
assert!(user_id.starts_with("{\"device_id\":\""));
let parsed: Value = serde_json::from_str(user_id).unwrap();
assert_eq!(parsed["device_id"].as_str().unwrap().len(), 64);
assert_eq!(parsed["account_uuid"], "acct-1");
assert_eq!(parsed["session_id"].as_str().unwrap().len(), 36);
assert_eq!(first["metadata"], second["metadata"]);
assert_eq!(
parsed["session_id"],
stable_session_id("key-1", "hello world, please help")
);
}
#[test]
fn keeps_client_metadata_user_id() {
let mut body = pi_body();
body["metadata"] = json!({"user_id": "client-provided"});
apply_claude_code_body_mimicry(&mut body, CONTEXT);
assert_eq!(body["metadata"]["user_id"], "client-provided");
}
#[test]
fn is_idempotent_and_leaves_genuine_claude_code_bodies_untouched() {
let mut body = pi_body();
assert!(apply_claude_code_body_mimicry(&mut body, CONTEXT));
let once = body.clone();
assert!(!apply_claude_code_body_mimicry(&mut body, CONTEXT));
assert_eq!(body, once);
let mut genuine = json!({
"model": "claude-sonnet-5-5",
"system": [
{"type": "text", "text": "x-anthropic-billing-header: cc_version=2.1.284.abc; cc_entrypoint=cli;"},
{"type": "text", "text": CLAUDE_CODE_SYSTEM_PROMPT}
],
"metadata": {"user_id": "{\"device_id\":\"d\",\"account_uuid\":\"\",\"session_id\":\"s\"}"},
"messages": [{"role": "user", "content": "hi"}]
});
let before = genuine.clone();
assert!(!apply_claude_code_body_mimicry(&mut genuine, CONTEXT));
assert_eq!(genuine, before);
}
#[test]
fn does_not_duplicate_system_that_already_carries_claude_code_identity() {
let mut body = pi_body();
body["system"] = json!(CLAUDE_CODE_SYSTEM_PROMPT);
apply_claude_code_body_mimicry(&mut body, CONTEXT);
assert_eq!(body["messages"].as_array().unwrap().len(), 1);
assert_eq!(body["system"].as_array().unwrap().len(), 3);
}
#[test]
fn fable_models_only_get_billing_and_identity_blocks() {
let mut body = pi_body();
body["model"] = json!("claude-fable-5-1");
apply_claude_code_body_mimicry(&mut body, CONTEXT);
assert_eq!(body["system"].as_array().unwrap().len(), 2);
}
#[test]
fn fills_fixed_fields_only_when_missing() {
let mut body = json!({
"model": "claude-sonnet-5-5",
"messages": [{"role": "user", "content": "hi"}],
"tool_choice": {"type": "auto"}
});
apply_claude_code_body_mimicry(&mut body, CONTEXT);
assert_eq!(body["tools"], json!([]));
assert_eq!(body["temperature"], 1);
assert_eq!(body["max_tokens"], 128000);
// Sonnet 5.5 keeps tool_choice even without tools.
assert!(body.get("tool_choice").is_some());
let mut body = json!({
"model": "claude-opus-5-5",
"temperature": 0.2,
"max_tokens": 10,
"messages": [{"role": "user", "content": "hi"}]
});
apply_claude_code_body_mimicry(&mut body, CONTEXT);
assert_eq!(body["temperature"], 0.2);
assert_eq!(body["max_tokens"], 10);
let mut body = json!({
"model": "claude-opus-5-5",
"messages": [{"role": "user", "content": "hi"}]
});
apply_claude_code_body_mimicry(&mut body, CONTEXT);
assert!(body.get("temperature").is_none());
let mut body = json!({
"model": "claude-opus-4-6",
"messages": [{"role": "user", "content": "hi"}],
"tool_choice": {"type": "auto"}
});
apply_claude_code_body_mimicry(&mut body, CONTEXT);
assert!(body.get("tool_choice").is_none());
}
#[test]
fn caps_cache_control_breakpoints_at_four() {
let mut body = pi_body();
body["tools"] = json!([
{"name": "a", "cache_control": {"type": "ephemeral"}},
{"name": "b", "cache_control": {"type": "ephemeral"}}
]);
body["messages"] = json!([
{"role": "user", "content": [{"type": "text", "text": "hello world, please help", "cache_control": {"type": "ephemeral"}}]},
{"role": "user", "content": [{"type": "text", "text": "again", "cache_control": {"type": "ephemeral"}}]}
]);
apply_claude_code_body_mimicry(&mut body, CONTEXT);
let count = |value: &Value| value.get("cache_control").is_some() as usize;
let total: usize = body["tools"]
.as_array()
.unwrap()
.iter()
.map(count)
.sum::<usize>()
+ body["system"]
.as_array()
.unwrap()
.iter()
.map(count)
.sum::<usize>()
+ body["messages"]
.as_array()
.unwrap()
.iter()
.flat_map(|message| message["content"].as_array().cloned().unwrap_or_default())
.map(|block| count(&block))
.sum::<usize>();
assert_eq!(total, 4);
// 6 breakpoints (2 tools, 3 messages incl. migrated instructions, 1 system): the two
// excess ones come from the tools, last first, so both tool breakpoints go.
assert!(body["tools"][0].get("cache_control").is_none());
assert!(body["tools"][1].get("cache_control").is_none());
assert!(body["system"][2].get("cache_control").is_some());
}
}