From 546eed94ac73e83abea237c128e5ec05257b6372 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Sun, 23 Aug 2026 03:07:47 +0000 Subject: [PATCH] Require content kinds for extension prompt fragments (#40177) ## What changed - Require each `PromptFragment` to include a producer-owned `ContentItemKind`. - Re-export `ContentItemKind` from `codex_extension_api` and assign stable kinds to skills, memories, examples, and test contributors. - Limit prompt slots to developer policy and developer capabilities, and combine contributed fragments into a single developer update. ## Testing - Update extension registry tests to verify fragment ordering with the new content-kind metadata. GitOrigin-RevId: ef8624a9b4ae9dce025bca9c0954d91dd8075e85 --- codex-rs/core/src/guardian/tests.rs | 3 + codex-rs/core/src/session/mod.rs | 66 ++----------------- codex-rs/core/src/session/tests.rs | 2 + .../shared_state_extension.rs | 3 + .../extension-api/src/contributors/prompt.rs | 26 ++++---- codex-rs/ext/extension-api/src/lib.rs | 1 + codex-rs/ext/extension-api/tests/registry.rs | 33 +++++++--- codex-rs/ext/memories/src/extension.rs | 8 ++- codex-rs/ext/skills/src/extension.rs | 8 ++- 9 files changed, 67 insertions(+), 83 deletions(-) diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index a15c306a9d..af5964ad88 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -128,6 +128,9 @@ impl codex_extension_api::ContextContributor for GuardianMemoryContextProbe { { vec![codex_extension_api::PromptFragment::developer_policy( GUARDIAN_MEMORY_CONTEXT_PROBE, + codex_extension_api::ContentItemKind( + "guardian.memory_context_probe".to_string(), + ), )] } else { Vec::new() diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 50fe4b9df6..28567c880f 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -62,8 +62,6 @@ use codex_execpolicy::prefix_rule_migration; use codex_extension_api::ConversationHistorySnapshot; use codex_extension_api::ExtensionDataInit; use codex_extension_api::LoadedUserInstructions; -use codex_extension_api::PromptFragment; -use codex_extension_api::PromptSlot; use codex_extension_api::TurnContextContributionInput; use codex_features::FEATURES; use codex_features::Feature; @@ -999,25 +997,6 @@ async fn thread_title_from_thread_store( (!title.is_empty() && thread.preview.trim() != title).then(|| title.to_string()) } -fn push_prompt_fragment( - fragment: PromptFragment, - developer_sections: &mut Vec, - contextual_user_sections: &mut Vec, - separate_developer_sections: &mut Vec, -) { - match fragment.slot() { - PromptSlot::DeveloperPolicy | PromptSlot::DeveloperCapabilities => { - developer_sections.push(fragment.text().to_string()); - } - PromptSlot::ContextualUser => { - contextual_user_sections.push(fragment.text().to_string()); - } - PromptSlot::SeparateDeveloper => { - separate_developer_sections.push(fragment.text().to_string()); - } - } -} - impl Session { pub(crate) async fn app_server_client_metadata(&self) -> AppServerClientMetadata { let state = self.state.lock().await; @@ -3486,8 +3465,6 @@ impl Session { ) -> Vec { let turn_context = step_context.turn.as_ref(); let mut developer_sections = Vec::new(); - let mut contextual_user_sections = Vec::new(); - let mut separate_developer_sections = Vec::new(); let context_contributors = self.services.extensions.context_contributors().to_vec(); for contributor in &context_contributors { @@ -3502,34 +3479,13 @@ impl Session { }) .await { - push_prompt_fragment( - fragment, - &mut developer_sections, - &mut contextual_user_sections, - &mut separate_developer_sections, - ); + developer_sections.push(fragment.text().to_string()); } } - let mut items = Vec::with_capacity(3); - if let Some(developer_message) = - crate::context_manager::updates::build_developer_update_item(developer_sections) - { - items.push(developer_message); - } - for section in separate_developer_sections { - if let Some(developer_message) = - crate::context_manager::updates::build_developer_update_item(vec![section]) - { - items.push(developer_message); - } - } - if let Some(contextual_user_message) = - crate::context_manager::updates::build_contextual_user_message(contextual_user_sections) - { - items.push(contextual_user_message); - } - items + crate::context_manager::updates::build_developer_update_item(developer_sections) + .into_iter() + .collect() } pub(crate) async fn build_initial_context_with_world_state( @@ -3598,12 +3554,7 @@ impl Session { ) .await { - push_prompt_fragment( - fragment, - &mut developer_sections, - &mut contextual_user_sections, - &mut separate_developer_sections, - ); + developer_sections.push(fragment.text().to_string()); } } for contributor in &context_contributors { @@ -3618,12 +3569,7 @@ impl Session { }) .await { - push_prompt_fragment( - fragment, - &mut developer_sections, - &mut contextual_user_sections, - &mut separate_developer_sections, - ); + developer_sections.push(fragment.text().to_string()); } } // This is full-context metadata. Steady-state context diffs should not re-emit it. diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 5e16b4a604..a2334f863b 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -9239,6 +9239,7 @@ impl codex_extension_api::ContextContributor for PromptExtensionTestContributor .then(|| { codex_extension_api::PromptFragment::developer_policy( "prompt extension enabled", + codex_extension_api::ContentItemKind("test.prompt_extension".to_string()), ) }) .into_iter() @@ -9271,6 +9272,7 @@ impl codex_extension_api::ContextContributor for TurnContextExtensionTestContrib .then(|| { codex_extension_api::PromptFragment::developer_policy( "turn context extension enabled", + codex_extension_api::ContentItemKind("test.turn_context".to_string()), ) }) .into_iter() diff --git a/codex-rs/ext/extension-api/examples/enabled_extensions/shared_state_extension.rs b/codex-rs/ext/extension-api/examples/enabled_extensions/shared_state_extension.rs index 414a67215b..0657f79940 100644 --- a/codex-rs/ext/extension-api/examples/enabled_extensions/shared_state_extension.rs +++ b/codex-rs/ext/extension-api/examples/enabled_extensions/shared_state_extension.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; +use codex_extension_api::ContentItemKind; use codex_extension_api::ContextContributor; use codex_extension_api::ExtensionData; use codex_extension_api::ExtensionRegistryBuilder; @@ -28,6 +29,7 @@ impl ContextContributor for StyleContributor { vec![PromptFragment::developer_policy( "Prefer short answers unless the user asks for detail.", + ContentItemKind("example.style_instructions".to_string()), )] }) } @@ -48,6 +50,7 @@ impl ContextContributor for UsageContributor { vec![PromptFragment::developer_capability( "This extension can contribute more than one prompt fragment.", + ContentItemKind("example.usage_instructions".to_string()), )] }) } diff --git a/codex-rs/ext/extension-api/src/contributors/prompt.rs b/codex-rs/ext/extension-api/src/contributors/prompt.rs index 1e29b72f9e..dd7c45e70e 100644 --- a/codex-rs/ext/extension-api/src/contributors/prompt.rs +++ b/codex-rs/ext/extension-api/src/contributors/prompt.rs @@ -1,41 +1,38 @@ // All this file should be replaced by the existing fragment implementation ofc +use codex_protocol::models::ContentItemKind; + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum PromptSlot { DeveloperPolicy, DeveloperCapabilities, - ContextualUser, - SeparateDeveloper, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct PromptFragment { slot: PromptSlot, text: String, + content_kind: ContentItemKind, } impl PromptFragment { /// Creates a prompt fragment for the given slot. - pub fn new(slot: PromptSlot, text: impl Into) -> Self { + pub fn new(slot: PromptSlot, text: impl Into, content_kind: ContentItemKind) -> Self { Self { slot, text: text.into(), + content_kind, } } /// Creates a developer-policy prompt fragment. - pub fn developer_policy(text: impl Into) -> Self { - Self::new(PromptSlot::DeveloperPolicy, text) + pub fn developer_policy(text: impl Into, content_kind: ContentItemKind) -> Self { + Self::new(PromptSlot::DeveloperPolicy, text, content_kind) } /// Creates a developer-capabilities prompt fragment. - pub fn developer_capability(text: impl Into) -> Self { - Self::new(PromptSlot::DeveloperCapabilities, text) - } - - /// Creates a separate top-level developer prompt fragment. - pub fn separate_developer(text: impl Into) -> Self { - Self::new(PromptSlot::SeparateDeveloper, text) + pub fn developer_capability(text: impl Into, content_kind: ContentItemKind) -> Self { + Self::new(PromptSlot::DeveloperCapabilities, text, content_kind) } /// Returns the target prompt slot. @@ -47,4 +44,9 @@ impl PromptFragment { pub fn text(&self) -> &str { &self.text } + + /// Returns the producer-owned classification of the model-visible text. + pub fn content_kind(&self) -> &ContentItemKind { + &self.content_kind + } } diff --git a/codex-rs/ext/extension-api/src/lib.rs b/codex-rs/ext/extension-api/src/lib.rs index ae67c1c366..92e359aa25 100644 --- a/codex-rs/ext/extension-api/src/lib.rs +++ b/codex-rs/ext/extension-api/src/lib.rs @@ -17,6 +17,7 @@ pub use capabilities::NoopResponseItemInjector; pub use capabilities::ResponseItemInjectionFuture; pub use capabilities::ResponseItemInjector; pub use codex_context_fragments::ContextualUserFragment; +pub use codex_protocol::models::ContentItemKind; pub use codex_protocol::models::ResponseItem; pub use codex_protocol::security_risk::SecurityRiskScore; pub use codex_tools::ConversationHistory; diff --git a/codex-rs/ext/extension-api/tests/registry.rs b/codex-rs/ext/extension-api/tests/registry.rs index 129e890589..05fa6f0de2 100644 --- a/codex-rs/ext/extension-api/tests/registry.rs +++ b/codex-rs/ext/extension-api/tests/registry.rs @@ -5,6 +5,7 @@ use std::sync::Mutex; use codex_extension_api::ApprovalReviewContributor; use codex_extension_api::ConfigContributor; +use codex_extension_api::ContentItemKind; use codex_extension_api::ContextContributor; use codex_extension_api::ContextualUserFragment; use codex_extension_api::ExtensionData; @@ -16,7 +17,6 @@ use codex_extension_api::ExtensionRegistryBuilder; use codex_extension_api::ExtensionWarning; use codex_extension_api::McpServerContributionContext; use codex_extension_api::PromptFragment; -use codex_extension_api::PromptSlot; use codex_extension_api::SkillInvocationContributor; use codex_extension_api::ThreadLifecycleContributor; use codex_extension_api::TokenUsageContributor; @@ -194,6 +194,7 @@ impl ContextContributor for NamedContextContributor { ) -> ExtensionFuture<'a, Vec> { Box::pin(std::future::ready(vec![PromptFragment::developer_policy( self.0, + ContentItemKind("test.thread_context".to_string()), )])) } } @@ -205,10 +206,12 @@ impl ContextContributor for NamedTurnContextContributor { &'a self, _input: TurnContextContributionInput<'a>, ) -> ExtensionFuture<'a, Vec> { - Box::pin(std::future::ready(vec![PromptFragment::new( - PromptSlot::ContextualUser, - self.0, - )])) + Box::pin(std::future::ready(vec![ + PromptFragment::developer_capability( + self.0, + ContentItemKind("test.turn_context".to_string()), + ), + ])) } } @@ -289,10 +292,22 @@ async fn contributors_preserve_registration_order() { assert_eq!( fragments, vec![ - PromptFragment::developer_policy("first"), - PromptFragment::developer_policy("second"), - PromptFragment::new(PromptSlot::ContextualUser, "turn-first"), - PromptFragment::new(PromptSlot::ContextualUser, "turn-second"), + PromptFragment::developer_policy( + "first", + ContentItemKind("test.thread_context".to_string()), + ), + PromptFragment::developer_policy( + "second", + ContentItemKind("test.thread_context".to_string()), + ), + PromptFragment::developer_capability( + "turn-first", + ContentItemKind("test.turn_context".to_string()), + ), + PromptFragment::developer_capability( + "turn-second", + ContentItemKind("test.turn_context".to_string()), + ), ] ); assert_eq!( diff --git a/codex-rs/ext/memories/src/extension.rs b/codex-rs/ext/memories/src/extension.rs index 464ce24576..0904604079 100644 --- a/codex-rs/ext/memories/src/extension.rs +++ b/codex-rs/ext/memories/src/extension.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use codex_core::config::Config; use codex_extension_api::ConfigContributor; +use codex_extension_api::ContentItemKind; use codex_extension_api::ContextContributor; use codex_extension_api::ExtensionData; use codex_extension_api::ExtensionFuture; @@ -63,7 +64,12 @@ impl ContextContributor for MemoriesExtension { build_memory_tool_developer_instructions(&config.codex_home) .await - .map(PromptFragment::developer_policy) + .map(|instructions| { + PromptFragment::developer_policy( + instructions, + ContentItemKind("memories.instructions".to_string()), + ) + }) .into_iter() .collect() }) diff --git a/codex-rs/ext/skills/src/extension.rs b/codex-rs/ext/skills/src/extension.rs index 0ac722182d..9041951b72 100644 --- a/codex-rs/ext/skills/src/extension.rs +++ b/codex-rs/ext/skills/src/extension.rs @@ -9,6 +9,7 @@ use codex_exec_server::FileSystemSandboxContext; use codex_exec_server::LOCAL_ENVIRONMENT_ID; use codex_exec_server::ResolvedSelectedCapabilityRoot; use codex_extension_api::ConfigContributor; +use codex_extension_api::ContentItemKind; use codex_extension_api::ContextContributor; use codex_extension_api::ContextualUserFragment; use codex_extension_api::ExtensionData; @@ -236,7 +237,12 @@ where } rendered .fragment - .map(|fragment| PromptFragment::developer_capability(fragment.render())) + .map(|fragment| { + PromptFragment::developer_capability( + fragment.render(), + ContentItemKind("skills.catalog".to_string()), + ) + }) .into_iter() .collect() })