use anyhow::Result;
use appam::llm::UnifiedContentBlock;
use appam::prelude::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Serialize)]
struct CatalogSection {
section: u32,
title: String,
body: String,
}
#[tool(description = "Fetch one section of the star catalog by number (1-5)")]
fn fetch_catalog(
#[arg(description = "Section number to fetch, between 1 and 5")] section: u32,
) -> Result<CatalogSection> {
let titles = [
"Main Sequence Stars of the Orion Arm",
"Variable Stars and Binary Systems",
"Deep Sky Objects and Nebulae",
"Star Clusters of the Southern Sky",
"Exoplanet Host Stars",
];
let title = titles
.get((section.saturating_sub(1)) as usize % titles.len())
.unwrap_or(&titles[0])
.to_string();
let body = (1..=400)
.map(|entry| {
format!(
"Record {entry:03} (sec {section}): catalogued object at field position \
{position}; magnitude nominal, spectra archived.",
position = entry * 13 % 360,
)
})
.collect::<Vec<_>>()
.join("\n");
Ok(CatalogSection {
section,
title,
body,
})
}
#[tokio::main]
async fn main() -> Result<()> {
println!("🗜 Auto-Compaction Agent - OpenAI Responses API\n");
let agent = AgentBuilder::new("compaction-demo-openai")
.provider(LlmProvider::OpenAI)
.model("gpt-5-mini")
.system_prompt(
"You are a star catalog librarian. Fetch catalog sections with the \
fetch_catalog tool exactly as instructed, one section per tool call. \
Do not summarize until every requested section has been fetched.",
)
.enable_auto_compaction(16_000)
.with_tool(Arc::new(fetch_catalog()))
.max_tokens(4096)
.build()?;
println!("✓ Auto-compaction enabled (trigger: 16K tokens)");
println!("✓ Tool: fetch_catalog (~4K tokens per section)\n");
let compaction_count = Arc::new(AtomicUsize::new(0));
let compaction_count_stream = Arc::clone(&compaction_count);
let session = agent
.stream(
"Fetch catalog sections 1, 2, 3, 4, and 5 one at a time using the \
fetch_catalog tool. After fetching ALL five sections, reply with a \
single short paragraph naming each section's title.",
)
.on_content(|content| {
print!("{}", content);
std::io::Write::flush(&mut std::io::stdout()).ok();
})
.on_tool_call(|tool_name, arguments| {
println!("\n🔧 {} {}", tool_name, arguments);
})
.on_tool_result(|tool_name, _result| {
println!(" ✓ {} completed", tool_name);
})
.on_compaction(move |provider, _summary| {
compaction_count_stream.fetch_add(1, Ordering::Relaxed);
println!("\n◈ Context compacted by {provider} (encrypted summary)");
})
.run()
.await?;
let compactions = compaction_count.load(Ordering::Relaxed);
let history_compaction_blocks = session
.messages
.iter()
.filter_map(|message| message.raw_content_blocks.as_ref())
.flatten()
.filter(|block| {
matches!(
block,
UnifiedContentBlock::Compaction {
encrypted_content: Some(_),
..
}
)
})
.count();
println!("\n\n===== Session Report =====");
println!("Compaction events observed: {compactions}");
println!("Compaction items in history: {history_compaction_blocks}");
if let Some(usage) = &session.usage {
println!("\n{}", usage.format_detailed());
}
if compactions == 0 {
println!(
"\n⚠ No compaction occurred — the conversation may not have crossed \
the 8K-token threshold. Increase the number of sections."
);
} else {
println!("\n✓ Server-side compaction verified end to end");
}
Ok(())
}