mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
chore: extract context fragments into dedicated crate (#26122)
## Why `codex-core` currently owns the generic contextual-fragment trait and several reusable fragment implementations. That makes it harder for other crates to share the same host-owned model-input abstraction without depending on all of `codex-core`. This change extracts the reusable fragment machinery into a small `codex-context-fragments` crate so future extension and skills work can depend on the fragment abstraction directly. ## What Changed - Added the `codex-context-fragments` crate with: - `ContextualUserFragment` - `FragmentRegistration` / `FragmentRegistrationProxy` - additional-context fragment types - Moved `SkillInstructions` into `codex-core-skills`, since skill-specific rendering belongs with skills rather than generic core context machinery. - Kept `codex-core` re-exporting the fragment types it still uses internally, so existing call sites keep the same shape. - Updated Cargo and Bazel workspace metadata for the new crate. ## Verification - `cargo metadata --locked --format-version 1 --no-deps` - `just bazel-lock-update` - `just bazel-lock-check`
This commit is contained in:
@@ -1,114 +0,0 @@
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Type-erased registration for a contextual user fragment.
|
||||
///
|
||||
/// Implementations are used by context filtering code to recognize injected
|
||||
/// fragments without constructing the concrete context payload.
|
||||
pub(crate) trait FragmentRegistration: Sync {
|
||||
fn matches_text(&self, text: &str) -> bool;
|
||||
}
|
||||
|
||||
pub(crate) struct FragmentRegistrationProxy<T> {
|
||||
_marker: PhantomData<fn() -> T>,
|
||||
}
|
||||
|
||||
impl<T> FragmentRegistrationProxy<T> {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Self {
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ContextualUserFragment> FragmentRegistration for FragmentRegistrationProxy<T> {
|
||||
fn matches_text(&self, text: &str) -> bool {
|
||||
T::matches_text(text)
|
||||
}
|
||||
}
|
||||
|
||||
/// Context payload that is injected as a message fragment.
|
||||
///
|
||||
/// Implementations own the response role and provide the exact fragment body.
|
||||
/// Marked fragments also provide start/end markers used to recognize injected
|
||||
/// context later. `render()` concatenates markers and body without adding
|
||||
/// separators, so implementations should include any whitespace they need
|
||||
/// between tags in `body()`. Unmarked fragments should leave both markers empty,
|
||||
/// in which case the default helpers render only the body and never match
|
||||
/// arbitrary text.
|
||||
pub trait ContextualUserFragment {
|
||||
fn role() -> &'static str
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn markers(&self) -> (&'static str, &'static str);
|
||||
|
||||
fn body(&self) -> String;
|
||||
|
||||
fn type_markers() -> (&'static str, &'static str)
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn matches_text(text: &str) -> bool
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let (start_marker, end_marker) = Self::type_markers();
|
||||
matches_marked_text(start_marker, end_marker, text)
|
||||
}
|
||||
|
||||
fn render(&self) -> String {
|
||||
let (start_marker, end_marker) = self.markers();
|
||||
let body = self.body();
|
||||
if start_marker.is_empty() && end_marker.is_empty() {
|
||||
return body;
|
||||
}
|
||||
|
||||
format!("{start_marker}{body}{end_marker}")
|
||||
}
|
||||
|
||||
fn into(self) -> ResponseItem
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
role: Self::role().to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: self.render(),
|
||||
}],
|
||||
phase: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_response_input_item(self) -> ResponseInputItem
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ResponseInputItem::Message {
|
||||
role: Self::role().to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: self.render(),
|
||||
}],
|
||||
phase: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_marked_text(start_marker: &str, end_marker: &str, text: &str) -> bool {
|
||||
if start_marker.is_empty() || end_marker.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let trimmed = text.trim_start();
|
||||
let starts_with_marker = trimmed
|
||||
.get(..start_marker.len())
|
||||
.is_some_and(|candidate| candidate.eq_ignore_ascii_case(start_marker));
|
||||
let trimmed = trimmed.trim_end();
|
||||
let ends_with_marker = trimmed
|
||||
.get(trimmed.len().saturating_sub(end_marker.len())..)
|
||||
.is_some_and(|candidate| candidate.eq_ignore_ascii_case(end_marker));
|
||||
starts_with_marker && ends_with_marker
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
use super::ContextualUserFragment;
|
||||
use codex_utils_string::truncate_middle_with_token_budget;
|
||||
|
||||
const MAX_ADDITIONAL_CONTEXT_VALUE_TOKENS: usize = 1_000;
|
||||
const ADDITIONAL_CONTEXT_END_MARKER_SUFFIX: &str = ">";
|
||||
const ADDITIONAL_CONTEXT_START_MARKER_PREFIX: &str = "<external_";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct AdditionalContextUserFragment {
|
||||
key: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl AdditionalContextUserFragment {
|
||||
pub(crate) fn new(key: String, value: String) -> Self {
|
||||
Self { key, value }
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextualUserFragment for AdditionalContextUserFragment {
|
||||
fn role() -> &'static str {
|
||||
"user"
|
||||
}
|
||||
|
||||
fn markers(&self) -> (&'static str, &'static str) {
|
||||
Self::type_markers()
|
||||
}
|
||||
|
||||
fn type_markers() -> (&'static str, &'static str) {
|
||||
(
|
||||
ADDITIONAL_CONTEXT_START_MARKER_PREFIX,
|
||||
ADDITIONAL_CONTEXT_END_MARKER_SUFFIX,
|
||||
)
|
||||
}
|
||||
|
||||
fn matches_text(text: &str) -> bool {
|
||||
let trimmed = text.trim();
|
||||
let Some(rest) = trimmed.strip_prefix(ADDITIONAL_CONTEXT_START_MARKER_PREFIX) else {
|
||||
return false;
|
||||
};
|
||||
let Some((key, value_and_close)) = rest.split_once(ADDITIONAL_CONTEXT_END_MARKER_SUFFIX)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
value_and_close.ends_with(&format!("</external_{key}>"))
|
||||
}
|
||||
|
||||
fn body(&self) -> String {
|
||||
additional_context_body(&self.key, &self.value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct AdditionalContextDeveloperFragment {
|
||||
key: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl AdditionalContextDeveloperFragment {
|
||||
pub(crate) fn new(key: String, value: String) -> Self {
|
||||
Self { key, value }
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextualUserFragment for AdditionalContextDeveloperFragment {
|
||||
fn role() -> &'static str {
|
||||
"developer"
|
||||
}
|
||||
|
||||
fn markers(&self) -> (&'static str, &'static str) {
|
||||
Self::type_markers()
|
||||
}
|
||||
|
||||
fn type_markers() -> (&'static str, &'static str) {
|
||||
("", "")
|
||||
}
|
||||
|
||||
fn body(&self) -> String {
|
||||
additional_context_developer_body(&self.key, &self.value)
|
||||
}
|
||||
}
|
||||
|
||||
fn additional_context_body(key: &str, value: &str) -> String {
|
||||
let value = truncate_middle_with_token_budget(value, MAX_ADDITIONAL_CONTEXT_VALUE_TOKENS).0;
|
||||
format!("{key}>{value}</external_{key}")
|
||||
}
|
||||
|
||||
fn additional_context_developer_body(key: &str, value: &str) -> String {
|
||||
let value = truncate_middle_with_token_budget(value, MAX_ADDITIONAL_CONTEXT_VALUE_TOKENS).0;
|
||||
format!("<{key}>{value}</{key}>")
|
||||
}
|
||||
@@ -7,8 +7,6 @@ mod available_skills_instructions;
|
||||
mod collaboration_mode_instructions;
|
||||
mod contextual_user_message;
|
||||
mod environment_context;
|
||||
mod fragment;
|
||||
mod fragments;
|
||||
mod guardian_followup_review_reminder;
|
||||
mod hook_additional_context;
|
||||
mod image_generation_instructions;
|
||||
@@ -24,7 +22,6 @@ mod plugin_instructions;
|
||||
mod realtime_end_instructions;
|
||||
mod realtime_start_instructions;
|
||||
mod realtime_start_with_instructions;
|
||||
mod skill_instructions;
|
||||
mod subagent_notification;
|
||||
mod turn_aborted;
|
||||
mod user_instructions;
|
||||
@@ -34,15 +31,16 @@ pub(crate) use approved_command_prefix_saved::ApprovedCommandPrefixSaved;
|
||||
pub(crate) use apps_instructions::AppsInstructions;
|
||||
pub(crate) use available_plugins_instructions::AvailablePluginsInstructions;
|
||||
pub(crate) use available_skills_instructions::AvailableSkillsInstructions;
|
||||
pub(crate) use codex_context_fragments::AdditionalContextDeveloperFragment;
|
||||
pub(crate) use codex_context_fragments::AdditionalContextUserFragment;
|
||||
pub use codex_context_fragments::ContextualUserFragment;
|
||||
pub(crate) use codex_context_fragments::FragmentRegistration;
|
||||
pub(crate) use codex_context_fragments::FragmentRegistrationProxy;
|
||||
pub(crate) use codex_core_skills::SkillInstructions;
|
||||
pub(crate) use collaboration_mode_instructions::CollaborationModeInstructions;
|
||||
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 fragment::ContextualUserFragment;
|
||||
pub(crate) use fragment::FragmentRegistration;
|
||||
pub(crate) use fragment::FragmentRegistrationProxy;
|
||||
pub(crate) use fragments::AdditionalContextDeveloperFragment;
|
||||
pub(crate) use fragments::AdditionalContextUserFragment;
|
||||
pub(crate) use guardian_followup_review_reminder::GuardianFollowupReviewReminder;
|
||||
pub(crate) use hook_additional_context::HookAdditionalContext;
|
||||
pub(crate) use image_generation_instructions::ImageGenerationInstructions;
|
||||
@@ -60,7 +58,6 @@ pub(crate) use plugin_instructions::PluginInstructions;
|
||||
pub(crate) use realtime_end_instructions::RealtimeEndInstructions;
|
||||
pub(crate) use realtime_start_instructions::RealtimeStartInstructions;
|
||||
pub(crate) use realtime_start_with_instructions::RealtimeStartWithInstructions;
|
||||
pub(crate) use skill_instructions::SkillInstructions;
|
||||
pub(crate) use subagent_notification::SubagentNotification;
|
||||
pub(crate) use turn_aborted::TurnAborted;
|
||||
pub(crate) use user_instructions::UserInstructions;
|
||||
|
||||
@@ -1,21 +1 @@
|
||||
use super::ContextualUserFragment;
|
||||
|
||||
pub use codex_prompts::PermissionsInstructions;
|
||||
|
||||
impl ContextualUserFragment for PermissionsInstructions {
|
||||
fn role() -> &'static str {
|
||||
"developer"
|
||||
}
|
||||
|
||||
fn markers(&self) -> (&'static str, &'static str) {
|
||||
Self::type_markers()
|
||||
}
|
||||
|
||||
fn type_markers() -> (&'static str, &'static str) {
|
||||
("<permissions instructions>", "</permissions instructions>")
|
||||
}
|
||||
|
||||
fn body(&self) -> String {
|
||||
PermissionsInstructions::body(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
use codex_core_skills::injection::SkillInjection;
|
||||
|
||||
use super::ContextualUserFragment;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct SkillInstructions {
|
||||
pub(crate) name: String,
|
||||
pub(crate) path: String,
|
||||
pub(crate) contents: String,
|
||||
}
|
||||
|
||||
impl From<&SkillInjection> for SkillInstructions {
|
||||
fn from(skill: &SkillInjection) -> Self {
|
||||
Self {
|
||||
name: skill.name.clone(),
|
||||
path: skill.path.clone(),
|
||||
contents: skill.contents.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextualUserFragment for SkillInstructions {
|
||||
fn role() -> &'static str {
|
||||
"user"
|
||||
}
|
||||
|
||||
fn markers(&self) -> (&'static str, &'static str) {
|
||||
Self::type_markers()
|
||||
}
|
||||
|
||||
fn type_markers() -> (&'static str, &'static str) {
|
||||
("<skill>", "</skill>")
|
||||
}
|
||||
|
||||
fn body(&self) -> String {
|
||||
format!(
|
||||
"\n<name>{}</name>\n<path>{}</path>\n{}\n",
|
||||
self.name, self.path, self.contents
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user