Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 67 additions & 3 deletions src/providers/codex/translate/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,21 @@ fn is_compact_message_content(content: &Value) -> bool {
}
}

/// Claude Code appends a `system`-role message after the user turn when a
/// `UserPromptSubmit` hook or an output style emits text, so the compaction
/// instruction is carried by the final *user* message, not the final message.
/// Trailing `system` messages are skipped; any other trailing role still means
/// the request is not a compaction boundary.
pub(crate) fn is_compact_messages_request(request: &MessagesRequest) -> bool {
is_compact_request(flatten_system_text(request.extra.get("system")).as_deref())
|| request.messages.last().is_some_and(|message| {
message.role == "user" && is_compact_message_content(&message.content)
})
|| request
.messages
.iter()
.rev()
.find(|message| message.role != "system")
.is_some_and(|message| {
message.role == "user" && is_compact_message_content(&message.content)
})
}

/// Reasoning-effort cap applied to compaction requests, or None when the
Expand Down Expand Up @@ -1937,6 +1947,60 @@ mod tests {
assert!(is_compact_messages_request(&req));
}

#[test]
fn compact_request_detected_when_hook_system_message_follows_user() {
// Claude Code 2.1.319 shape: the compaction instruction is appended to the
// user's text and a `system`-role hook/output-style message comes last.
let req: MessagesRequest = serde_json::from_value(json!({
"model": "gpt-6-astra",
"messages": [
{"role": "user", "content": "prior turn"},
{"role": "assistant", "content": "prior reply"},
{
"role": "user",
"content": concat!(
"Filler 3 of 3: write about 150 words on an everyday object.\n",
"CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.\n\n",
"Your task is to create a detailed summary of the conversation so far, ",
"paying close attention to the user's explicit requests."
)
},
{
"role": "system",
"content": concat!(
"Concise output style is active.\n\n",
"UserPromptSubmit hook success: [TOPOLOGY] least-complex topology"
)
}
],
"system": "You are Claude Code."
}))
.unwrap();

assert!(is_compact_messages_request(&req));
}

#[test]
fn compact_message_not_detected_when_assistant_message_follows_user() {
let req: MessagesRequest = serde_json::from_value(json!({
"model": "gpt-6-astra",
"messages": [
{
"role": "user",
"content": concat!(
"CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.\n",
"Your task is to create a detailed summary of the conversation so far."
)
},
{"role": "assistant", "content": "Summary: ..."}
],
"system": "You are Claude Code."
}))
.unwrap();

assert!(!is_compact_messages_request(&req));
}

#[test]
fn compact_message_markers_must_be_in_final_user_message() {
let req: MessagesRequest = serde_json::from_value(json!({
Expand Down