Support window prefixes for extension world-state sections (#57276)

## What changed

Add `WorldStateSectionContribution::with_window_prefix()` to render extension developer fragments as separate messages before retained history when building a context window, including after compaction. Updates within an existing window still append to history. Ignore fragments with other roles for sections that opt in.

## Testing

Add a unit test for prefix placement and duplicate avoidance, plus local and remote compaction scenarios covering live updates, persisted replacement history, and rejection of non-developer fragments.

GitOrigin-RevId: 8621457c6eec17f1585ca8e6fa154b1bf3257330
This commit is contained in:
Francis Chalissery
2026-10-26 21:46:26 +00:00
committed by copyberry
parent 166aed9854
commit d416d8a0ff
7 changed files with 928 additions and 2 deletions
+49
View File
@@ -271,6 +271,55 @@ fn compacted_user_message<'a>(text: &str, original: &'a ResponseItem) -> Compact
}
}
#[tokio::test]
async fn extension_window_prefix_precedes_retained_turns_and_is_not_repeated() {
use codex_extension_api::PreviousWorldStateSection;
use codex_extension_api::RenderedWorldStateFragment;
use codex_extension_api::WorldStateSectionContribution;
let (session, turn) = crate::session::tests::make_session_and_context().await;
let step = StepContext::for_test(Arc::new(turn));
let mut world = WorldState::default();
world.add_extension_section(
WorldStateSectionContribution::new("example", |previous| {
let fragment = (!matches!(previous, PreviousWorldStateSection::Known(_))).then(|| {
RenderedWorldStateFragment::new(
"developer",
("<example>", "</example>"),
codex_protocol::models::input_text("active policy"),
)
});
(Some(json!("active policy")), fragment)
})
.with_window_prefix()
.with_retained_fragment_matcher(|role, text| {
role == "developer" && text == "<example>active policy</example>"
}),
);
let retained = vec![user_message("earlier turn"), user_message("latest turn")];
let (replacement, snapshot) =
build_compaction_replacement_history(&session, &step, &world, annotated(retained.clone()))
.await;
let items = raw(replacement);
let ResponseItem::Message { role, content, .. } = &items[0] else {
panic!("expected a separate developer prefix");
};
assert_eq!(role, "developer");
assert_eq!(
content,
&[codex_protocol::models::input_text(
"<example>active policy</example>"
)]
);
assert_eq!(items[1], retained[0]);
assert!(
world
.render_history_diff(Some(&snapshot), &items)
.1
.is_empty()
);
}
#[test]
fn content_items_to_text_joins_non_empty_segments() {
let items = vec![
+11 -2
View File
@@ -247,11 +247,20 @@ impl ErasedWorldStateSection for ExtensionWorldStateSection {
});
let updates = fragment
.into_iter()
.filter(|fragment| !self.0.is_window_prefix() || fragment.role() == "developer")
.map(|fragment| {
WorldStateUpdate::fragment(WorldStateContextFragment {
let update = WorldStateUpdate::fragment(WorldStateContextFragment {
fragment,
content_kind: ContentItemKind(format!("{}.instructions", self.0.id())),
})
});
if self.0.is_window_prefix() {
WorldStateUpdate {
placement: Placement::Prefix,
..update
}
} else {
update
}
})
.collect();
(snapshot, updates)