use crate::display;
use crate::message::{BashOutput, ConversationMessage, MessageView, Part, ToolCall};
use crate::model::{Embedder, TextGen};
use crate::text;
use std::collections::{BTreeSet, HashSet};
const BRIEF_PARAPHRASE_THRESHOLD: f32 = 0.94;
const BRIEF_MAX_LINES: usize = 120;
const BRIEF_HEAD_LINES: usize = 24;
const TEXT_LINE_MAX_CHARS: usize = 240;
const ASSISTANT_LINE_MAX_CHARS: usize = 200;
const BASH_LINE_MAX_CHARS: usize = 120;
const TOOL_CALLS_PER_TURN: usize = 8;
const GOAL_LIMIT: usize = 8;
const PREFERENCE_LIMIT: usize = 15;
const SECTION_SESSION_GOAL: &str = "[Session Goal]";
const SECTION_FILES_AND_CHANGES: &str = "[Files And Changes]";
const SECTION_COMMITS: &str = "[Commits]";
const SECTION_OUTSTANDING_CONTEXT: &str = "[Outstanding Context]";
const SECTION_USER_PREFERENCES: &str = "[User Preferences]";
#[derive(Debug, Default)]
struct ExtractedContext {
goals: Vec<String>,
file_activity: FileActivity,
commits: Vec<String>,
outstanding: Vec<String>,
preferences: Vec<String>,
brief: String,
}
#[derive(Debug, Default)]
struct FileActivity {
read: BTreeSet<String>,
modified: BTreeSet<String>,
created: BTreeSet<String>,
}
#[derive(Debug)]
struct BriefSection {
header: &'static str,
lines: Vec<String>,
}
pub fn summary(messages: &[ConversationMessage]) -> serde_json::Value {
let mut textgen = TextGen::load();
let ctx = extract_context(messages, Embedder::load().as_ref(), textgen.as_mut());
serde_json::json!({
"goals": ctx.goals,
"files": {
"modified": ctx.file_activity.modified,
"created": ctx.file_activity.created,
"read": ctx.file_activity.read,
},
"commits": ctx.commits,
"outstanding": ctx.outstanding,
"preferences": ctx.preferences,
"brief": ctx.brief,
})
}
fn extract_recent(items: &mut Vec<String>, limit: usize, keep_first: bool) {
if items.len() <= limit {
return;
}
if keep_first {
let tail_start = items.len() - (limit - 1);
let mut capped = Vec::with_capacity(limit);
capped.push(items[0].clone());
capped.extend_from_slice(&items[tail_start..]);
*items = capped;
} else {
let excess = items.len() - limit;
items.drain(0..excess);
}
}
fn extract_context(
messages: &[ConversationMessage],
embedder: Option<&Embedder>,
textgen: Option<&mut TextGen>,
) -> ExtractedContext {
let mut ctx = ExtractedContext::default();
collect_prior_summaries(messages, &mut ctx);
for message in messages {
collect_goals(message, &mut ctx.goals);
collect_preferences(message, &mut ctx.preferences);
collect_commits(message, &mut ctx.commits);
}
collect_file_activity(messages, &mut ctx.file_activity);
extract_recent(&mut ctx.goals, GOAL_LIMIT, true);
extract_recent(&mut ctx.preferences, PREFERENCE_LIMIT, false);
if let Some(textgen) = textgen {
refine_directives(&mut ctx.goals, "session goals", textgen);
refine_directives(&mut ctx.preferences, "user preferences", textgen);
}
trim_file_activity(&mut ctx.file_activity);
ctx.outstanding = todo_snapshot(messages);
ctx.outstanding.extend(conversation_outstanding(messages));
ctx.brief = conversation_brief(messages, embedder);
if ctx.goals.is_empty() {
ctx.goals.push("Ongoing development work".to_string());
}
ctx
}
fn collect_prior_summaries(messages: &[ConversationMessage], ctx: &mut ExtractedContext) {
for message in messages {
if let Some(text) = compact_summary_text(message) {
merge_prior_summary(&text, ctx);
}
}
}
fn compact_summary_text(message: &ConversationMessage) -> Option<String> {
let text = match message.view() {
MessageView::Text { text, .. } | MessageView::Assistant { text, .. } => text,
MessageView::ToolResult(_) | MessageView::Bash(_) => return None,
};
is_compact_summary(&text).then_some(text)
}
fn is_compact_summary(text: &str) -> bool {
text.lines()
.any(|line| matches_compact_section(line.trim()))
}
fn matches_compact_section(line: &str) -> bool {
matches!(
line,
SECTION_SESSION_GOAL
| SECTION_FILES_AND_CHANGES
| SECTION_COMMITS
| SECTION_OUTSTANDING_CONTEXT
| SECTION_USER_PREFERENCES
)
}
fn merge_prior_summary(text: &str, ctx: &mut ExtractedContext) {
let mut section = None;
for line in text.lines().map(str::trim) {
if matches_compact_section(line) {
section = Some(line);
continue;
}
let Some(item) = line.strip_prefix("- ") else {
continue;
};
match section {
Some(SECTION_SESSION_GOAL) => push_unique(&mut ctx.goals, item.to_string()),
Some(SECTION_FILES_AND_CHANGES) => {
merge_prior_file_activity(item, &mut ctx.file_activity);
}
Some(SECTION_COMMITS) => push_unique(&mut ctx.commits, item.to_string()),
Some(SECTION_USER_PREFERENCES) => push_unique(&mut ctx.preferences, item.to_string()),
_ => {}
}
}
}
fn merge_prior_file_activity(item: &str, activity: &mut FileActivity) {
let Some((kind, paths)) = item.split_once(": ") else {
return;
};
for path in paths
.split(',')
.map(str::trim)
.filter(|path| !path.is_empty())
{
let path = path.to_string();
match kind {
"Read" => {
activity.read.insert(path);
}
"Modified" => {
activity.modified.insert(path);
}
"Created" => {
activity.created.insert(path);
}
_ => {}
}
}
}
fn collect_goals(message: &ConversationMessage, goals: &mut Vec<String>) {
let MessageView::Text { role, text } = message.view() else {
return;
};
if role != "user" {
return;
}
for line in non_empty_lines(&text) {
let lower = line.to_ascii_lowercase();
let is_first_goal = goals.is_empty();
let is_scope_change = contains_any(
&lower,
&[
"also ", "instead", "change ", "switch ", "update ", "fix ", "add ", "remove ",
"don't ", "do not ",
],
);
if is_first_goal || is_scope_change {
push_unique(
goals,
referenced_sentence(&line, TEXT_LINE_MAX_CHARS, &message.entry_id),
);
}
}
}
fn collect_commits(message: &ConversationMessage, commits: &mut Vec<String>) {
if compact_summary_text(message).is_some() {
return;
}
let text_value = match message.view() {
MessageView::ToolResult(result) => result.content.clone(),
MessageView::Bash(output) => output.output.clone(),
MessageView::Text { .. } | MessageView::Assistant { .. } => return,
};
for line in non_empty_lines(&text_value) {
if let Some(commit) = (|| {
let (inside, after) = line.trim().strip_prefix('[')?.split_once(']')?;
let hash = inside.split_whitespace().next_back()?;
if !(7..=40).contains(&hash.len()) || !hash.chars().all(|ch| ch.is_ascii_hexdigit()) {
return None;
}
let subject = after.trim();
let subject = if subject.is_empty() {
line.as_str()
} else {
subject
};
Some(format!(
"{}: {}",
&hash[..hash.len().min(12)],
clean_sentence(subject, 120)
))
})() {
push_unique(
commits,
format!("{} ({})", commit, entry_ref(&message.entry_id)),
);
}
if commits.len() >= 8 {
break;
}
}
}
fn collect_preferences(message: &ConversationMessage, preferences: &mut Vec<String>) {
let MessageView::Text { role, text } = message.view() else {
return;
};
if role != "user" {
return;
}
for line in non_empty_lines(&text) {
let lower = line.to_ascii_lowercase();
if contains_any(
&lower,
&[
"prefer ", "always ", "never ", "don't ", "do not ", "must ", "should ",
],
) {
push_unique(
preferences,
referenced_sentence(&line, 180, &message.entry_id),
);
}
}
}
fn collect_file_activity(messages: &[ConversationMessage], activity: &mut FileActivity) {
let errored: HashSet<String> = messages
.iter()
.flat_map(|message| message.parts.iter())
.filter_map(|part| match part {
Part::ToolResult(result) if result.is_error && !result.call_id.is_empty() => {
Some(result.call_id.clone())
}
_ => None,
})
.collect();
let mut seen: HashSet<String> = HashSet::new();
for message in messages {
if !message.is_assistant() {
continue;
}
for tool_call in message.tool_calls() {
if !tool_call.id.is_empty() && errored.contains(tool_call.id.as_str()) {
continue;
}
let Some(path) = display::path_argument(&tool_call.arguments) else {
continue;
};
if is_read_tool(&tool_call.name) {
activity.read.insert(path.clone());
seen.insert(path);
} else if is_create_tool(&tool_call.name) {
if !activity.created.contains(&path) {
if seen.contains(&path) {
activity.modified.insert(path.clone());
} else {
activity.created.insert(path.clone());
}
}
seen.insert(path);
} else if is_write_tool(&tool_call.name) {
if !activity.created.contains(&path) {
activity.modified.insert(path.clone());
}
seen.insert(path);
}
}
}
}
fn is_read_tool(name: &str) -> bool {
matches!(name, "read" | "Read" | "read_file" | "View")
}
fn is_write_tool(name: &str) -> bool {
matches!(
name,
"edit" | "Edit" | "write" | "Write" | "edit_file" | "write_file" | "MultiEdit"
)
}
fn is_create_tool(name: &str) -> bool {
matches!(name, "write" | "Write" | "write_file")
}
fn trim_file_activity(activity: &mut FileActivity) {
let all: Vec<String> = activity
.read
.iter()
.chain(activity.modified.iter())
.chain(activity.created.iter())
.cloned()
.collect();
let prefix = longest_common_dir_prefix(&all);
if prefix.is_empty() {
return;
}
activity.read = trim_paths(&activity.read, &prefix);
activity.modified = trim_paths(&activity.modified, &prefix);
activity.created = trim_paths(&activity.created, &prefix);
}
fn longest_common_dir_prefix(paths: &[String]) -> String {
let absolute: Vec<&str> = paths
.iter()
.filter_map(|path| path.starts_with('/').then_some(path.as_str()))
.collect();
if absolute.len() < 2 {
return String::new();
}
let split: Vec<Vec<&str>> = absolute
.iter()
.map(|path| path.split('/').collect())
.collect();
let min_len = split.iter().map(Vec::len).min().unwrap_or(0);
let mut idx = 0;
while idx + 1 < min_len {
let segment = split[0][idx];
if !split.iter().all(|parts| parts[idx] == segment) {
break;
}
idx += 1;
}
if idx < 2 {
String::new()
} else {
format!("{}/", split[0][..idx].join("/"))
}
}
fn trim_paths(paths: &BTreeSet<String>, prefix: &str) -> BTreeSet<String> {
paths
.iter()
.map(|path| path.strip_prefix(prefix).unwrap_or(path).to_string())
.collect()
}
fn refine_directives(items: &mut Vec<String>, kind: &str, textgen: &mut TextGen) {
if items.len() < 2 {
return;
}
let mut numbered = String::new();
for (idx, item) in items.iter().enumerate() {
use std::fmt::Write as _;
let _ = writeln!(numbered, "{}. {}", idx + 1, strip_entry_ref(item));
}
let system = format!(
"You judge numbered candidate lines extracted from a coding-agent session. \
Reply with only the numbers of the lines that are genuine, still-current {kind}, \
comma-separated, in ascending order. A line superseded or reversed by a later \
line is not current."
);
let Ok(answer) = textgen.complete(&system, &numbered, 64) else {
return;
};
let mut keep: Vec<usize> = answer
.split(|ch: char| !ch.is_ascii_digit())
.filter(|part| !part.is_empty())
.filter_map(|part| part.parse().ok())
.filter(|idx| (1..=items.len()).contains(idx))
.collect();
keep.sort_unstable();
keep.dedup();
if keep.is_empty() || keep.len() == items.len() {
return;
}
*items = keep
.iter()
.filter_map(|&idx| items.get(idx - 1).cloned())
.collect();
}
fn todo_snapshot(messages: &[ConversationMessage]) -> Vec<String> {
let mut snapshot: Vec<(String, String)> = Vec::new();
let mut events: std::collections::BTreeMap<String, (String, String)> =
std::collections::BTreeMap::new();
let mut entry_id = String::new();
for message in messages {
if message.is_assistant() {
for tool_call in message.tool_calls() {
if !is_internal_tool_name(&tool_call.name) {
continue;
}
if let Some(items) = todo_items_from_arguments(&tool_call.arguments) {
snapshot = items;
events.clear();
entry_id.clone_from(&message.entry_id);
}
}
}
if let MessageView::ToolResult(result) = message.view()
&& is_internal_tool_name(&result.tool_name)
{
let mut changed = false;
for line in non_empty_lines(&result.content) {
changed |= apply_todo_event(&line, &mut events);
}
if changed {
entry_id.clone_from(&message.entry_id);
}
}
}
if !events.is_empty() {
snapshot = events.into_values().collect();
}
snapshot
.into_iter()
.filter(|(_, status)| {
!matches!(
status.as_str(),
"completed" | "done" | "cancelled" | "canceled"
)
})
.map(|(title, status)| format!("[todo] {title} ({status}) ({})", entry_ref(&entry_id)))
.collect()
}
fn todo_items_from_arguments(arguments: &serde_json::Value) -> Option<Vec<(String, String)>> {
let obj = arguments.as_object()?;
let list = ["todos", "plan"]
.iter()
.find_map(|key| obj.get(*key).and_then(serde_json::Value::as_array))?;
let mut items = Vec::new();
for entry in list {
let entry = entry.as_object()?;
let title = ["content", "step", "text", "title"]
.iter()
.find_map(|key| entry.get(*key).and_then(serde_json::Value::as_str))?;
let status = entry
.get("status")
.and_then(serde_json::Value::as_str)
.unwrap_or("pending");
items.push((title.to_string(), status.to_string()));
}
(!items.is_empty()).then_some(items)
}
fn apply_todo_event(
line: &str,
items: &mut std::collections::BTreeMap<String, (String, String)>,
) -> bool {
if let Some(rest) = line.strip_prefix("Created #")
&& let Some((id, rest)) = rest.split_once(':')
{
let rest = rest.trim();
let (title, status) = match rest.rsplit_once(" (") {
Some((title, status)) if status.ends_with(')') => (title, status.trim_end_matches(')')),
_ => (rest, "pending"),
};
items.insert(id.to_string(), (title.to_string(), status.to_string()));
return true;
}
if let Some(rest) = line.strip_prefix("Updated #")
&& let Some((id, transition)) = rest.split_once(" (")
&& let Some((_, to)) = transition.trim_end_matches(')').rsplit_once("-> ")
&& let Some(item) = items.get_mut(id)
{
item.1 = to.trim().to_string();
return true;
}
false
}
fn conversation_resolved(messages: &[ConversationMessage]) -> bool {
for message in messages.iter().rev() {
let text_value = match message.view() {
MessageView::ToolResult(result) => result.content.clone(),
MessageView::Bash(output) => output.output.clone(),
_ => continue,
};
for line in non_empty_lines(&text_value) {
let lower = line.to_ascii_lowercase();
if is_success_line(&lower) {
return true;
}
if is_tool_failure_line(&lower) {
return false;
}
}
}
false
}
fn conversation_outstanding(messages: &[ConversationMessage]) -> Vec<String> {
let conversation_resolved = conversation_resolved(messages);
let mut items = Vec::new();
for message in messages.iter().rev() {
if compact_summary_text(message).is_some() {
continue;
}
match message.view() {
MessageView::Text { role, text } => {
if role != "user" && role != "assistant" {
continue;
}
collect_outstanding_lines(&text, role, &message.entry_id, &mut items);
}
MessageView::Assistant { text, .. } => {
collect_outstanding_lines(&text, "assistant", &message.entry_id, &mut items);
}
MessageView::ToolResult(result) => {
if result.is_error && !conversation_resolved {
collect_tool_outstanding_lines(&result.content, &message.entry_id, &mut items);
}
}
MessageView::Bash(output) => {
if !conversation_resolved {
collect_tool_outstanding_lines(&output.output, &message.entry_id, &mut items);
}
}
}
if items.len() >= 5 {
break;
}
}
items.reverse();
items
}
fn collect_outstanding_lines(
text_value: &str,
role: &str,
entry_id: &str,
items: &mut Vec<String>,
) {
for line in non_empty_lines(text_value) {
let lower = line.to_ascii_lowercase();
if is_resolved_line(&lower) {
continue;
}
if !contains_any(
&lower,
&[
"fail",
"failure",
"error",
"broken",
"cannot",
"can't",
"won't work",
"does not work",
"doesn't work",
"blocked",
"blocker",
"not fixed",
"not resolved",
"crash",
"todo",
"pending",
"remaining",
],
) {
continue;
}
if is_success_line(&lower) {
continue;
}
if is_short_or_omitted_line(&line) {
continue;
}
let item = if role == "user" {
format!("[user] {}", referenced_sentence(&line, 150, entry_id))
} else {
referenced_sentence(&line, 150, entry_id)
};
push_unique(items, item);
break;
}
}
fn collect_tool_outstanding_lines(text_value: &str, entry_id: &str, items: &mut Vec<String>) {
for line in non_empty_lines(text_value) {
let lower = line.to_ascii_lowercase();
if !is_tool_failure_line(&lower)
|| is_success_line(&lower)
|| is_short_or_omitted_line(&line)
{
continue;
}
push_unique(items, referenced_sentence(&line, 150, entry_id));
break;
}
}
fn is_short_or_omitted_line(line: &str) -> bool {
line.len() < 12 || line.starts_with("...")
}
fn is_tool_failure_line(line: &str) -> bool {
contains_any(
line,
&[
"error:",
"error ",
"failed",
"failure",
"panic",
"traceback",
"exception",
"command not found",
"no such file",
"permission denied",
],
)
}
fn is_resolved_line(line: &str) -> bool {
contains_any(
line,
&[
"fixed",
"resolved",
"passing",
"passes",
"now works",
"no longer",
"done",
"completed",
],
) && !contains_any(line, &["not fixed", "not resolved", "unresolved"])
}
fn is_success_line(line: &str) -> bool {
line.contains("test result: ok")
|| line.contains(" 0 failed")
|| line.contains("fail=0")
|| line.contains("failed 0")
|| line.contains("error=0")
}
fn conversation_brief(messages: &[ConversationMessage], embedder: Option<&Embedder>) -> String {
let mut sections: Vec<BriefSection> = Vec::new();
for message in messages {
match message.view() {
MessageView::Text { role, text } => {
if is_compact_summary(&text) {
continue;
}
if text.trim().is_empty() {
continue;
}
let header = match role {
"user" => "[user]",
"assistant" => "[assistant]",
_ => continue,
};
push_brief(
&mut sections,
header,
format!(
"{} ({})",
clean_sentence(&text, brief_limit(header)),
entry_ref(&message.entry_id)
),
);
}
MessageView::Assistant {
text, tool_calls, ..
} => {
if is_compact_summary(&text) {
continue;
}
if !text.trim().is_empty() {
push_brief(
&mut sections,
"[assistant]",
format!(
"{} ({})",
clean_sentence(&strip_self_talk(&text), ASSISTANT_LINE_MAX_CHARS),
entry_ref(&message.entry_id)
),
);
}
push_tool_calls(&mut sections, &tool_calls, &message.entry_id);
}
MessageView::Bash(output) => {
let cmd = compress_bash(output);
if !cmd.is_empty() {
push_brief(
&mut sections,
"[user]",
format!("$ {} ({})", cmd, entry_ref(&message.entry_id)),
);
}
}
MessageView::ToolResult(_) => {}
}
}
semantic_dedup_sections(&mut sections, embedder);
cap_brief(&stringify_brief(&mut sections))
}
fn semantic_dedup_sections(sections: &mut [BriefSection], embedder: Option<&Embedder>) {
let Some(embedder) = embedder else {
return;
};
let mut anchors: Vec<(usize, usize)> = Vec::new();
let mut texts: Vec<String> = Vec::new();
for (section_idx, section) in sections.iter().enumerate() {
for (line_idx, line) in section.lines.iter().enumerate() {
if line.starts_with("* ") {
continue;
}
anchors.push((section_idx, line_idx));
texts.push(strip_entry_ref(line));
}
}
if texts.len() < 2 {
return;
}
let Ok(embeddings) = embedder.embed(&texts) else {
return;
};
let mut drop: HashSet<(usize, usize)> = HashSet::new();
let mut kept: Vec<usize> = Vec::new();
for (idx, embedding) in embeddings.iter().enumerate() {
let duplicate = kept.iter().any(|&other| {
let mut dot = 0.0f32;
let mut norm_a = 0.0f32;
let mut norm_b = 0.0f32;
for (x, y) in embedding.iter().zip(&embeddings[other]) {
dot += x * y;
norm_a += x * x;
norm_b += y * y;
}
(if norm_a == 0.0 || norm_b == 0.0 {
0.0
} else {
dot / (norm_a.sqrt() * norm_b.sqrt())
}) >= BRIEF_PARAPHRASE_THRESHOLD
});
if duplicate {
drop.insert(anchors[idx]);
} else {
kept.push(idx);
}
}
for (section_idx, section) in sections.iter_mut().enumerate() {
let mut line_idx = 0;
section.lines.retain(|_| {
let keep = !drop.contains(&(section_idx, line_idx));
line_idx += 1;
keep
});
}
}
fn strip_entry_ref(line: &str) -> String {
match line.rfind(" (#") {
Some(idx) if line.ends_with(')') => line[..idx].to_string(),
_ => line.to_string(),
}
}
fn push_tool_calls(sections: &mut Vec<BriefSection>, tool_calls: &[&ToolCall], entry_id: &str) {
let visible_tool_calls: Vec<&ToolCall> = tool_calls
.iter()
.copied()
.filter(|tool_call| is_visible_tool_call(tool_call))
.collect();
if visible_tool_calls.is_empty() {
return;
}
let omitted = visible_tool_calls.len().saturating_sub(TOOL_CALLS_PER_TURN);
if omitted > 0 {
push_brief(
sections,
"[assistant]",
format!("* ({omitted} earlier tool-call entries omitted)"),
);
}
for tool_call in visible_tool_calls.into_iter().skip(omitted) {
push_brief(
sections,
"[assistant]",
format!("{} ({})", tool_one_liner(tool_call), entry_ref(entry_id)),
);
}
}
fn is_visible_tool_call(tool_call: &ToolCall) -> bool {
let name = tool_call.name.trim();
!name.is_empty() && !is_internal_tool_name(name)
}
fn is_internal_tool_name(name: &str) -> bool {
[
"todo",
"todowrite",
"task",
"task_status",
"progress",
"update_plan",
]
.iter()
.any(|internal| name.eq_ignore_ascii_case(internal))
}
fn push_brief(sections: &mut Vec<BriefSection>, header: &'static str, line: String) {
if line.trim().is_empty() {
return;
}
if sections.last().is_some_and(|last| last.header == header) {
if let Some(last) = sections.last_mut() {
last.lines.push(line);
}
return;
}
sections.push(BriefSection {
header,
lines: vec![line],
});
}
fn stringify_brief(sections: &mut [BriefSection]) -> String {
collapse_repeated_tool_lines(sections);
cap_tool_lines(sections);
let mut out = Vec::new();
for (idx, section) in sections.iter().enumerate() {
if idx > 0 {
let previous = §ions[idx - 1];
let previous_tools = previous.lines.iter().all(|line| line.starts_with("* "));
let current_tools = section.lines.iter().all(|line| line.starts_with("* "));
if !(previous.header == "[assistant]"
&& section.header == "[assistant]"
&& previous_tools
&& current_tools)
{
out.push(String::new());
}
}
out.push(section.header.to_string());
out.extend(section.lines.iter().cloned());
}
out.join("\n")
}
fn collapse_repeated_tool_lines(sections: &mut [BriefSection]) {
let mut seen: HashSet<String> = HashSet::new();
for section in sections {
if section.header != "[assistant]" {
continue;
}
section
.lines
.retain(|line| match tool_line_signature(line) {
Some(signature) => seen.insert(signature.to_string()),
None => true,
});
}
}
fn tool_line_signature(line: &str) -> Option<&str> {
if !line.starts_with("* ") {
return None;
}
line.rsplit_once(" (#").map(|(signature, _)| signature)
}
fn cap_tool_lines(sections: &mut [BriefSection]) {
for section in sections {
if section.header != "[assistant]" {
continue;
}
let tool_indexes: Vec<usize> = section
.lines
.iter()
.enumerate()
.filter_map(|(idx, line)| line.starts_with("* ").then_some(idx))
.collect();
if tool_indexes.len() <= TOOL_CALLS_PER_TURN {
continue;
}
let drop_count = tool_indexes.len() - TOOL_CALLS_PER_TURN;
let drop_set: BTreeSet<usize> = tool_indexes.iter().take(drop_count).copied().collect();
let first_kept = tool_indexes[drop_count];
let mut next = Vec::new();
let mut inserted = false;
for (idx, line) in section.lines.iter().enumerate() {
if drop_set.contains(&idx) {
continue;
}
if !inserted && idx == first_kept {
next.push(format!(
"* ({drop_count} earlier tool-call entries omitted)"
));
inserted = true;
}
next.push(line.clone());
}
section.lines = next;
}
}
fn cap_brief(value: &str) -> String {
let lines: Vec<&str> = value.lines().collect();
if lines.len() <= BRIEF_MAX_LINES {
return value.to_string();
}
let first_header = lines
.iter()
.position(|line| is_brief_header(line))
.unwrap_or(0);
let effective = &lines[first_header..];
if effective.len() <= BRIEF_MAX_LINES {
return effective.join("\n");
}
let head_len = BRIEF_HEAD_LINES.min(effective.len());
let tail_len = BRIEF_MAX_LINES - head_len;
let tail_start = effective.len() - tail_len;
let omitted = tail_start - head_len;
let head = effective[..head_len].join("\n");
let tail = effective[tail_start..].join("\n");
let tail_header = if is_brief_header(effective[tail_start]) {
String::new()
} else {
match effective[..tail_start]
.iter()
.rev()
.copied()
.find(|line| is_brief_header(line))
{
Some(header) => format!("{header}\n"),
None => String::new(),
}
};
format!("{head}\n\n...({omitted} earlier lines omitted)\n\n{tail_header}{tail}")
}
fn is_brief_header(line: &str) -> bool {
line.starts_with('[') && line.ends_with(']')
}
fn tool_one_liner(tool_call: &ToolCall) -> String {
if let Some(path) = display::path_argument(&tool_call.arguments) {
return format!("* {} \"{}\"", tool_call.name, text::clip(&path, 90));
}
if let Some(obj) = tool_call.arguments.as_object() {
if (tool_call.name == "bash" || tool_call.name == "Bash")
&& let Some(command) = obj.get("command").and_then(|value| value.as_str())
{
return format!("* {} \"{}\"", tool_call.name, compress_command(command));
}
for key in ["query", "pattern", "description"] {
if let Some(value) = obj.get(key).and_then(|value| value.as_str()) {
return format!("* {} \"{}\"", tool_call.name, text::clip(value, 60));
}
}
}
format!("* {}", tool_call.name)
}
fn compress_bash(output: &BashOutput) -> String {
compress_command(&output.command)
}
fn compress_command(command: &str) -> String {
let mut cmd = command
.lines()
.map(str::trim)
.find(|line| !line.is_empty())
.unwrap_or(command)
.to_string();
if let Some(stripped) = strip_cd_prefix(&cmd) {
cmd = stripped.to_string();
}
for pipe in [
" | head",
" | tail",
" | sort",
" | wc",
" | column",
" | tr",
" | cut",
] {
if let Some(idx) = cmd.rfind(pipe) {
cmd.truncate(idx);
}
}
text::clip(&cmd, BASH_LINE_MAX_CHARS)
}
fn strip_cd_prefix(command: &str) -> Option<&str> {
let rest = command.strip_prefix("cd ")?;
let (_, suffix) = rest.split_once(" && ")?;
Some(suffix)
}
fn entry_ref(entry_id: &str) -> String {
format!("#{entry_id}")
}
fn brief_limit(header: &str) -> usize {
if header == "[assistant]" {
ASSISTANT_LINE_MAX_CHARS
} else {
TEXT_LINE_MAX_CHARS
}
}
fn strip_self_talk(value: &str) -> String {
let mut text_value = value.trim().to_string();
for _ in 0..2 {
let lower = text_value.to_ascii_lowercase();
let Some(prefix) = ["hmm", "wait", "actually", "oh", "okay", "ok", "well", "so"]
.iter()
.find(|prefix| lower.starts_with(**prefix))
else {
break;
};
let rest = text_value[prefix.len()..].trim_start_matches([',', '.', '!', ' ', '-']);
if rest == text_value {
break;
}
text_value = rest.to_string();
}
text_value
}
fn non_empty_lines(value: &str) -> Vec<String> {
text::sanitize(value)
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.filter(|line| !line.starts_with("<skill") && !line.starts_with("</skill"))
.map(ToString::to_string)
.collect()
}
fn clean_sentence(value: &str, max_chars: usize) -> String {
let flat = value.split_whitespace().collect::<Vec<_>>().join(" ");
text::clip(flat.trim_matches(['-', '*', ' ']), max_chars)
}
fn referenced_sentence(value: &str, max_chars: usize, entry_id: &str) -> String {
format!(
"{} ({})",
clean_sentence(value, max_chars),
entry_ref(entry_id)
)
}
fn contains_any(value: &str, needles: &[&str]) -> bool {
needles.iter().any(|needle| value.contains(needle))
}
fn push_unique(items: &mut Vec<String>, item: String) {
if !item.is_empty() && !items.iter().any(|existing| existing == &item) {
items.push(item);
}
}