feat: add remote compaction v2 Responses client path (#20773)

## Why

This adds the `remote_compaction_v2` client path so remote compaction
can run through the normal Responses stream and install a
`context_compaction` item that trigger a compaction.

The goal is to migrate some of the compaction logic on the client side

We keeps the v2 transport behind a feature flag while letting follow-up
requests reuse the compacted context instead of falling back to the
legacy compaction item shape.

## What changed

- add `ResponseItem::ContextCompaction` and refresh the generated
app-server / schema / TypeScript fixtures that expose response items on
the wire
- add `core/src/compact_remote_v2.rs` to send compaction through the
standard streamed Responses client, require exactly one
`context_compaction` output item, and install that item into compacted
history
- route manual compact and auto-compaction through the v2 path when
`remote_compaction_v2` is enabled, while keeping the existing remote
compaction path as the fallback
- preserve the new item type across history retention, follow-up request
construction, telemetry, rollout persistence, and rollout-trace
normalization
- add targeted coverage for the feature flag, `context_compaction`
serialization, rollout-trace normalization, and remote-compaction
follow-up behavior

## Verification

- added protocol tests for `context_compaction`
serialization/deserialization in `protocol/src/models.rs`
- added rollout-trace coverage for `context_compaction` normalization in
`rollout-trace/src/reducer/conversation_tests.rs`
- added remote compaction integration coverage for v2 follow-up reuse
and mixed compaction output streams in
`core/tests/suite/compact_remote.rs`

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
jif-oai
2026-05-04 14:15:01 +02:00
committed by GitHub
co-authored by Codex
parent d013155f40
commit d927f61208
27 changed files with 991 additions and 43 deletions
+35
View File
@@ -881,6 +881,11 @@ pub enum ResponseItem {
},
#[serde(alias = "compaction_summary")]
Compaction { encrypted_content: String },
ContextCompaction {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
encrypted_content: Option<String>,
},
#[serde(other)]
Other,
}
@@ -2403,6 +2408,36 @@ mod tests {
Ok(())
}
#[test]
fn deserializes_context_compaction() -> Result<()> {
let json = r#"{"type":"context_compaction","encrypted_content":"abc"}"#;
let item: ResponseItem = serde_json::from_str(json)?;
assert_eq!(
item,
ResponseItem::ContextCompaction {
encrypted_content: Some("abc".into()),
}
);
Ok(())
}
#[test]
fn serializes_context_compaction_trigger_without_payload() -> Result<()> {
let item = ResponseItem::ContextCompaction {
encrypted_content: None,
};
assert_eq!(
serde_json::to_value(item)?,
serde_json::json!({
"type": "context_compaction",
})
);
Ok(())
}
#[test]
fn deserializes_legacy_ghost_snapshot_as_other() -> Result<()> {
let json = r#"{