use serde_json::Value;
pub(crate) const MAX_DESCRIPTION_BYTES: usize = 1024;
const SUMMARY_BYTES: usize = 320;
pub(crate) fn shorten(doc: &mut Value) -> bool {
if super::compress::protected() {
return false;
}
let Some(tools) = doc.get_mut("tools").and_then(Value::as_array_mut) else {
return false;
};
let Some(expand) = tools.iter().find_map(|tool| {
tool_name(tool)
.filter(|name| super::compress::expand_tool_name(name))
.map(str::to_owned)
}) else {
return false;
};
let mut changed = false;
for tool in tools.iter_mut() {
if tool_name(tool).is_none_or(|name| name == expand) {
continue;
}
let Some(short) = shortened_description(tool, &expand) else {
continue;
};
if let Some(slot) = description_slot(tool) {
*slot = Value::String(short);
changed = true;
}
}
changed
}
fn tool_name(tool: &Value) -> Option<&str> {
tool.get("function")
.and_then(|function| function.get("name"))
.or_else(|| tool.get("name"))
.and_then(Value::as_str)
}
fn description_slot(tool: &mut Value) -> Option<&mut Value> {
if tool.get("function").is_some_and(Value::is_object) {
return tool.get_mut("function")?.get_mut("description");
}
tool.get_mut("description")
}
fn shortened_description(tool: &Value, expand: &str) -> Option<String> {
let description = tool
.get("function")
.and_then(|function| function.get("description"))
.or_else(|| tool.get("description"))
.and_then(Value::as_str)?;
if description.len() <= MAX_DESCRIPTION_BYTES {
return None;
}
let definition = serde_json::to_string(tool).ok()?;
let handle = super::ccr::persist_tool_definition(&definition)?;
let short = format!("{}\n{}", summary(description), pointer(expand, &handle));
(short.len() < description.len()).then_some(short)
}
pub(crate) fn pointer(expand: &str, handle: &str) -> String {
format!("[lean-ctx: description shortened — full definition: {expand}(id=\"{handle}\")]")
}
fn summary(description: &str) -> String {
let paragraph = description
.trim_start()
.split("\n\n")
.next()
.unwrap_or_default()
.trim_end();
if paragraph.len() <= SUMMARY_BYTES {
return paragraph.to_owned();
}
let mut end = SUMMARY_BYTES;
while !paragraph.is_char_boundary(end) {
end -= 1;
}
let window = ¶graph[..end];
let cut = window
.rfind(". ")
.map(|at| at + 1)
.filter(|&at| at >= SUMMARY_BYTES / 3)
.or_else(|| window.rfind(char::is_whitespace))
.unwrap_or(end);
format!("{} …", window[..cut].trim_end())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn long_description() -> String {
let sentence =
"Reads a file from the project and returns its content in the requested mode. ";
format!(
"{}\n\nDetails:\n{}",
sentence.repeat(8),
"- mode=full returns everything\n".repeat(60)
)
}
fn anthropic_doc(description: &str) -> Value {
json!({
"model": "claude",
"tools": [
{"name": "read_file", "description": description,
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}}}},
{"name": "small", "description": "Small tool", "input_schema": {}},
{"name": "mcp__lean-ctx__ctx_expand", "description": description,
"input_schema": {}, "cache_control": {"type": "ephemeral"}}
],
"messages": []
})
}
fn names(doc: &Value) -> Vec<String> {
doc["tools"]
.as_array()
.unwrap()
.iter()
.map(|tool| tool_name(tool).unwrap().to_owned())
.collect()
}
#[test]
fn a_large_description_is_shortened_with_a_pointer_to_the_full_definition() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let description = long_description();
let original = anthropic_doc(&description);
let mut doc = original.clone();
assert!(shorten(&mut doc));
let short = doc["tools"][0]["description"].as_str().unwrap();
assert!(short.len() < description.len() / 4, "{short}");
assert!(
short.starts_with("Reads a file from the project"),
"{short}"
);
let last = short.lines().last().unwrap();
let handle = last
.strip_prefix(
"[lean-ctx: description shortened — full definition: mcp__lean-ctx__ctx_expand(id=\"",
)
.and_then(|rest| rest.strip_suffix("\")]"))
.unwrap_or_else(|| panic!("pointer grammar: {last}"));
let path = crate::ccr::resolve_tee(handle).expect("handle resolves");
let stored: Value = serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap();
assert_eq!(stored, original["tools"][0], "the whole tool entry");
assert_eq!(names(&doc), names(&original));
assert_eq!(doc["tools"][1], original["tools"][1]);
assert_eq!(
doc["tools"][2], original["tools"][2],
"the expand tool stays whole"
);
assert_eq!(
doc["tools"][0]["input_schema"],
original["tools"][0]["input_schema"]
);
}
#[test]
fn shortening_is_byte_stable_across_turns() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let mut first = anthropic_doc(&long_description());
let mut second = first.clone();
shorten(&mut first);
shorten(&mut second);
assert_eq!(
serde_json::to_vec(&first).unwrap(),
serde_json::to_vec(&second).unwrap()
);
assert!(!shorten(&mut first), "a shortened list is left alone");
}
#[test]
fn without_a_listed_expand_tool_nothing_changes() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let mut doc = anthropic_doc(&long_description());
doc["tools"].as_array_mut().unwrap().pop();
let before = doc.clone();
assert!(!shorten(&mut doc));
assert_eq!(doc, before);
}
#[test]
fn chat_and_responses_shapes_are_shortened_in_place() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let description = long_description();
let mut chat = json!({"tools": [
{"type": "function", "function": {"name": "read_file", "description": description, "parameters": {}}},
{"type": "function", "function": {"name": "ctx_expand", "description": "Expand", "parameters": {}}}
]});
assert!(shorten(&mut chat));
let short = chat["tools"][0]["function"]["description"]
.as_str()
.unwrap();
assert!(
short.ends_with("\")]") && short.contains("ctx_expand(id=\""),
"{short}"
);
assert!(chat["tools"][0].get("description").is_none());
let mut responses = json!({"tools": [
{"type": "function", "name": "read_file", "description": description, "parameters": {}},
{"type": "function", "name": "ctx_expand", "description": "Expand", "parameters": {}}
]});
assert!(shorten(&mut responses));
assert!(
responses["tools"][0]["description"]
.as_str()
.unwrap()
.contains("full definition: ctx_expand(id=\"")
);
}
#[test]
fn small_descriptions_stay_whole() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let mut doc = anthropic_doc(&"x".repeat(MAX_DESCRIPTION_BYTES));
let before = doc.clone();
assert!(!shorten(&mut doc));
assert_eq!(doc, before);
}
}