use std::borrow::Cow;
use std::path::PathBuf;
use caliban_provider::{ContentBlock, TextBlock};
pub trait AssistantPostProcessor: Send + Sync {
fn process<'a>(&self, text: &'a str) -> Cow<'a, str>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoopPostProcessor;
impl AssistantPostProcessor for NoopPostProcessor {
fn process<'a>(&self, text: &'a str) -> Cow<'a, str> {
Cow::Borrowed(text)
}
}
const HEAD_TAIL_CHARS: usize = 2048;
pub struct ToolResultCap {
pub max_chars: usize,
pub overflow_dir: PathBuf,
pub session_id: String,
}
impl ToolResultCap {
pub async fn cap(&self, blocks: &mut [ContentBlock]) -> std::io::Result<usize> {
if self.max_chars == 0 {
return Ok(0);
}
let session_dir = self.overflow_dir.join(&self.session_id);
let mut overflows = 0;
for block in blocks.iter_mut() {
let ContentBlock::ToolResult(tr) = block else {
continue;
};
if let Some(ContentBlock::Text(t)) = tr.content.first()
&& (t.text.starts_with("[truncated:") || t.text.starts_with("[superseded:"))
{
continue;
}
let full: String = tr
.content
.iter()
.filter_map(|b| match b {
ContentBlock::Text(t) => Some(t.text.as_str()),
_ => None,
})
.collect::<Vec<_>>()
.join("\n");
let full_chars = full.chars().count();
if full_chars <= self.max_chars {
continue;
}
tokio::fs::create_dir_all(&session_dir).await?;
let path = session_dir.join(format!("{}.txt", tr.tool_use_id));
tokio::fs::write(&path, &full).await?;
let head: String = full.chars().take(HEAD_TAIL_CHARS).collect();
let tail_start = full_chars.saturating_sub(HEAD_TAIL_CHARS);
let tail: String = full.chars().skip(tail_start).collect();
let placeholder = format!(
"[truncated: {} chars, full content at {}]\n\n--- head 2KB ---\n{}\n--- tail 2KB ---\n{}",
full_chars,
path.display(),
head,
tail,
);
tr.content = vec![ContentBlock::Text(TextBlock {
text: placeholder,
cache_control: None,
})];
overflows += 1;
}
Ok(overflows)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn noop_returns_input_unchanged() {
let p = NoopPostProcessor;
let out = p.process("hello world");
assert_eq!(out, "hello world");
assert!(matches!(out, Cow::Borrowed(_)));
}
}