use std::path::Path;
use crate::models::{ContentBlock, Message, SystemPrompt};
use zagens_core::compaction::CompactionConfig;
use zagens_core::engine::token_estimate::TokenEstimator;
use super::plan::plan_compaction;
use super::{KEEP_RECENT_MESSAGES, MIN_SUMMARIZE_MESSAGES};
pub(crate) fn message_has_tool_use(message: &Message) -> bool {
message
.content
.iter()
.any(|block| matches!(block, ContentBlock::ToolUse { .. }))
}
pub(crate) fn estimate_tokens_for_message(message: &Message, include_thinking: bool) -> usize {
let est = TokenEstimator;
message
.content
.iter()
.map(|c| match c {
ContentBlock::Text { text, .. } => est.estimate_text(text),
ContentBlock::Thinking { thinking } if include_thinking => est.estimate_text(thinking),
ContentBlock::Thinking { .. } => 0,
ContentBlock::ToolUse { input, .. } => est.estimate_text(&input.to_string()),
ContentBlock::ToolResult { content, .. } => est.estimate_text(content),
ContentBlock::ServerToolUse { .. }
| ContentBlock::ToolSearchToolResult { .. }
| ContentBlock::CodeExecutionToolResult { .. } => 0,
})
.sum::<usize>()
}
pub fn estimate_tokens(messages: &[Message]) -> usize {
messages
.iter()
.map(|m| estimate_tokens_for_message(m, message_has_tool_use(m)))
.sum()
}
pub(crate) fn estimate_system_tokens_conservative(system: Option<&SystemPrompt>) -> usize {
TokenEstimator.estimate_system(system)
}
#[must_use]
pub fn estimate_input_tokens_conservative(
messages: &[Message],
system: Option<&SystemPrompt>,
) -> usize {
TokenEstimator.estimate_request_input_with_selective_thinking(messages, system)
}
pub fn should_compact(
messages: &[Message],
config: &CompactionConfig,
workspace: Option<&Path>,
external_pins: Option<&[usize]>,
external_working_set_paths: Option<&[String]>,
) -> bool {
if !config.enabled {
return false;
}
if config.auto_floor_tokens > 0 {
let total_session_tokens: usize = messages
.iter()
.map(|m| estimate_tokens_for_message(m, false))
.sum();
if total_session_tokens < config.auto_floor_tokens {
return false;
}
}
let plan = plan_compaction(
messages,
workspace,
KEEP_RECENT_MESSAGES,
external_pins,
external_working_set_paths,
);
let pinned_tokens: usize = plan
.pinned_indices
.iter()
.map(|&idx| estimate_tokens_for_message(&messages[idx], false))
.sum();
let token_estimate: usize = plan
.summarize_indices
.iter()
.map(|&idx| estimate_tokens_for_message(&messages[idx], false))
.sum();
let message_count = plan.summarize_indices.len();
let effective_token_threshold = config.token_threshold.saturating_sub(pinned_tokens);
if effective_token_threshold == 0 {
return message_count >= MIN_SUMMARIZE_MESSAGES;
}
if message_count < MIN_SUMMARIZE_MESSAGES {
return false;
}
token_estimate > effective_token_threshold
}