mirror of
https://github.com/openai/codex.git
synced 2026-09-05 15:18:41 +00:00
Register hidden context markers from extensions
This commit is contained in:
2
codex-rs/Cargo.lock
generated
2
codex-rs/Cargo.lock
generated
@@ -2848,6 +2848,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"codex-protocol",
|
||||
"codex-tools",
|
||||
"inventory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2976,6 +2977,7 @@ dependencies = [
|
||||
"codex-state",
|
||||
"codex-tools",
|
||||
"codex-utils-template",
|
||||
"inventory",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -6,7 +6,6 @@ use super::AdditionalContextUserFragment;
|
||||
use super::EnvironmentContext;
|
||||
use super::FragmentRegistration;
|
||||
use super::FragmentRegistrationProxy;
|
||||
use super::GoalContext;
|
||||
use super::LegacyApplyPatchExecCommandWarning;
|
||||
use super::LegacyModelMismatchWarning;
|
||||
use super::LegacyUnifiedExecProcessLimitWarning;
|
||||
@@ -22,8 +21,6 @@ static ENVIRONMENT_CONTEXT_REGISTRATION: FragmentRegistrationProxy<EnvironmentCo
|
||||
FragmentRegistrationProxy::new();
|
||||
static ADDITIONAL_CONTEXT_REGISTRATION: FragmentRegistrationProxy<AdditionalContextUserFragment> =
|
||||
FragmentRegistrationProxy::new();
|
||||
static GOAL_CONTEXT_REGISTRATION: FragmentRegistrationProxy<GoalContext> =
|
||||
FragmentRegistrationProxy::new();
|
||||
static SKILL_INSTRUCTIONS_REGISTRATION: FragmentRegistrationProxy<SkillInstructions> =
|
||||
FragmentRegistrationProxy::new();
|
||||
static USER_SHELL_COMMAND_REGISTRATION: FragmentRegistrationProxy<UserShellCommand> =
|
||||
@@ -46,7 +43,6 @@ static CONTEXTUAL_USER_FRAGMENTS: &[&dyn FragmentRegistration] = &[
|
||||
&USER_INSTRUCTIONS_REGISTRATION,
|
||||
&ENVIRONMENT_CONTEXT_REGISTRATION,
|
||||
&ADDITIONAL_CONTEXT_REGISTRATION,
|
||||
&GOAL_CONTEXT_REGISTRATION,
|
||||
&SKILL_INSTRUCTIONS_REGISTRATION,
|
||||
&USER_SHELL_COMMAND_REGISTRATION,
|
||||
&TURN_ABORTED_REGISTRATION,
|
||||
@@ -62,11 +58,17 @@ fn is_standard_contextual_user_text(text: &str) -> bool {
|
||||
.any(|fragment| fragment.matches_text(text))
|
||||
}
|
||||
|
||||
fn is_registered_extension_context_text(text: &str) -> bool {
|
||||
codex_extension_api::registered_hidden_context_markers().any(|marker| marker.matches_text(text))
|
||||
}
|
||||
|
||||
pub(crate) fn is_contextual_user_fragment(content_item: &ContentItem) -> bool {
|
||||
let ContentItem::InputText { text } = content_item else {
|
||||
return false;
|
||||
};
|
||||
parse_hook_prompt_fragment(text).is_some() || is_standard_contextual_user_text(text)
|
||||
parse_hook_prompt_fragment(text).is_some()
|
||||
|| is_standard_contextual_user_text(text)
|
||||
|| is_registered_extension_context_text(text)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_visible_hook_prompt_message(
|
||||
@@ -83,7 +85,7 @@ pub(crate) fn parse_visible_hook_prompt_message(
|
||||
fragments.push(fragment);
|
||||
continue;
|
||||
}
|
||||
if is_standard_contextual_user_text(text) {
|
||||
if is_standard_contextual_user_text(text) || is_registered_extension_context_text(text) {
|
||||
continue;
|
||||
}
|
||||
return None;
|
||||
|
||||
@@ -14,9 +14,10 @@ fn detects_environment_context_fragment() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_legacy_goal_context_fragment() {
|
||||
assert!(is_contextual_user_fragment(&ContentItem::InputText {
|
||||
text: "<goal_context>\nlegacy hidden goal prompt\n</goal_context>".to_string(),
|
||||
fn does_not_hard_code_extension_context_fragments() {
|
||||
assert!(!is_contextual_user_fragment(&ContentItem::InputText {
|
||||
text: "<example_extension_context>\nextension-owned hidden prompt\n</example_extension_context>"
|
||||
.to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
//! Legacy hidden user-context marker for goal steering prompts.
|
||||
//!
|
||||
//! The goal implementation now lives in `codex-goal-extension`, but existing
|
||||
//! rollouts can still contain this marker. Core keeps recognizing it so old
|
||||
//! hidden goal prompts do not resurface as normal user messages after resume.
|
||||
|
||||
use super::ContextualUserFragment;
|
||||
|
||||
/// Marker-only registration for hidden goal steering context.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct GoalContext;
|
||||
|
||||
impl ContextualUserFragment for GoalContext {
|
||||
fn role() -> &'static str {
|
||||
"user"
|
||||
}
|
||||
|
||||
fn markers(&self) -> (&'static str, &'static str) {
|
||||
Self::type_markers()
|
||||
}
|
||||
|
||||
fn body(&self) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn type_markers() -> (&'static str, &'static str) {
|
||||
("<goal_context>", "</goal_context>")
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ mod contextual_user_message;
|
||||
mod environment_context;
|
||||
mod fragment;
|
||||
mod fragments;
|
||||
mod goal_context;
|
||||
mod guardian_followup_review_reminder;
|
||||
mod hook_additional_context;
|
||||
mod image_generation_instructions;
|
||||
@@ -43,7 +42,6 @@ pub(crate) use fragment::FragmentRegistration;
|
||||
pub(crate) use fragment::FragmentRegistrationProxy;
|
||||
pub(crate) use fragments::AdditionalContextDeveloperFragment;
|
||||
pub(crate) use fragments::AdditionalContextUserFragment;
|
||||
pub(crate) use goal_context::GoalContext;
|
||||
pub(crate) use guardian_followup_review_reminder::GuardianFollowupReviewReminder;
|
||||
pub(crate) use hook_additional_context::HookAdditionalContext;
|
||||
pub(crate) use image_generation_instructions::ImageGenerationInstructions;
|
||||
|
||||
@@ -252,7 +252,7 @@ mod tests {
|
||||
let original_len = prompt.len();
|
||||
|
||||
let request = ThreadIdleRequest::new(HiddenContext::new(
|
||||
HiddenContextMarker::new("<goal_context>", "</goal_context>"),
|
||||
HiddenContextMarker::new("<test_context>", "</test_context>"),
|
||||
prompt,
|
||||
));
|
||||
let TurnInput::ResponseInputItem(ResponseInputItem::Message { content, .. }) =
|
||||
@@ -264,7 +264,7 @@ mod tests {
|
||||
panic!("expected one text content item");
|
||||
};
|
||||
|
||||
assert!(text.starts_with("<goal_context>"));
|
||||
assert!(text.starts_with("<test_context>"));
|
||||
assert!(text.contains("start"));
|
||||
assert!(text.contains("end"));
|
||||
assert!(text.len() < original_len);
|
||||
|
||||
@@ -17,3 +17,4 @@ workspace = true
|
||||
async-trait = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-tools = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
|
||||
@@ -18,6 +18,40 @@ impl HiddenContextMarker {
|
||||
end_marker,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true when text is wrapped in this marker pair.
|
||||
pub fn matches_text(self, text: &str) -> bool {
|
||||
if self.start_marker.is_empty() || self.end_marker.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let trimmed = text.trim_start();
|
||||
let starts_with_marker = trimmed
|
||||
.get(..self.start_marker.len())
|
||||
.is_some_and(|candidate| candidate.eq_ignore_ascii_case(self.start_marker));
|
||||
let trimmed = trimmed.trim_end();
|
||||
let ends_with_marker = trimmed
|
||||
.get(trimmed.len().saturating_sub(self.end_marker.len())..)
|
||||
.is_some_and(|candidate| candidate.eq_ignore_ascii_case(self.end_marker));
|
||||
starts_with_marker && ends_with_marker
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile-time registration for extension-owned hidden context markers.
|
||||
///
|
||||
/// Extensions use this to reserve their hidden context wire tags without adding
|
||||
/// feature-specific tags to core parsing code.
|
||||
pub struct HiddenContextMarkerRegistration {
|
||||
pub marker: HiddenContextMarker,
|
||||
}
|
||||
|
||||
inventory::collect!(HiddenContextMarkerRegistration);
|
||||
|
||||
/// Returns all hidden context markers registered by linked extensions.
|
||||
pub fn registered_hidden_context_markers() -> impl Iterator<Item = HiddenContextMarker> {
|
||||
inventory::iter::<HiddenContextMarkerRegistration>
|
||||
.into_iter()
|
||||
.map(|registration| registration.marker)
|
||||
}
|
||||
|
||||
/// Extension-owned hidden context body with the marker pair used to wrap it.
|
||||
|
||||
@@ -27,6 +27,8 @@ pub use codex_tools::parse_tool_input_schema;
|
||||
pub use codex_tools::parse_tool_input_schema_without_compaction;
|
||||
pub use context::HiddenContext;
|
||||
pub use context::HiddenContextMarker;
|
||||
pub use context::HiddenContextMarkerRegistration;
|
||||
pub use context::registered_hidden_context_markers;
|
||||
pub use contributors::ApprovalReviewContributor;
|
||||
pub use contributors::ConfigContributor;
|
||||
pub use contributors::ContextContributor;
|
||||
|
||||
@@ -21,6 +21,7 @@ codex-protocol = { workspace = true }
|
||||
codex-state = { workspace = true }
|
||||
codex-tools = { workspace = true }
|
||||
codex-utils-template = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use codex_extension_api::HiddenContext;
|
||||
use codex_extension_api::HiddenContextMarker;
|
||||
use codex_extension_api::HiddenContextMarkerRegistration;
|
||||
use codex_extension_api::ThreadIdleRequest;
|
||||
use codex_protocol::protocol::ThreadGoal;
|
||||
use codex_utils_template::Template;
|
||||
@@ -8,6 +9,12 @@ use std::sync::LazyLock;
|
||||
const GOAL_CONTEXT_MARKER: HiddenContextMarker =
|
||||
HiddenContextMarker::new("<goal_context>", "</goal_context>");
|
||||
|
||||
inventory::submit! {
|
||||
HiddenContextMarkerRegistration {
|
||||
marker: GOAL_CONTEXT_MARKER,
|
||||
}
|
||||
}
|
||||
|
||||
static CONTINUATION_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {
|
||||
parse_embedded_template(
|
||||
include_str!("../templates/goals/continuation.md"),
|
||||
|
||||
@@ -42,6 +42,15 @@ use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn goal_context_marker_is_registered_as_hidden_context() {
|
||||
assert!(
|
||||
codex_extension_api::registered_hidden_context_markers().any(|marker| {
|
||||
marker.matches_text("<goal_context>\nhidden goal prompt\n</goal_context>")
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn installed_goal_tools_create_goal_and_fill_empty_preview() -> anyhow::Result<()> {
|
||||
let runtime = test_runtime().await?;
|
||||
|
||||
Reference in New Issue
Block a user