use anyhow::{anyhow, Result};
use serde::Serialize;
use serde_json::{Map, Value};
use super::recall_projection::FIRST_FILTERABLE_LAYER;
pub(super) const DEFAULT_MAX_BYTES: usize = 48 * 1024;
pub(super) const HARD_MAX_BYTES: usize = 512 * 1024;
const RECALL_HINT: &str =
"a smaller `top_k`, a `min_score` floor, or `full: true` retrieves the rest";
struct CappedTool {
name: &'static str,
items_key: &'static str,
one: &'static str,
many: &'static str,
protect_layers: bool,
hint: &'static str,
}
const CAPPED_TOOLS: [CappedTool; 7] = [
CappedTool {
name: "memory_recall",
items_key: "results",
one: "hit",
many: "hits",
protect_layers: true,
hint: RECALL_HINT,
},
CappedTool {
name: "memory_recall_deep",
items_key: "results",
one: "hit",
many: "hits",
protect_layers: true,
hint: RECALL_HINT,
},
CappedTool {
name: "memory_recall_all",
items_key: "results",
one: "hit",
many: "hits",
protect_layers: true,
hint: "a smaller `top_k`, or `full: true` retrieves the rest",
},
CappedTool {
name: "memory_list",
items_key: "drawers",
one: "drawer",
many: "drawers",
protect_layers: false,
hint: "a smaller `limit`, a `room`/`wing`/`tag` filter, or `full: true` retrieves the rest",
},
CappedTool {
name: "kg_query",
items_key: "triples",
one: "triple",
many: "triples",
protect_layers: false,
hint: "a narrower `subject` (see `kg_list_subjects`), or `full: true` retrieves the rest",
},
CappedTool {
name: "chat_session_recall",
items_key: "history",
one: "turn",
many: "turns",
protect_layers: false,
hint: "`full: true` returns the whole history",
},
CappedTool {
name: "list_prompt_facts",
items_key: "facts",
one: "fact",
many: "facts",
protect_layers: false,
hint: "`full: true` returns every fact",
},
];
fn capped_tool(tool: &str) -> Option<&'static CappedTool> {
CAPPED_TOOLS.iter().find(|t| t.name == tool)
}
pub(super) fn is_capped(tool: &str) -> bool {
capped_tool(tool).is_some()
}
#[cfg(test)]
pub(super) fn capped_tool_names() -> Vec<&'static str> {
CAPPED_TOOLS.iter().map(|t| t.name).collect()
}
pub(super) fn measure<T: Serialize + ?Sized>(value: &T) -> Result<usize> {
serde_json::to_string(value).map(|s| s.len()).map_err(|e| {
anyhow!(
"response size measurement failed ({e}); the response is withheld \
rather than returned unmeasured (#7493)"
)
})
}
#[derive(Clone, Copy)]
struct Bounds {
ceiling: usize,
clamped: bool,
full: bool,
}
impl Bounds {
fn parse(args: &Value, tool: &str) -> Result<Self> {
let requested = match args.get("max_bytes") {
None | Some(Value::Null) => None,
Some(v) => match v.as_u64() {
Some(n) if n <= u32::MAX as u64 => Some(n as usize),
_ => {
return Err(anyhow!(
"{tool}: 'max_bytes' must be an integer number of bytes in 0..={}, \
got {v} — a quoted or negative value is not coerced",
u32::MAX
))
}
},
};
let full = match args.get("full") {
None | Some(Value::Null) => false,
Some(Value::Bool(b)) => *b,
Some(other) => {
return Err(anyhow!(
"{tool}: 'full' must be a boolean (true returns every entry with no \
byte ceiling), got {other} — a quoted value is not coerced"
))
}
};
Ok(Self {
ceiling: requested.unwrap_or(DEFAULT_MAX_BYTES).min(HARD_MAX_BYTES),
clamped: !full && requested.is_some_and(|n| n > HARD_MAX_BYTES),
full,
})
}
}
#[derive(Clone, Copy, PartialEq)]
enum Fit {
Whole,
Folded,
Oversized,
}
struct Fold<'a> {
spec: &'a CappedTool,
base: Map<String, Value>,
items: Vec<Value>,
protected: usize,
bounds: Bounds,
}
fn is_protected(spec: &CappedTool, item: &Value) -> bool {
spec.protect_layers
&& item
.get("layer")
.and_then(Value::as_u64)
.is_some_and(|layer| layer < u64::from(FIRST_FILTERABLE_LAYER))
}
impl Fold<'_> {
fn droppable(&self) -> usize {
self.items.len() - self.protected
}
fn candidate(&self, keep: usize, fit: Fit) -> Value {
let mut map = self.base.clone();
let mut rank = 0usize;
let mut kept: Vec<Value> = Vec::with_capacity(self.items.len());
for item in &self.items {
if is_protected(self.spec, item) {
kept.push(item.clone());
continue;
}
if rank < keep {
kept.push(item.clone());
}
rank += 1;
}
let returned = kept.len();
let withheld = self.items.len() - returned;
map.insert(self.spec.items_key.to_string(), Value::Array(kept));
let truncated = fit != Fit::Whole;
map.insert("truncated".into(), Value::Bool(truncated));
if truncated {
map.insert("returned".into(), Value::from(returned));
map.insert("withheld".into(), Value::from(withheld));
map.insert(
"truncation_notice".into(),
Value::String(self.notice(returned, withheld, fit)),
);
}
if self.bounds.clamped {
map.insert("max_bytes_clamped".into(), Value::Bool(true));
map.insert("max_bytes".into(), Value::from(self.bounds.ceiling));
}
Value::Object(map)
}
fn noun(&self, n: usize) -> &'static str {
if n == 1 {
self.spec.one
} else {
self.spec.many
}
}
fn notice(&self, returned: usize, withheld: usize, fit: Fit) -> String {
let (ceiling, hint, total) = (self.bounds.ceiling, self.spec.hint, self.items.len());
let many = self.spec.many;
match (fit, withheld) {
(Fit::Oversized, 0) => format!(
"This response exceeds the {ceiling}-byte ceiling on its own: its {returned} \
{returned_noun} cannot be folded smaller without answering an empty result, \
so they are returned whole; {hint}.",
returned_noun = self.noun(returned),
),
(Fit::Oversized, _) => format!(
"This response exceeds the {ceiling}-byte ceiling on its own: the smallest it \
folds to is {returned} of {total} {many}, and the other {withheld} were \
dropped; {hint}."
),
_ => format!(
"{withheld} of {total} {many} were withheld to keep this response under \
{ceiling} bytes; {hint}."
),
}
}
}
pub(super) fn apply(tool: &str, args: &Value, resp: &mut Value) -> Result<()> {
let Some(spec) = capped_tool(tool) else {
return Ok(());
};
let bounds = Bounds::parse(args, tool)?;
let Some(base) = resp.as_object() else {
return Ok(());
};
let Some(items) = base.get(spec.items_key).and_then(Value::as_array) else {
return Ok(());
};
let items = items.clone();
let protected = items.iter().filter(|i| is_protected(spec, i)).count();
let fold = Fold {
spec,
base: base.clone(),
items,
protected,
bounds,
};
let droppable = fold.droppable();
if bounds.full {
*resp = fold.candidate(droppable, Fit::Whole);
return Ok(());
}
let whole = fold.candidate(droppable, Fit::Whole);
if measure(&whole)? <= bounds.ceiling || fold.items.is_empty() {
*resp = whole;
return Ok(());
}
let min_keep = usize::from(fold.protected == 0);
let (mut lo, mut hi, mut best) = (min_keep, droppable.saturating_sub(1), None::<usize>);
while lo <= hi && droppable > 0 {
let mid = lo + (hi - lo) / 2;
if measure(&fold.candidate(mid, Fit::Folded))? <= bounds.ceiling {
best = Some(mid);
lo = mid + 1;
} else if mid == 0 {
break;
} else {
hi = mid - 1;
}
}
*resp = match best {
Some(keep) => fold.candidate(keep, Fit::Folded),
None => fold.candidate(min_keep, Fit::Oversized),
};
Ok(())
}
pub(super) fn annotate_capped_tools(tools: &mut Value) {
let Some(tools) = tools.as_array_mut() else {
return;
};
for tool in tools.iter_mut() {
let Some(spec) = tool
.get("name")
.and_then(Value::as_str)
.and_then(capped_tool)
else {
continue;
};
let Some(props) = tool
.get_mut("inputSchema")
.and_then(Value::as_object_mut)
.and_then(|schema| schema.get_mut("properties"))
.and_then(Value::as_object_mut)
else {
continue;
};
let many = spec.many;
props.insert(
"max_bytes".into(),
serde_json::json!({
"type": "integer",
"default": DEFAULT_MAX_BYTES,
"description": format!(
"Ceiling on this response's serialized size, in bytes (default \
{DEFAULT_MAX_BYTES}). A larger value is clamped to {HARD_MAX_BYTES} and \
the clamp is reported in the response. Over the ceiling, whole {many} are \
dropped from the tail — never a partial one — and `truncated`, \
`withheld`, and `truncation_notice` say what is missing (#7493)."
),
}),
);
props.insert(
"full".into(),
serde_json::json!({
"type": "boolean",
"default": false,
"description": format!(
"Return every {many} with no byte ceiling. Reach for it after a capped \
call has told you what it withheld, not before (#7493)."
),
}),
);
}
}