Use generic extension context for goals

This commit is contained in:
Eric Traut
2026-05-27 13:05:34 -07:00
parent 12143a14c5
commit 2506748012
6 changed files with 17 additions and 66 deletions
@@ -1,6 +1,5 @@
use super::*;
use crate::context::ContextualUserFragment;
use crate::context::ExtensionContextTag;
use crate::context::SubagentNotification;
use codex_protocol::items::HookPromptFragment;
use codex_protocol::items::build_hook_prompt_message;
@@ -21,15 +20,6 @@ fn detects_extension_context_fragment() {
}));
}
#[test]
fn detects_goal_extension_context_fragment() {
let text = ExtensionContext::with_tag(ExtensionContextTag::GOAL, "hidden goal prompt").render();
assert!(is_contextual_user_fragment(&ContentItem::InputText {
text
}));
}
#[test]
fn detects_agents_instructions_fragment() {
assert!(is_contextual_user_fragment(&ContentItem::InputText {
+8 -41
View File
@@ -1,51 +1,23 @@
//! Hidden user-context wrappers owned by extensions.
//!
//! Extensions can use one of these host-recognized tags when they need hidden
//! model-visible context to survive history filtering without teaching core
//! about the extension's domain concepts.
//! Extensions can use this host-recognized tag when they need hidden
//! model-visible context to survive history filtering.
use super::ContextualUserFragment;
use super::fragment::matches_marked_text;
/// Host-recognized marker pair for extension-owned hidden context.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExtensionContextTag {
start_marker: &'static str,
end_marker: &'static str,
}
impl ExtensionContextTag {
pub const DEFAULT: Self = Self::new("<extension_context>", "</extension_context>");
pub const GOAL: Self = Self::new("<goal_context>", "</goal_context>");
const fn new(start_marker: &'static str, end_marker: &'static str) -> Self {
Self {
start_marker,
end_marker,
}
}
}
const EXTENSION_CONTEXT_TAGS: &[ExtensionContextTag] =
&[ExtensionContextTag::DEFAULT, ExtensionContextTag::GOAL];
const START_MARKER: &str = "<extension_context>";
const END_MARKER: &str = "</extension_context>";
/// Hidden user-context fragment for extension-owned steering prompts.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtensionContext {
tag: ExtensionContextTag,
body: String,
}
impl ExtensionContext {
pub fn new(body: impl Into<String>) -> Self {
Self::with_tag(ExtensionContextTag::DEFAULT, body)
}
pub fn with_tag(tag: ExtensionContextTag, body: impl Into<String>) -> Self {
Self {
tag,
body: body.into(),
}
Self { body: body.into() }
}
}
@@ -55,20 +27,15 @@ impl ContextualUserFragment for ExtensionContext {
}
fn markers(&self) -> (&'static str, &'static str) {
(self.tag.start_marker, self.tag.end_marker)
(START_MARKER, END_MARKER)
}
fn type_markers() -> (&'static str, &'static str) {
(
ExtensionContextTag::DEFAULT.start_marker,
ExtensionContextTag::DEFAULT.end_marker,
)
(START_MARKER, END_MARKER)
}
fn matches_text(text: &str) -> bool {
EXTENSION_CONTEXT_TAGS
.iter()
.any(|tag| matches_marked_text(tag.start_marker, tag.end_marker, text))
matches_marked_text(START_MARKER, END_MARKER, text)
}
fn body(&self) -> String {
-1
View File
@@ -39,7 +39,6 @@ pub(crate) use contextual_user_message::is_contextual_user_fragment;
pub(crate) use contextual_user_message::parse_visible_hook_prompt_message;
pub(crate) use environment_context::EnvironmentContext;
pub use extension_context::ExtensionContext;
pub use extension_context::ExtensionContextTag;
pub use fragment::ContextualUserFragment;
pub(crate) use fragment::FragmentRegistration;
pub(crate) use fragment::FragmentRegistrationProxy;
+2 -4
View File
@@ -228,8 +228,6 @@ fn idle_extension_input(prompt: String) -> TurnInput {
#[cfg(test)]
mod tests {
use crate::context::ExtensionContextTag;
use super::*;
#[test]
@@ -240,7 +238,7 @@ mod tests {
);
let original_len = prompt.len();
let prompt = ExtensionContext::with_tag(ExtensionContextTag::GOAL, prompt).render();
let prompt = ExtensionContext::new(prompt).render();
let TurnInput::ResponseInputItem(ResponseInputItem::Message { content, .. }) =
idle_extension_input(prompt)
else {
@@ -250,7 +248,7 @@ mod tests {
panic!("expected one text content item");
};
assert!(text.starts_with("<goal_context>"));
assert!(text.starts_with("<extension_context>"));
assert!(text.contains("start"));
assert!(text.contains("end"));
assert!(text.len() < original_len);
+5 -8
View File
@@ -1,6 +1,5 @@
use codex_core::context::ContextualUserFragment;
use codex_core::context::ExtensionContext;
use codex_core::context::ExtensionContextTag;
use codex_extension_api::ThreadIdleRequest;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::protocol::ThreadGoal;
@@ -47,21 +46,19 @@ fn render_embedded_template<const N: usize>(
}
pub(crate) fn budget_limit_steering_item(goal: &ThreadGoal) -> ResponseInputItem {
goal_context_input_item(budget_limit_prompt(goal))
extension_context_input_item(budget_limit_prompt(goal))
}
pub(crate) fn continuation_steering_request(goal: &ThreadGoal) -> ThreadIdleRequest {
ThreadIdleRequest::new(
ExtensionContext::with_tag(ExtensionContextTag::GOAL, continuation_prompt(goal)).render(),
)
ThreadIdleRequest::new(ExtensionContext::new(continuation_prompt(goal)).render())
}
pub(crate) fn objective_updated_steering_item(goal: &ThreadGoal) -> ResponseInputItem {
goal_context_input_item(objective_updated_prompt(goal))
extension_context_input_item(objective_updated_prompt(goal))
}
fn goal_context_input_item(prompt: String) -> ResponseInputItem {
ExtensionContext::with_tag(ExtensionContextTag::GOAL, prompt).into_response_input_item()
fn extension_context_input_item(prompt: String) -> ResponseInputItem {
ExtensionContext::new(prompt).into_response_input_item()
}
fn continuation_prompt(goal: &ThreadGoal) -> String {
@@ -507,14 +507,14 @@ async fn thread_idle_hook_requests_hidden_goal_continuation() -> anyhow::Result<
.ok_or_else(|| anyhow::anyhow!("active goal should request idle continuation"))?;
assert!(request.validation_key.is_some());
assert!(request.prompt.starts_with("<goal_context>"));
assert!(request.prompt.starts_with("<extension_context>"));
assert!(
request
.prompt
.contains("Continue working toward the active thread goal.")
);
assert!(request.prompt.contains("Completion audit:"));
assert!(request.prompt.ends_with("</goal_context>"));
assert!(request.prompt.ends_with("</extension_context>"));
assert!(
harness
.should_start_idle_turn(ModeKind::Default, &request)