Support typed content in contextual prompt fragments (#54776)

## Why

Contextual fragments and extension-owned world-state sections currently return strings, limiting them to text content.

## What changed

- Return `ContentItem` from fragment bodies and rendering, preserving classification and attribution. Apply text markers only to `InputText`; pass other content through unchanged.
- Allow extension prompt and world-state contributions to carry typed content, and preserve media in context-window hints.
- Hash non-text world-state fragments by their serialized content and role while retaining the existing text hashing behavior.
- Add shared content-to-tool-output conversion that preserves image references, image detail, and audio.

## Testing

Add coverage for typed world-state hashing and preservation of image content and attribution. Expand code-mode output tests to cover input text, output text, file-backed and inline images, and audio.

GitOrigin-RevId: 2ed00d466a3f169b151202f3eef71e667ea64be1
This commit is contained in:
pakrym-oai
2026-10-17 04:02:21 +00:00
committed by copyberry
parent 3b8f43a06b
commit ce0323f2f2
162 changed files with 1109 additions and 570 deletions
@@ -11,7 +11,7 @@ use codex_protocol::models::ContentItemMetadata;
pub enum PromptSlot {
DeveloperPolicy,
DeveloperCapabilities,
/// Text inside the context-window message, supplied by `contribute_thread_context`.
/// Content inside the context-window message, supplied by `contribute_thread_context`.
ContextWindow,
}
@@ -22,16 +22,12 @@ pub struct PromptFragment {
}
impl PromptFragment {
/// Places a contextual fragment in the prompt, preserving its text, kind, and source.
/// Places a contextual fragment in the prompt, preserving its content, kind, and source.
/// The slot determines placement within the developer message, regardless of its original role.
pub fn from_fragment(slot: PromptSlot, fragment: &impl ContextualUserFragment) -> Self {
Self {
slot,
content: AnnotatedContent::text(
fragment.render(),
fragment.content_kind(),
fragment.content_metadata(),
),
content: fragment.render_fragment().into_parts().1,
}
}
@@ -75,15 +71,12 @@ impl PromptFragment {
self.slot
}
/// Returns the model-visible text.
pub fn text(&self) -> &str {
let ContentItem::InputText { text } = self.content.content() else {
unreachable!("prompt fragments contain text");
};
text
/// Returns the model-visible content.
pub fn content(&self) -> &ContentItem {
self.content.content()
}
/// Returns the producer-owned classification of the model-visible text.
/// Returns the producer-owned classification of the model-visible content.
pub fn content_kind(&self) -> &ContentItemKind {
self.content.kind()
}
@@ -4,6 +4,7 @@ use codex_exec_server_protocol::ExecutorCapabilityDiscoverySnapshot;
use codex_exec_server_protocol::ExecutorCapabilityDiscoveryV2Snapshot;
use codex_protocol::ThreadId;
use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ContentItemMetadata;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::protocol::TurnEnvironmentSelection;
@@ -45,12 +46,15 @@ pub enum PreviousWorldStateSection<'a> {
Known(&'a Value),
}
/// Plain model-visible data rendered by an extension-owned World State section.
#[derive(Clone, Debug, PartialEq, Eq)]
/// Model-visible content rendered by an extension-owned World State section.
/// Markers apply only to `InputText` bodies.
/// The producer owns size bounds for the content; rendering does not truncate it.
/// Non-text user-role bodies are currently classified as ordinary user media.
#[derive(Clone, Debug, PartialEq)]
pub struct RenderedWorldStateFragment {
role: &'static str,
markers: (&'static str, &'static str),
body: String,
body: ContentItem,
metadata: ContentItemMetadata,
}
@@ -58,12 +62,12 @@ impl RenderedWorldStateFragment {
pub fn new(
role: &'static str,
markers: (&'static str, &'static str),
body: impl Into<String>,
body: ContentItem,
) -> Self {
Self {
role,
markers,
body: body.into(),
body,
metadata: ContentItemMetadata::harness(),
}
}
@@ -86,7 +90,7 @@ impl RenderedWorldStateFragment {
self.markers
}
pub fn body(&self) -> &str {
pub fn body(&self) -> &ContentItem {
&self.body
}
}
+2
View File
@@ -22,8 +22,10 @@ pub use capabilities::ResponseItemInjector;
pub use codex_context_fragments::ContextualUserFragment;
pub use codex_mcp::McpProtocolMode;
pub use codex_mcp::ToolInfo as McpToolInfo;
pub use codex_protocol::models::ContentItem;
pub use codex_protocol::models::ContentItemKind;
pub use codex_protocol::models::ResponseItem;
pub use codex_protocol::models::input_text;
pub use codex_protocol::security_risk::SecurityRiskScore;
pub use codex_tools::ConversationHistory;
pub use codex_tools::ExtensionTurnItem;