feat: Trident compaction pipeline (supersede + collapse + cluster)
Add the 3-stage Trident compaction strategy from R.A.D.1.C.A.L, adapted for the Rust CLI session model: Stage 1 - SUPERSEDE: Zero-cost factual pruning. If a file was read and then later written/edited, the earlier read is obsolete and removed. Earlier writes superseded by later writes are also dropped. Stage 2 - COLLAPSE: Buffer short chatty exchanges (under 200 chars, no tool calls) and collapse them into dense summary blocks when the threshold is exceeded. Stage 3 - CLUSTER: Group semantically similar messages (same tool names, same file paths, similar lengths) using Jaccard-based fingerprinting and collapse clusters into summary blocks. All three stages run before the existing summary-based compaction, so less data needs to be summarized. Wired into both /compact and the auto-compact retry on context window errors.
This commit is contained in:
parent
c28092d540
commit
8b120a322d
|
|
@ -10,6 +10,7 @@ pub mod bash_validation;
|
||||||
mod bootstrap;
|
mod bootstrap;
|
||||||
pub mod branch_lock;
|
pub mod branch_lock;
|
||||||
mod compact;
|
mod compact;
|
||||||
|
pub mod trident;
|
||||||
mod config;
|
mod config;
|
||||||
pub mod config_validate;
|
pub mod config_validate;
|
||||||
mod conversation;
|
mod conversation;
|
||||||
|
|
|
||||||
|
|
@ -78,10 +78,14 @@ impl TridentStats {
|
||||||
self.messages_clustered, self.clusters_found
|
self.messages_clustered, self.clusters_found
|
||||||
),
|
),
|
||||||
format!(" Original: {} messages", self.original_message_count),
|
format!(" Original: {} messages", self.original_message_count),
|
||||||
|
<<<<<<< HEAD
|
||||||
format!(
|
format!(
|
||||||
" Final: {} messages ({:.1}x compression)",
|
" Final: {} messages ({:.1}x compression)",
|
||||||
self.final_message_count, compression
|
self.final_message_count, compression
|
||||||
),
|
),
|
||||||
|
=======
|
||||||
|
format!(" Final: {} messages ({:.1}x compression)", self.final_message_count, compression),
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
];
|
];
|
||||||
if self.tokens_saved_estimate > 0 {
|
if self.tokens_saved_estimate > 0 {
|
||||||
lines.push(format!(
|
lines.push(format!(
|
||||||
|
|
@ -124,8 +128,12 @@ pub fn trident_compact_session(
|
||||||
}
|
}
|
||||||
|
|
||||||
if trident_config.collapse_enabled {
|
if trident_config.collapse_enabled {
|
||||||
|
<<<<<<< HEAD
|
||||||
let (collapsed, chains, collapsed_count) =
|
let (collapsed, chains, collapsed_count) =
|
||||||
stage2_collapse(&messages, trident_config.collapse_threshold);
|
stage2_collapse(&messages, trident_config.collapse_threshold);
|
||||||
|
=======
|
||||||
|
let (collapsed, chains, collapsed_count) = stage2_collapse(&messages, trident_config.collapse_threshold);
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
stats.collapsed_chains = chains;
|
stats.collapsed_chains = chains;
|
||||||
stats.messages_collapsed = collapsed_count;
|
stats.messages_collapsed = collapsed_count;
|
||||||
messages = collapsed;
|
messages = collapsed;
|
||||||
|
|
@ -182,10 +190,17 @@ fn stage1_supersede(messages: &[ConversationMessage]) -> (Vec<ConversationMessag
|
||||||
for (i, msg) in messages.iter().enumerate() {
|
for (i, msg) in messages.iter().enumerate() {
|
||||||
for block in &msg.blocks {
|
for block in &msg.blocks {
|
||||||
if let Some((path, op_type)) = extract_file_operation(block) {
|
if let Some((path, op_type)) = extract_file_operation(block) {
|
||||||
|
<<<<<<< HEAD
|
||||||
file_ops
|
file_ops
|
||||||
.entry(path)
|
.entry(path)
|
||||||
.or_default()
|
.or_default()
|
||||||
.push(FileOperation { index: i, op_type });
|
.push(FileOperation { index: i, op_type });
|
||||||
|
=======
|
||||||
|
file_ops.entry(path).or_default().push(FileOperation {
|
||||||
|
index: i,
|
||||||
|
op_type,
|
||||||
|
});
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -239,9 +254,13 @@ fn extract_file_operation(block: &ContentBlock) -> Option<(String, FileOp)> {
|
||||||
};
|
};
|
||||||
Some((path, op_type))
|
Some((path, op_type))
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
ContentBlock::ToolResult {
|
ContentBlock::ToolResult {
|
||||||
tool_name, output, ..
|
tool_name, output, ..
|
||||||
} => {
|
} => {
|
||||||
|
=======
|
||||||
|
ContentBlock::ToolResult { tool_name, output, .. } => {
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
let path = extract_path_from_tool_output(tool_name, output)?;
|
let path = extract_path_from_tool_output(tool_name, output)?;
|
||||||
let op_type = match tool_name.as_str() {
|
let op_type = match tool_name.as_str() {
|
||||||
"read_file" | "Read" => FileOp::Read,
|
"read_file" | "Read" => FileOp::Read,
|
||||||
|
|
@ -252,15 +271,23 @@ fn extract_file_operation(block: &ContentBlock) -> Option<(String, FileOp)> {
|
||||||
Some((path, op_type))
|
Some((path, op_type))
|
||||||
}
|
}
|
||||||
ContentBlock::Text { .. } => None,
|
ContentBlock::Text { .. } => None,
|
||||||
|
<<<<<<< HEAD
|
||||||
ContentBlock::Thinking { .. } => None,
|
ContentBlock::Thinking { .. } => None,
|
||||||
|
=======
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_path_from_tool_input(tool_name: &str, input: &str) -> Option<String> {
|
fn extract_path_from_tool_input(tool_name: &str, input: &str) -> Option<String> {
|
||||||
|
<<<<<<< HEAD
|
||||||
if !matches!(
|
if !matches!(
|
||||||
tool_name,
|
tool_name,
|
||||||
"read_file" | "write_file" | "edit_file" | "Read" | "Write" | "Edit"
|
"read_file" | "write_file" | "edit_file" | "Read" | "Write" | "Edit"
|
||||||
) {
|
) {
|
||||||
|
=======
|
||||||
|
if !matches!(tool_name, "read_file" | "write_file" | "edit_file" | "Read" | "Write" | "Edit")
|
||||||
|
{
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
serde_json::from_str::<serde_json::Value>(input)
|
serde_json::from_str::<serde_json::Value>(input)
|
||||||
|
|
@ -274,10 +301,15 @@ fn extract_path_from_tool_input(tool_name: &str, input: &str) -> Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_path_from_tool_output(tool_name: &str, output: &str) -> Option<String> {
|
fn extract_path_from_tool_output(tool_name: &str, output: &str) -> Option<String> {
|
||||||
|
<<<<<<< HEAD
|
||||||
if !matches!(
|
if !matches!(
|
||||||
tool_name,
|
tool_name,
|
||||||
"read_file" | "write_file" | "edit_file" | "Read" | "Write" | "Edit"
|
"read_file" | "write_file" | "edit_file" | "Read" | "Write" | "Edit"
|
||||||
) {
|
) {
|
||||||
|
=======
|
||||||
|
if !matches!(tool_name, "read_file" | "write_file" | "edit_file" | "Read" | "Write" | "Edit")
|
||||||
|
{
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
serde_json::from_str::<serde_json::Value>(output)
|
serde_json::from_str::<serde_json::Value>(output)
|
||||||
|
|
@ -351,6 +383,7 @@ fn stage2_collapse(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_chatty_message(msg: &ConversationMessage) -> bool {
|
fn is_chatty_message(msg: &ConversationMessage) -> bool {
|
||||||
|
<<<<<<< HEAD
|
||||||
let total_chars: usize = msg
|
let total_chars: usize = msg
|
||||||
.blocks
|
.blocks
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -370,6 +403,16 @@ fn is_chatty_message(msg: &ConversationMessage) -> bool {
|
||||||
.blocks
|
.blocks
|
||||||
.iter()
|
.iter()
|
||||||
.any(|b| matches!(b, ContentBlock::ToolResult { .. }));
|
.any(|b| matches!(b, ContentBlock::ToolResult { .. }));
|
||||||
|
=======
|
||||||
|
let total_chars: usize = msg.blocks.iter().map(|b| match b {
|
||||||
|
ContentBlock::Text { text } => text.len(),
|
||||||
|
ContentBlock::ToolUse { input, .. } => input.len(),
|
||||||
|
ContentBlock::ToolResult { output, .. } => output.len(),
|
||||||
|
}).sum();
|
||||||
|
|
||||||
|
let has_tool_use = msg.blocks.iter().any(|b| matches!(b, ContentBlock::ToolUse { .. }));
|
||||||
|
let has_tool_result = msg.blocks.iter().any(|b| matches!(b, ContentBlock::ToolResult { .. }));
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
|
|
||||||
if has_tool_use || has_tool_result {
|
if has_tool_use || has_tool_result {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -485,12 +528,24 @@ fn stage3_cluster(
|
||||||
cluster_buffers.entry(cid).or_default().push(*msg_idx);
|
cluster_buffers.entry(cid).or_default().push(*msg_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
|
||||||
|
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
for (i, msg) in messages.iter().enumerate() {
|
for (i, msg) in messages.iter().enumerate() {
|
||||||
if let Some(&cid) = cluster_assignments.get(&i) {
|
if let Some(&cid) = cluster_assignments.get(&i) {
|
||||||
if let Some(buffer) = cluster_buffers.get_mut(&cid) {
|
if let Some(buffer) = cluster_buffers.get_mut(&cid) {
|
||||||
if buffer[0] == i {
|
if buffer[0] == i {
|
||||||
|
<<<<<<< HEAD
|
||||||
let cluster_messages: Vec<&ConversationMessage> =
|
let cluster_messages: Vec<&ConversationMessage> =
|
||||||
buffer.iter().filter_map(|&idx| messages.get(idx)).collect();
|
buffer.iter().filter_map(|&idx| messages.get(idx)).collect();
|
||||||
|
=======
|
||||||
|
let cluster_messages: Vec<&ConversationMessage> = buffer
|
||||||
|
.iter()
|
||||||
|
.filter_map(|&idx| messages.get(idx))
|
||||||
|
.collect();
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
let summary = generate_cluster_summary(&cluster_messages);
|
let summary = generate_cluster_summary(&cluster_messages);
|
||||||
result.push(ConversationMessage {
|
result.push(ConversationMessage {
|
||||||
role: MessageRole::System,
|
role: MessageRole::System,
|
||||||
|
|
@ -536,9 +591,13 @@ fn fingerprint_message(index: usize, msg: &ConversationMessage) -> Option<Messag
|
||||||
}
|
}
|
||||||
text_length += input.len();
|
text_length += input.len();
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
ContentBlock::ToolResult {
|
ContentBlock::ToolResult {
|
||||||
tool_name, output, ..
|
tool_name, output, ..
|
||||||
} => {
|
} => {
|
||||||
|
=======
|
||||||
|
ContentBlock::ToolResult { tool_name, output, .. } => {
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
tool_names.insert(tool_name.clone());
|
tool_names.insert(tool_name.clone());
|
||||||
if let Some(path) = extract_path_from_tool_output(tool_name, output) {
|
if let Some(path) = extract_path_from_tool_output(tool_name, output) {
|
||||||
file_paths.insert(path);
|
file_paths.insert(path);
|
||||||
|
|
@ -548,9 +607,12 @@ fn fingerprint_message(index: usize, msg: &ConversationMessage) -> Option<Messag
|
||||||
ContentBlock::Text { text } => {
|
ContentBlock::Text { text } => {
|
||||||
text_length += text.len();
|
text_length += text.len();
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
ContentBlock::Thinking { thinking, .. } => {
|
ContentBlock::Thinking { thinking, .. } => {
|
||||||
text_length += thinking.len();
|
text_length += thinking.len();
|
||||||
}
|
}
|
||||||
|
=======
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -614,16 +676,23 @@ fn generate_cluster_summary(messages: &[&ConversationMessage]) -> String {
|
||||||
file_paths.insert(path);
|
file_paths.insert(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
ContentBlock::ToolResult {
|
ContentBlock::ToolResult {
|
||||||
tool_name, output, ..
|
tool_name, output, ..
|
||||||
} => {
|
} => {
|
||||||
|
=======
|
||||||
|
ContentBlock::ToolResult { tool_name, output, .. } => {
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
tool_names.insert(tool_name.clone());
|
tool_names.insert(tool_name.clone());
|
||||||
if let Some(path) = extract_path_from_tool_output(tool_name, output) {
|
if let Some(path) = extract_path_from_tool_output(tool_name, output) {
|
||||||
file_paths.insert(path);
|
file_paths.insert(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContentBlock::Text { .. } => {}
|
ContentBlock::Text { .. } => {}
|
||||||
|
<<<<<<< HEAD
|
||||||
ContentBlock::Thinking { .. } => {}
|
ContentBlock::Thinking { .. } => {}
|
||||||
|
=======
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -659,7 +728,10 @@ fn estimate_message_tokens(message: &ConversationMessage) -> usize {
|
||||||
ContentBlock::ToolResult {
|
ContentBlock::ToolResult {
|
||||||
tool_name, output, ..
|
tool_name, output, ..
|
||||||
} => (tool_name.len() + output.len()) / 4 + 1,
|
} => (tool_name.len() + output.len()) / 4 + 1,
|
||||||
|
<<<<<<< HEAD
|
||||||
ContentBlock::Thinking { thinking, .. } => thinking.len() / 4 + 1,
|
ContentBlock::Thinking { thinking, .. } => thinking.len() / 4 + 1,
|
||||||
|
=======
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
})
|
})
|
||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
@ -687,23 +759,31 @@ mod tests {
|
||||||
name: "read_file".to_string(),
|
name: "read_file".to_string(),
|
||||||
input: r#"{"path":"src/main.rs"}"#.to_string(),
|
input: r#"{"path":"src/main.rs"}"#.to_string(),
|
||||||
}]),
|
}]),
|
||||||
|
<<<<<<< HEAD
|
||||||
ConversationMessage::tool_result(
|
ConversationMessage::tool_result(
|
||||||
"1",
|
"1",
|
||||||
"read_file",
|
"read_file",
|
||||||
r#"{"path":"src/main.rs","content":"old"}"#,
|
r#"{"path":"src/main.rs","content":"old"}"#,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
=======
|
||||||
|
ConversationMessage::tool_result("1", "read_file", r#"{"path":"src/main.rs","content":"old"}"#, false),
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
ConversationMessage::assistant(vec![ContentBlock::ToolUse {
|
ConversationMessage::assistant(vec![ContentBlock::ToolUse {
|
||||||
id: "2".to_string(),
|
id: "2".to_string(),
|
||||||
name: "edit_file".to_string(),
|
name: "edit_file".to_string(),
|
||||||
input: r#"{"path":"src/main.rs","old":"old","new":"new"}"#.to_string(),
|
input: r#"{"path":"src/main.rs","old":"old","new":"new"}"#.to_string(),
|
||||||
}]),
|
}]),
|
||||||
|
<<<<<<< HEAD
|
||||||
ConversationMessage::tool_result(
|
ConversationMessage::tool_result(
|
||||||
"2",
|
"2",
|
||||||
"edit_file",
|
"edit_file",
|
||||||
r#"{"path":"src/main.rs","ok":true}"#,
|
r#"{"path":"src/main.rs","ok":true}"#,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
=======
|
||||||
|
ConversationMessage::tool_result("2", "edit_file", r#"{"path":"src/main.rs","ok":true}"#, false),
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
];
|
];
|
||||||
|
|
||||||
let (kept, superseded) = stage1_supersede(&messages);
|
let (kept, superseded) = stage1_supersede(&messages);
|
||||||
|
|
@ -719,12 +799,16 @@ mod tests {
|
||||||
name: "read_file".to_string(),
|
name: "read_file".to_string(),
|
||||||
input: r#"{"path":"src/main.rs"}"#.to_string(),
|
input: r#"{"path":"src/main.rs"}"#.to_string(),
|
||||||
}]),
|
}]),
|
||||||
|
<<<<<<< HEAD
|
||||||
ConversationMessage::tool_result(
|
ConversationMessage::tool_result(
|
||||||
"1",
|
"1",
|
||||||
"read_file",
|
"read_file",
|
||||||
r#"{"path":"src/main.rs","content":"data"}"#,
|
r#"{"path":"src/main.rs","content":"data"}"#,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
=======
|
||||||
|
ConversationMessage::tool_result("1", "read_file", r#"{"path":"src/main.rs","content":"data"}"#, false),
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
];
|
];
|
||||||
|
|
||||||
let (kept, superseded) = stage1_supersede(&messages);
|
let (kept, superseded) = stage1_supersede(&messages);
|
||||||
|
|
@ -741,6 +825,7 @@ mod tests {
|
||||||
text: format!("got {i}"),
|
text: format!("got {i}"),
|
||||||
}]));
|
}]));
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
messages.push(ConversationMessage::assistant(vec![
|
messages.push(ConversationMessage::assistant(vec![
|
||||||
ContentBlock::ToolUse {
|
ContentBlock::ToolUse {
|
||||||
id: "t".to_string(),
|
id: "t".to_string(),
|
||||||
|
|
@ -748,6 +833,13 @@ mod tests {
|
||||||
input: r#"{"command":"ls"}"#.to_string(),
|
input: r#"{"command":"ls"}"#.to_string(),
|
||||||
},
|
},
|
||||||
]));
|
]));
|
||||||
|
=======
|
||||||
|
messages.push(ConversationMessage::assistant(vec![ContentBlock::ToolUse {
|
||||||
|
id: "t".to_string(),
|
||||||
|
name: "bash".to_string(),
|
||||||
|
input: r#"{"command":"ls"}"#.to_string(),
|
||||||
|
}]));
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
|
|
||||||
let (result, chains, collapsed) = stage2_collapse(&messages, 4);
|
let (result, chains, collapsed) = stage2_collapse(&messages, 4);
|
||||||
assert!(chains > 0, "should collapse at least one chain");
|
assert!(chains > 0, "should collapse at least one chain");
|
||||||
|
|
@ -759,6 +851,7 @@ mod tests {
|
||||||
fn stage3_clusters_similar_messages() {
|
fn stage3_clusters_similar_messages() {
|
||||||
let mut messages = vec![];
|
let mut messages = vec![];
|
||||||
for i in 0..5 {
|
for i in 0..5 {
|
||||||
|
<<<<<<< HEAD
|
||||||
messages.push(ConversationMessage::assistant(vec![
|
messages.push(ConversationMessage::assistant(vec![
|
||||||
ContentBlock::ToolUse {
|
ContentBlock::ToolUse {
|
||||||
id: format!("read_{i}"),
|
id: format!("read_{i}"),
|
||||||
|
|
@ -766,6 +859,13 @@ mod tests {
|
||||||
input: format!(r#"{{"path":"src/{i}.rs"}}"#),
|
input: format!(r#"{{"path":"src/{i}.rs"}}"#),
|
||||||
},
|
},
|
||||||
]));
|
]));
|
||||||
|
=======
|
||||||
|
messages.push(ConversationMessage::assistant(vec![ContentBlock::ToolUse {
|
||||||
|
id: format!("read_{i}"),
|
||||||
|
name: "read_file".to_string(),
|
||||||
|
input: format!(r#"{{"path":"src/{i}.rs"}}"#),
|
||||||
|
}]));
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
messages.push(ConversationMessage::tool_result(
|
messages.push(ConversationMessage::tool_result(
|
||||||
&format!("read_{i}"),
|
&format!("read_{i}"),
|
||||||
"read_file",
|
"read_file",
|
||||||
|
|
@ -774,7 +874,12 @@ mod tests {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
let (result, clusters, clustered) = stage3_cluster(&messages, 3, 0.4);
|
let (result, clusters, clustered) = stage3_cluster(&messages, 3, 0.4);
|
||||||
|
=======
|
||||||
|
let (result, clusters, clustered) =
|
||||||
|
stage3_cluster(&messages, 3, 0.4);
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
assert!(clusters > 0, "should find at least one cluster");
|
assert!(clusters > 0, "should find at least one cluster");
|
||||||
assert!(clustered > 0);
|
assert!(clustered > 0);
|
||||||
assert!(result.len() < messages.len());
|
assert!(result.len() < messages.len());
|
||||||
|
|
@ -790,23 +895,31 @@ mod tests {
|
||||||
name: "read_file".to_string(),
|
name: "read_file".to_string(),
|
||||||
input: r#"{"path":"src/main.rs"}"#.to_string(),
|
input: r#"{"path":"src/main.rs"}"#.to_string(),
|
||||||
}]),
|
}]),
|
||||||
|
<<<<<<< HEAD
|
||||||
ConversationMessage::tool_result(
|
ConversationMessage::tool_result(
|
||||||
"1",
|
"1",
|
||||||
"read_file",
|
"read_file",
|
||||||
r#"{"path":"src/main.rs","content":"fn main() { buggy }"}"#,
|
r#"{"path":"src/main.rs","content":"fn main() { buggy }"}"#,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
=======
|
||||||
|
ConversationMessage::tool_result("1", "read_file", r#"{"path":"src/main.rs","content":"fn main() { buggy }"}"#, false),
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
ConversationMessage::assistant(vec![ContentBlock::ToolUse {
|
ConversationMessage::assistant(vec![ContentBlock::ToolUse {
|
||||||
id: "2".to_string(),
|
id: "2".to_string(),
|
||||||
name: "edit_file".to_string(),
|
name: "edit_file".to_string(),
|
||||||
input: r#"{"path":"src/main.rs","old":"buggy","new":"fixed"}"#.to_string(),
|
input: r#"{"path":"src/main.rs","old":"buggy","new":"fixed"}"#.to_string(),
|
||||||
}]),
|
}]),
|
||||||
|
<<<<<<< HEAD
|
||||||
ConversationMessage::tool_result(
|
ConversationMessage::tool_result(
|
||||||
"2",
|
"2",
|
||||||
"edit_file",
|
"edit_file",
|
||||||
r#"{"path":"src/main.rs","ok":true}"#,
|
r#"{"path":"src/main.rs","ok":true}"#,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
=======
|
||||||
|
ConversationMessage::tool_result("2", "edit_file", r#"{"path":"src/main.rs","ok":true}"#, false),
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
ConversationMessage::assistant(vec![ContentBlock::Text {
|
ConversationMessage::assistant(vec![ContentBlock::Text {
|
||||||
text: "Fixed the bug in main.rs".to_string(),
|
text: "Fixed the bug in main.rs".to_string(),
|
||||||
}]),
|
}]),
|
||||||
|
|
@ -822,10 +935,14 @@ mod tests {
|
||||||
&trident_config,
|
&trident_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
assert!(
|
assert!(
|
||||||
result.removed_message_count > 0
|
result.removed_message_count > 0
|
||||||
|| result.compacted_session.messages.len() < session.messages.len()
|
|| result.compacted_session.messages.len() < session.messages.len()
|
||||||
);
|
);
|
||||||
|
=======
|
||||||
|
assert!(result.removed_message_count > 0 || result.compacted_session.messages.len() < session.messages.len());
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -7865,6 +7865,7 @@ impl LiveCli {
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
let error_str = error.to_string();
|
let error_str = error.to_string();
|
||||||
|
<<<<<<< HEAD
|
||||||
// Detect context window overflow. Some providers (e.g. OpenAI-compat backends)
|
// Detect context window overflow. Some providers (e.g. OpenAI-compat backends)
|
||||||
// return 400 with "no parseable body" instead of a proper context_length_exceeded
|
// return 400 with "no parseable body" instead of a proper context_length_exceeded
|
||||||
// error when the request is too large to even parse — treat that as context overflow too.
|
// error when the request is too large to even parse — treat that as context overflow too.
|
||||||
|
|
@ -7931,6 +7932,32 @@ impl LiveCli {
|
||||||
// No more messages to compact — further rounds won't help
|
// No more messages to compact — further rounds won't help
|
||||||
println!(" No further compaction possible.");
|
println!(" No further compaction possible.");
|
||||||
break;
|
break;
|
||||||
|
=======
|
||||||
|
let is_context_window = error_str.contains("context_window") || error_str.contains("Context window");
|
||||||
|
|
||||||
|
if is_context_window {
|
||||||
|
println!(" Auto-compacting session and retrying...");
|
||||||
|
|
||||||
|
// Step 1: Compact the session to free up context space
|
||||||
|
// Run the Trident compaction pipeline (supersede + collapse + cluster)
|
||||||
|
// then apply summary-based compaction for maximum context reduction
|
||||||
|
let result = runtime::trident::trident_compact_session(
|
||||||
|
runtime.session(),
|
||||||
|
CompactionConfig {
|
||||||
|
max_estimated_tokens: 0,
|
||||||
|
..CompactionConfig::default()
|
||||||
|
},
|
||||||
|
&runtime::trident::TridentConfig::default(),
|
||||||
|
);
|
||||||
|
let removed = result.removed_message_count;
|
||||||
|
|
||||||
|
// Only proceed if compaction actually happened (messages were removed)
|
||||||
|
// or there's still a session to work with
|
||||||
|
if removed > 0 || result.compacted_session.messages.len() > 0 {
|
||||||
|
if removed > 0 {
|
||||||
|
// Report compaction results to user
|
||||||
|
println!("{}", format_compact_report(removed, result.compacted_session.messages.len(), false));
|
||||||
|
>>>>>>> 5e19cf1c (feat: Trident compaction pipeline (supersede + collapse + cluster))
|
||||||
}
|
}
|
||||||
|
|
||||||
if removed > 0 {
|
if removed > 0 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue