use super::*;
#[derive(Debug, Clone)]
pub(crate) enum AgentRender<'a> {
Kept(&'a AgentMsg),
Placeholder(PlaceholderSpan),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PlaceholderSpan {
pub(crate) messages: usize,
pub(crate) tool_calls: usize,
pub(crate) failed: usize,
pub(crate) first_line: usize,
pub(crate) last_line: usize,
}
pub(crate) fn select_agent_messages<'a>(
turn: &'a TurnSlice,
cfg: &RichnessCfg,
) -> Vec<AgentRender<'a>> {
let agents = &turn.agents;
if agents.is_empty() {
return Vec::new();
}
match cfg.mode {
AgentMsgMode::Longest => {
if agents.len() == 1 {
return vec![AgentRender::Kept(&agents[0])];
}
let longest = agents
.iter()
.enumerate()
.max_by_key(|(_, a)| a.unit.full_chars)
.map(|(i, _)| i)
.expect("non-empty");
let last = agents.len() - 1;
let keep = |i: usize, a: &AgentMsg| -> bool {
if i == longest {
return true; }
if i == 0 {
return a.unit.full_chars >= cfg.rich_min_chars; }
if i == last {
return agent_msg_is_rich(&a.unit.text, cfg); }
agent_msg_is_rich(&a.unit.text, cfg) };
collapse_unkept(agents, keep)
}
AgentMsgMode::EotOnly => {
vec![AgentRender::Kept(agents.last().expect("non-empty"))]
}
AgentMsgMode::All => agents.iter().map(AgentRender::Kept).collect(),
AgentMsgMode::Rich => {
if agents.len() <= cfg.run_threshold {
return agents.iter().map(AgentRender::Kept).collect();
}
let last = agents.len() - 1;
let keep = |i: usize, a: &AgentMsg| -> bool {
if i == last {
return true; }
if i == 0 && cfg.keep_first {
return true; }
!agent_msg_is_droppable(&a.unit.text, cfg)
};
collapse_unkept(agents, keep)
}
}
}
pub(crate) fn collapse_unkept<'a>(
agents: &'a [AgentMsg],
keep: impl Fn(usize, &AgentMsg) -> bool,
) -> Vec<AgentRender<'a>> {
let mut out: Vec<AgentRender> = Vec::new();
let mut span: Option<PlaceholderSpan> = None;
for (i, a) in agents.iter().enumerate() {
if keep(i, a) {
if let Some(s) = span.take() {
out.push(AgentRender::Placeholder(s));
}
out.push(AgentRender::Kept(a));
} else {
let line = a.unit.line_no;
match span.as_mut() {
Some(s) => {
s.messages += 1;
s.tool_calls += a.preceding_tool_calls;
s.failed += a.preceding_failed;
s.last_line = line;
}
None => {
span = Some(PlaceholderSpan {
messages: 1,
tool_calls: a.preceding_tool_calls,
failed: a.preceding_failed,
first_line: line,
last_line: line,
});
}
}
}
}
if let Some(s) = span.take() {
out.push(AgentRender::Placeholder(s));
}
out
}
pub(crate) fn plural(n: usize, noun: &str) -> String {
if n == 1 {
format!("1 {noun}")
} else {
format!("{n} {noun}s")
}
}
pub(crate) fn agent_placeholder_line(span: &PlaceholderSpan) -> String {
let range = if span.first_line == span.last_line {
format!("L{}", span.first_line)
} else {
format!("L{}–L{}", span.first_line, span.last_line)
};
let msgs = plural(span.messages, "agent message");
let tools = plural(span.tool_calls, "tool call");
let body = if span.failed == 0 {
format!("[{msgs}, {tools}]")
} else {
format!("[{msgs}, {tools}, {} failed]", span.failed)
};
format!("△ {range} {body}")
}
pub(crate) fn agent_placeholder_cost(span: &PlaceholderSpan) -> usize {
agent_placeholder_line(span).chars().count() + NEWLINE_COST
}
pub(crate) fn unit_glyph(role: Role) -> &'static str {
match role {
Role::User => "▽",
Role::Assistant => "△",
}
}
pub(crate) fn unit_header_line(unit: &TurnUnit) -> String {
let dup = if unit.also_in_summary {
" (also in summary)"
} else {
""
};
let locator = if unit.from_sidecar {
"(elicitation sidecar)".to_string()
} else {
format!("L{}", unit.line_no)
};
let role_field = match &unit.inbound {
Some(ic) => format!("{} {} ⇨ self", ic.class.path(), ic.from),
None => unit.role.label().to_uppercase(),
};
format!(
"{} {locator} {role_field} ({}){dup}",
unit_glyph(unit.role),
format_timestamp(unit.ts_utc.as_deref())
)
}
pub(crate) fn unit_cost(unit: &TurnUnit) -> usize {
let header_chars = unit_header_line(unit).chars().count() + NEWLINE_COST;
let body_chars = render_unit_body(unit, None).body.chars().count() + NEWLINE_COST;
header_chars + body_chars
}
pub(crate) fn marker_cost(tool_calls: usize) -> usize {
if tool_calls == 0 {
0
} else {
format!(" [{tool_calls} tool calls]").chars().count() + NEWLINE_COST
}
}
pub(crate) fn image_marker_line(ids: &[String]) -> String {
let noun = if ids.len() == 1 { "image" } else { "images" };
format!(" [{} {}: {}]", ids.len(), noun, ids.join(", "))
}
pub(crate) fn image_marker_cost(ids: &[String]) -> usize {
if ids.is_empty() {
0
} else {
image_marker_line(ids).chars().count() + NEWLINE_COST
}
}
pub(crate) fn boundary_banner_line(line_no: usize) -> String {
format!(
"{0} compaction boundary · summary at L{1} · (turns below predate it) {0}",
"══", line_no
)
}
pub(crate) fn banner_cost(line_no: usize) -> usize {
boundary_banner_line(line_no).chars().count() + NEWLINE_COST
}
pub(crate) fn cumulative_banner_cost(summaries: &[SummaryInfo], depth: usize) -> usize {
if depth == 0 {
return 0;
}
let mut by_rank: Vec<usize> = summaries.iter().map(|s| s.line_no).collect();
by_rank.sort_unstable_by(|a, b| b.cmp(a));
by_rank.into_iter().take(depth).map(banner_cost).sum()
}
pub(crate) fn doc_header_block_max_chars(sr: &ScanResult, budget: usize) -> usize {
let turns = sr.turns.len();
let summaries = sr.summaries.len();
let max_agent_units = sr.turns.iter().map(|t| t.agents.len()).sum::<usize>();
let max_line = sr
.summaries
.iter()
.map(|s| s.line_no)
.chain(sr.turns.iter().map(turn_latest_line))
.max()
.unwrap_or(0);
let line_session = format!("SESSION {}", sr.session_id);
let line_budget = format!(
" budget {} chars · round-trip-fraction {:.2} · spanned {} of {} compaction boundaries in scope",
budget, 0.0_f64, summaries, summaries
);
let has_automation = sr.turns.iter().any(|t| t.is_automation);
let line_selected = if has_automation {
format!(
" selected {} user ({} automation triggers) + {} assistant units across {} turns · {} / {} chars used",
turns, turns, max_agent_units, turns, budget, budget
)
} else {
format!(
" selected {} user + {} assistant units across {} turns · {} / {} chars used",
turns, max_agent_units, turns, budget, budget
)
};
let line_dedup = format!(
" dedup: {} units also present in summary L{} (demoted, flagged)",
2 * turns,
max_line
);
let line_rule = format!(" {}", "─".repeat(60));
[
line_session,
line_budget,
line_selected,
line_dedup,
line_rule,
]
.iter()
.map(|l| l.chars().count() + NEWLINE_COST)
.sum()
}
pub(crate) fn assistant_lane_cost(turn: &TurnSlice, cfg: &RichnessCfg) -> usize {
select_agent_messages(turn, cfg)
.iter()
.map(|r| match r {
AgentRender::Kept(a) => unit_cost(&a.unit),
AgentRender::Placeholder(s) => agent_placeholder_cost(s),
})
.sum()
}
pub(crate) fn turn_cost(turn: &TurnSlice, sides: SelSides, cfg: &RichnessCfg) -> usize {
let mut c = 0;
if matches!(sides, SelSides::Both | SelSides::UserOnly) {
if let Some(u) = &turn.user {
c += unit_cost(u);
c += image_marker_cost(&turn.image_ids);
}
}
if matches!(sides, SelSides::Both) {
c += marker_cost(turn.tool_calls);
}
if matches!(sides, SelSides::Both | SelSides::AssistantOnly) {
c += assistant_lane_cost(turn, cfg);
}
c
}