@@ -16,7 +16,6 @@ use crate::protocol::TurnContextItem;
1616use crate :: protocol:: WarningEvent ;
1717use crate :: truncate:: truncate_middle;
1818use crate :: util:: backoff;
19- use askama:: Template ;
2019use codex_protocol:: items:: TurnItem ;
2120use codex_protocol:: models:: ContentItem ;
2221use codex_protocol:: models:: ResponseInputItem ;
@@ -29,13 +28,6 @@ use tracing::error;
2928pub const SUMMARIZATION_PROMPT : & str = include_str ! ( "../../templates/compact/prompt.md" ) ;
3029const COMPACT_USER_MESSAGE_MAX_TOKENS : usize = 20_000 ;
3130
32- #[ derive( Template ) ]
33- #[ template( path = "compact/history_bridge.md" , escape = "none" ) ]
34- struct HistoryBridgeTemplate < ' a > {
35- user_messages_text : & ' a str ,
36- summary_text : & ' a str ,
37- }
38-
3931pub ( crate ) async fn run_inline_auto_compact_task (
4032 sess : Arc < Session > ,
4133 turn_context : Arc < TurnContext > ,
@@ -150,6 +142,7 @@ async fn run_compact_task_inner(
150142 let history_snapshot = sess. clone_history ( ) . await . get_history ( ) ;
151143 let summary_text = get_last_assistant_message_from_turn ( & history_snapshot) . unwrap_or_default ( ) ;
152144 let user_messages = collect_user_messages ( & history_snapshot) ;
145+
153146 let initial_context = sess. build_initial_context ( turn_context. as_ref ( ) ) ;
154147 let mut new_history = build_compacted_history ( initial_context, & user_messages, & summary_text) ;
155148 let ghost_snapshots: Vec < ResponseItem > = history_snapshot
@@ -224,33 +217,47 @@ fn build_compacted_history_with_limit(
224217 summary_text : & str ,
225218 max_bytes : usize ,
226219) -> Vec < ResponseItem > {
227- let mut user_messages_text = if user_messages. is_empty ( ) {
228- "(none)" . to_string ( )
229- } else {
230- user_messages. join ( "\n \n " )
231- } ;
232- // Truncate the concatenated prior user messages so the bridge message
233- // stays well under the context window (approx. 4 bytes/token).
234- if user_messages_text. len ( ) > max_bytes {
235- user_messages_text = truncate_middle ( & user_messages_text, max_bytes) . 0 ;
220+ let mut selected_messages: Vec < String > = Vec :: new ( ) ;
221+ if max_bytes > 0 {
222+ let mut remaining = max_bytes;
223+ for message in user_messages. iter ( ) . rev ( ) {
224+ if remaining == 0 {
225+ break ;
226+ }
227+ if message. len ( ) <= remaining {
228+ selected_messages. push ( message. clone ( ) ) ;
229+ remaining = remaining. saturating_sub ( message. len ( ) ) ;
230+ } else {
231+ let ( truncated, _) = truncate_middle ( message, remaining) ;
232+ selected_messages. push ( truncated) ;
233+ break ;
234+ }
235+ }
236+ selected_messages. reverse ( ) ;
237+ }
238+
239+ for message in & selected_messages {
240+ history. push ( ResponseItem :: Message {
241+ id : None ,
242+ role : "user" . to_string ( ) ,
243+ content : vec ! [ ContentItem :: InputText {
244+ text: message. clone( ) ,
245+ } ] ,
246+ } ) ;
236247 }
248+
237249 let summary_text = if summary_text. is_empty ( ) {
238250 "(no summary available)" . to_string ( )
239251 } else {
240252 summary_text. to_string ( )
241253 } ;
242- let Ok ( bridge) = HistoryBridgeTemplate {
243- user_messages_text : & user_messages_text,
244- summary_text : & summary_text,
245- }
246- . render ( ) else {
247- return vec ! [ ] ;
248- } ;
254+
249255 history. push ( ResponseItem :: Message {
250256 id : None ,
251257 role : "user" . to_string ( ) ,
252- content : vec ! [ ContentItem :: InputText { text: bridge } ] ,
258+ content : vec ! [ ContentItem :: InputText { text: summary_text } ] ,
253259 } ) ;
260+
254261 history
255262}
256263
@@ -390,30 +397,55 @@ mod tests {
390397 "SUMMARY" ,
391398 max_bytes,
392399 ) ;
400+ assert_eq ! ( history. len( ) , 2 ) ;
393401
394- // Expect exactly one bridge message added to history (plus any initial context we provided, which is none).
395- assert_eq ! ( history . len ( ) , 1 ) ;
402+ let truncated_message = & history[ 0 ] ;
403+ let summary_message = & history [ 1 ] ;
396404
397- // Extract the text content of the bridge message.
398- let bridge_text = match & history[ 0 ] {
405+ let truncated_text = match truncated_message {
399406 ResponseItem :: Message { role, content, .. } if role == "user" => {
400407 content_items_to_text ( content) . unwrap_or_default ( )
401408 }
402409 other => panic ! ( "unexpected item in history: {other:?}" ) ,
403410 } ;
404411
405- // The bridge should contain the truncation marker and not the full original payload.
406412 assert ! (
407- bridge_text . contains( "tokens truncated" ) ,
408- "expected truncation marker in bridge message"
413+ truncated_text . contains( "tokens truncated" ) ,
414+ "expected truncation marker in truncated user message"
409415 ) ;
410416 assert ! (
411- !bridge_text . contains( & big) ,
412- "bridge should not include the full oversized user text"
417+ !truncated_text . contains( & big) ,
418+ "truncated user message should not include the full oversized user text"
413419 ) ;
420+
421+ let summary_text = match summary_message {
422+ ResponseItem :: Message { role, content, .. } if role == "user" => {
423+ content_items_to_text ( content) . unwrap_or_default ( )
424+ }
425+ other => panic ! ( "unexpected item in history: {other:?}" ) ,
426+ } ;
427+ assert_eq ! ( summary_text, "SUMMARY" ) ;
428+ }
429+
430+ #[ test]
431+ fn build_compacted_history_appends_summary_message ( ) {
432+ let initial_context: Vec < ResponseItem > = Vec :: new ( ) ;
433+ let user_messages = vec ! [ "first user message" . to_string( ) ] ;
434+ let summary_text = "summary text" ;
435+
436+ let history = build_compacted_history ( initial_context, & user_messages, summary_text) ;
414437 assert ! (
415- bridge_text . contains ( "SUMMARY" ) ,
416- "bridge should include the provided summary text "
438+ !history . is_empty ( ) ,
439+ "expected compacted history to include summary"
417440 ) ;
441+
442+ let last = history. last ( ) . expect ( "history should have a summary entry" ) ;
443+ let summary = match last {
444+ ResponseItem :: Message { role, content, .. } if role == "user" => {
445+ content_items_to_text ( content) . unwrap_or_default ( )
446+ }
447+ other => panic ! ( "expected summary message, found {other:?}" ) ,
448+ } ;
449+ assert_eq ! ( summary, summary_text) ;
418450 }
419451}
0 commit comments