use serde::{Deserialize, Serialize};
pub const ANTHROPIC_COMPACTION_BETA: &str = "compact-2026-01-12";
pub const ANTHROPIC_COMPACTION_EDIT_TYPE: &str = "compact_20260112";
pub const ANTHROPIC_MIN_TRIGGER_TOKENS: u64 = 50_000;
pub const OPENAI_MIN_TRIGGER_TOKENS: u64 = 1_000;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CompactionConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trigger_tokens: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
}
impl CompactionConfig {
pub const DEFAULT_TRIGGER_TOKENS: u64 = 150_000;
pub fn enabled() -> Self {
Self {
enabled: true,
trigger_tokens: None,
instructions: None,
}
}
pub fn with_trigger_tokens(trigger_tokens: u64) -> Self {
Self {
enabled: true,
trigger_tokens: Some(trigger_tokens),
instructions: None,
}
}
pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = Some(instructions.into());
self
}
pub fn is_active(&self) -> bool {
self.enabled
}
pub fn effective_trigger_tokens(&self, provider_min: u64) -> u64 {
let requested = self.trigger_tokens.unwrap_or(Self::DEFAULT_TRIGGER_TOKENS);
if requested < provider_min {
tracing::warn!(
requested_trigger_tokens = requested,
provider_min_tokens = provider_min,
"Compaction trigger below provider minimum; clamping upward"
);
provider_min
} else {
requested
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_disabled_by_default() {
let config = CompactionConfig::default();
assert!(!config.is_active());
assert!(config.trigger_tokens.is_none());
}
#[test]
fn test_enabled_with_threshold() {
let config = CompactionConfig::with_trigger_tokens(120_000);
assert!(config.is_active());
assert_eq!(config.trigger_tokens, Some(120_000));
}
#[test]
fn test_effective_trigger_clamps_to_provider_minimum() {
let config = CompactionConfig::with_trigger_tokens(10_000);
assert_eq!(
config.effective_trigger_tokens(ANTHROPIC_MIN_TRIGGER_TOKENS),
ANTHROPIC_MIN_TRIGGER_TOKENS
);
assert_eq!(
config.effective_trigger_tokens(OPENAI_MIN_TRIGGER_TOKENS),
10_000
);
}
#[test]
fn test_effective_trigger_defaults_when_unset() {
let config = CompactionConfig::enabled();
assert_eq!(
config.effective_trigger_tokens(OPENAI_MIN_TRIGGER_TOKENS),
CompactionConfig::DEFAULT_TRIGGER_TOKENS
);
}
#[test]
fn test_serde_roundtrip() {
let config = CompactionConfig::with_trigger_tokens(75_000)
.instructions("Preserve code snippets and decisions.");
let json = serde_json::to_string(&config).unwrap();
let parsed: CompactionConfig = serde_json::from_str(&json).unwrap();
assert!(parsed.enabled);
assert_eq!(parsed.trigger_tokens, Some(75_000));
assert_eq!(
parsed.instructions.as_deref(),
Some("Preserve code snippets and decisions.")
);
}
}