mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
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
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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<String>,
|
||||
contextual_user_sections: &mut Vec<String>,
|
||||
separate_developer_sections: &mut Vec<String>,
|
||||
) {
|
||||
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<ResponseItem> {
|
||||
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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()),
|
||||
)]
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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<String>) -> Self {
|
||||
pub fn new(slot: PromptSlot, text: impl Into<String>, content_kind: ContentItemKind) -> Self {
|
||||
Self {
|
||||
slot,
|
||||
text: text.into(),
|
||||
content_kind,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a developer-policy prompt fragment.
|
||||
pub fn developer_policy(text: impl Into<String>) -> Self {
|
||||
Self::new(PromptSlot::DeveloperPolicy, text)
|
||||
pub fn developer_policy(text: impl Into<String>, content_kind: ContentItemKind) -> Self {
|
||||
Self::new(PromptSlot::DeveloperPolicy, text, content_kind)
|
||||
}
|
||||
|
||||
/// Creates a developer-capabilities prompt fragment.
|
||||
pub fn developer_capability(text: impl Into<String>) -> Self {
|
||||
Self::new(PromptSlot::DeveloperCapabilities, text)
|
||||
}
|
||||
|
||||
/// Creates a separate top-level developer prompt fragment.
|
||||
pub fn separate_developer(text: impl Into<String>) -> Self {
|
||||
Self::new(PromptSlot::SeparateDeveloper, text)
|
||||
pub fn developer_capability(text: impl Into<String>, 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<PromptFragment>> {
|
||||
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<PromptFragment>> {
|
||||
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!(
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user