Files
codex/codex-rs/core/src/context_manager/updates.rs
T
pakrym-oai a70974c1a0 Preserve developer instruction annotations in subagent forks (#40297)
## What changed

- Add a dedicated contextual fragment for developer instructions that emits the
  `generic.developer_instructions` content kind.
- Use the fragment when adding child-specific developer instructions to a
  full-history subagent fork, preserving their classification and ensuring they
  reach the child exactly once without appearing in the parent request.

## Testing

- Extend full-history subagent coverage to verify the child-only instructions'
  annotation, placement, and count.

GitOrigin-RevId: 82589d311535225fed76d500a19458fda72f40f2
2026-08-23 23:56:23 +00:00

61 lines
2.0 KiB
Rust

use crate::context::ContextualUserFragment;
use codex_context_fragments::RenderedFragment;
use codex_protocol::models::InternalChatMessageMetadataPassthrough;
use codex_protocol::models::ResponseItem;
#[derive(Clone, Copy, PartialEq, Eq)]
enum MessageGroup {
Standalone,
Mergeable,
}
pub(crate) fn build_rendered_message(fragments: Vec<RenderedFragment>) -> Option<ResponseItem> {
let role = fragments.first()?.role();
debug_assert!(fragments.iter().all(|fragment| fragment.role() == role));
let (content, content_item_kinds): (Vec<_>, Vec<_>) = fragments
.into_iter()
.map(|fragment| fragment.into_parts().1.into_parts())
.unzip();
Some(ResponseItem::Message {
id: None,
role: role.to_string(),
content,
phase: None,
internal_chat_message_metadata_passthrough: Some(InternalChatMessageMetadataPassthrough {
content_item_kinds: Some(content_item_kinds),
..Default::default()
}),
})
}
pub(crate) fn merge_contextual_fragments(
fragments: Vec<Box<dyn ContextualUserFragment>>,
) -> Vec<ResponseItem> {
let mut messages: Vec<(&str, MessageGroup, Vec<RenderedFragment>)> =
Vec::with_capacity(fragments.len());
for fragment in fragments {
let group = if fragment.requires_separate_message() {
MessageGroup::Standalone
} else {
MessageGroup::Mergeable
};
let rendered = fragment.render_fragment();
let role = rendered.role();
match messages.last_mut() {
Some((previous_role, previous_group, rendered_fragments))
if *previous_role == role
&& *previous_group == MessageGroup::Mergeable
&& group == MessageGroup::Mergeable =>
{
rendered_fragments.push(rendered);
}
_ => messages.push((role, group, vec![rendered])),
}
}
messages
.into_iter()
.filter_map(|(_, _, fragments)| build_rendered_message(fragments))
.collect()
}