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:
jif
2026-06-03 12:25:21 +02:00
committed by GitHub
Unverified
parent 668703c23f
commit ac67905fc4
15 changed files with 99 additions and 47 deletions
+11
View File
@@ -2506,6 +2506,14 @@ dependencies = [
"urlencoding",
]
[[package]]
name = "codex-context-fragments"
version = "0.0.0"
dependencies = [
"codex-protocol",
"codex-utils-string",
]
[[package]]
name = "codex-core"
version = "0.0.0"
@@ -2528,6 +2536,7 @@ dependencies = [
"codex-code-mode",
"codex-config",
"codex-connectors",
"codex-context-fragments",
"codex-core-plugins",
"codex-core-skills",
"codex-exec-server",
@@ -2692,6 +2701,7 @@ dependencies = [
"codex-analytics",
"codex-app-server-protocol",
"codex-config",
"codex-context-fragments",
"codex-exec-server",
"codex-login",
"codex-model-provider",
@@ -3458,6 +3468,7 @@ name = "codex-prompts"
version = "0.0.0"
dependencies = [
"anyhow",
"codex-context-fragments",
"codex-execpolicy",
"codex-git-utils",
"codex-protocol",
+2
View File
@@ -29,6 +29,7 @@ members = [
"collaboration-mode-templates",
"connectors",
"config",
"context-fragments",
"shell-command",
"shell-escalation",
"skills",
@@ -156,6 +157,7 @@ codex-cloud-tasks-mock-client = { path = "cloud-tasks-mock-client" }
codex-code-mode = { path = "code-mode" }
codex-config = { path = "config" }
codex-connectors = { path = "connectors" }
codex-context-fragments = { path = "context-fragments" }
codex-core = { path = "core" }
codex-core-api = { path = "core-api" }
codex-core-plugins = { path = "core-plugins" }
+6
View File
@@ -0,0 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "context-fragments",
crate_name = "codex_context_fragments",
)
+18
View File
@@ -0,0 +1,18 @@
[package]
edition.workspace = true
license.workspace = true
name = "codex-context-fragments"
version.workspace = true
[lib]
name = "codex_context_fragments"
path = "src/lib.rs"
test = false
doctest = false
[lints]
workspace = true
[dependencies]
codex-protocol = { workspace = true }
codex-utils-string = { workspace = true }
@@ -1,18 +1,19 @@
use super::ContextualUserFragment;
use codex_utils_string::truncate_middle_with_token_budget;
use crate::ContextualUserFragment;
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 {
pub struct AdditionalContextUserFragment {
key: String,
value: String,
}
impl AdditionalContextUserFragment {
pub(crate) fn new(key: String, value: String) -> Self {
pub fn new(key: String, value: String) -> Self {
Self { key, value }
}
}
@@ -52,13 +53,13 @@ impl ContextualUserFragment for AdditionalContextUserFragment {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct AdditionalContextDeveloperFragment {
pub struct AdditionalContextDeveloperFragment {
key: String,
value: String,
}
impl AdditionalContextDeveloperFragment {
pub(crate) fn new(key: String, value: String) -> Self {
pub fn new(key: String, value: String) -> Self {
Self { key, value }
}
}
@@ -1,28 +1,33 @@
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 {
pub trait FragmentRegistration: Sync {
fn matches_text(&self, text: &str) -> bool;
}
pub(crate) struct FragmentRegistrationProxy<T> {
_marker: PhantomData<fn() -> T>,
pub struct FragmentRegistrationProxy<T> {
_marker: std::marker::PhantomData<fn() -> T>,
}
impl<T> FragmentRegistrationProxy<T> {
pub(crate) const fn new() -> Self {
pub const fn new() -> Self {
Self {
_marker: PhantomData,
_marker: std::marker::PhantomData,
}
}
}
impl<T> Default for FragmentRegistrationProxy<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: ContextualUserFragment> FragmentRegistration for FragmentRegistrationProxy<T> {
fn matches_text(&self, text: &str) -> bool {
T::matches_text(text)
@@ -97,7 +102,7 @@ pub trait ContextualUserFragment {
}
}
fn matches_marked_text(start_marker: &str, end_marker: &str, text: &str) -> bool {
pub(crate) fn matches_marked_text(start_marker: &str, end_marker: &str, text: &str) -> bool {
if start_marker.is_empty() || end_marker.is_empty() {
return false;
}
+8
View File
@@ -0,0 +1,8 @@
mod additional_context;
mod fragment;
pub use additional_context::AdditionalContextDeveloperFragment;
pub use additional_context::AdditionalContextUserFragment;
pub use fragment::ContextualUserFragment;
pub use fragment::FragmentRegistration;
pub use fragment::FragmentRegistrationProxy;
+1
View File
@@ -17,6 +17,7 @@ anyhow = { workspace = true }
codex-analytics = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-config = { workspace = true }
codex-context-fragments = { workspace = true }
codex-exec-server = { workspace = true }
codex-login = { workspace = true }
codex-model-provider = { workspace = true }
+2
View File
@@ -7,6 +7,7 @@ mod mention_counts;
pub mod model;
pub mod remote;
pub mod render;
mod skill_instructions;
pub mod system;
pub(crate) use invocation_utils::build_implicit_skill_path_indexes;
@@ -29,3 +30,4 @@ pub use render::SkillRenderReport;
pub use render::build_available_skills;
pub use render::default_skill_metadata_budget;
pub use render::render_available_skills_body;
pub use skill_instructions::SkillInstructions;
@@ -1,12 +1,12 @@
use codex_core_skills::injection::SkillInjection;
use codex_context_fragments::ContextualUserFragment;
use super::ContextualUserFragment;
use crate::injection::SkillInjection;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SkillInstructions {
pub(crate) name: String,
pub(crate) path: String,
pub(crate) contents: String,
pub struct SkillInstructions {
name: String,
path: String,
contents: String,
}
impl From<&SkillInjection> for SkillInstructions {
+1
View File
@@ -31,6 +31,7 @@ codex-apply-patch = { workspace = true }
codex-async-utils = { workspace = true }
codex-code-mode = { workspace = true }
codex-connectors = { workspace = true }
codex-context-fragments = { workspace = true }
codex-config = { workspace = true }
codex-core-plugins = { workspace = true }
codex-core-skills = { workspace = true }
+6 -9
View File
@@ -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
View File
@@ -14,6 +14,7 @@ workspace = true
[dependencies]
anyhow = { workspace = true }
codex-context-fragments = { workspace = true }
codex-git-utils = { workspace = true }
codex-execpolicy = { workspace = true }
codex-protocol = { workspace = true }
@@ -1,3 +1,4 @@
use codex_context_fragments::ContextualUserFragment;
use codex_execpolicy::Policy;
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::SandboxMode;
@@ -141,6 +142,24 @@ impl 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)
}
}
fn sandbox_prompt_from_policy(
file_system_policy: &FileSystemSandboxPolicy,
cwd: &Path,