From ac67905fc44b2e4ebd1c295dfe73f87033c50826 Mon Sep 17 00:00:00 2001 From: jif Date: Wed, 3 Jun 2026 12:25:21 +0200 Subject: [PATCH] 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` --- codex-rs/Cargo.lock | 11 ++++++++++ codex-rs/Cargo.toml | 2 ++ codex-rs/context-fragments/BUILD.bazel | 6 ++++++ codex-rs/context-fragments/Cargo.toml | 18 +++++++++++++++++ .../src/additional_context.rs} | 11 +++++----- .../src}/fragment.rs | 19 +++++++++++------- codex-rs/context-fragments/src/lib.rs | 8 ++++++++ codex-rs/core-skills/Cargo.toml | 1 + codex-rs/core-skills/src/lib.rs | 2 ++ .../src}/skill_instructions.rs | 12 +++++------ codex-rs/core/Cargo.toml | 1 + codex-rs/core/src/context/mod.rs | 15 ++++++-------- .../src/context/permissions_instructions.rs | 20 ------------------- codex-rs/prompts/Cargo.toml | 1 + .../prompts/src/permissions_instructions.rs | 19 ++++++++++++++++++ 15 files changed, 99 insertions(+), 47 deletions(-) create mode 100644 codex-rs/context-fragments/BUILD.bazel create mode 100644 codex-rs/context-fragments/Cargo.toml rename codex-rs/{core/src/context/fragments.rs => context-fragments/src/additional_context.rs} (89%) rename codex-rs/{core/src/context => context-fragments/src}/fragment.rs (88%) create mode 100644 codex-rs/context-fragments/src/lib.rs rename codex-rs/{core/src/context => core-skills/src}/skill_instructions.rs (77%) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 94dac4569..f7e6ed5de 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -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", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 482951fb9..59d161958 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -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" } diff --git a/codex-rs/context-fragments/BUILD.bazel b/codex-rs/context-fragments/BUILD.bazel new file mode 100644 index 000000000..b5920d0a2 --- /dev/null +++ b/codex-rs/context-fragments/BUILD.bazel @@ -0,0 +1,6 @@ +load("//:defs.bzl", "codex_rust_crate") + +codex_rust_crate( + name = "context-fragments", + crate_name = "codex_context_fragments", +) diff --git a/codex-rs/context-fragments/Cargo.toml b/codex-rs/context-fragments/Cargo.toml new file mode 100644 index 000000000..ca84916ce --- /dev/null +++ b/codex-rs/context-fragments/Cargo.toml @@ -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 } diff --git a/codex-rs/core/src/context/fragments.rs b/codex-rs/context-fragments/src/additional_context.rs similarity index 89% rename from codex-rs/core/src/context/fragments.rs rename to codex-rs/context-fragments/src/additional_context.rs index e23f96f59..4d08b5178 100644 --- a/codex-rs/core/src/context/fragments.rs +++ b/codex-rs/context-fragments/src/additional_context.rs @@ -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 = " 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 } } } diff --git a/codex-rs/core/src/context/fragment.rs b/codex-rs/context-fragments/src/fragment.rs similarity index 88% rename from codex-rs/core/src/context/fragment.rs rename to codex-rs/context-fragments/src/fragment.rs index 5c500fe8e..f41cbb63e 100644 --- a/codex-rs/core/src/context/fragment.rs +++ b/codex-rs/context-fragments/src/fragment.rs @@ -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 { - _marker: PhantomData T>, +pub struct FragmentRegistrationProxy { + _marker: std::marker::PhantomData T>, } impl FragmentRegistrationProxy { - pub(crate) const fn new() -> Self { + pub const fn new() -> Self { Self { - _marker: PhantomData, + _marker: std::marker::PhantomData, } } } +impl Default for FragmentRegistrationProxy { + fn default() -> Self { + Self::new() + } +} + impl FragmentRegistration for FragmentRegistrationProxy { 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; } diff --git a/codex-rs/context-fragments/src/lib.rs b/codex-rs/context-fragments/src/lib.rs new file mode 100644 index 000000000..c95f3001d --- /dev/null +++ b/codex-rs/context-fragments/src/lib.rs @@ -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; diff --git a/codex-rs/core-skills/Cargo.toml b/codex-rs/core-skills/Cargo.toml index 4324d29de..3c18bee60 100644 --- a/codex-rs/core-skills/Cargo.toml +++ b/codex-rs/core-skills/Cargo.toml @@ -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 } diff --git a/codex-rs/core-skills/src/lib.rs b/codex-rs/core-skills/src/lib.rs index 1577662bc..e4a61697d 100644 --- a/codex-rs/core-skills/src/lib.rs +++ b/codex-rs/core-skills/src/lib.rs @@ -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; diff --git a/codex-rs/core/src/context/skill_instructions.rs b/codex-rs/core-skills/src/skill_instructions.rs similarity index 77% rename from codex-rs/core/src/context/skill_instructions.rs rename to codex-rs/core-skills/src/skill_instructions.rs index 5c4b4b78a..47dd0d021 100644 --- a/codex-rs/core/src/context/skill_instructions.rs +++ b/codex-rs/core-skills/src/skill_instructions.rs @@ -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 { diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 4f6ab2e77..97e104464 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -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 } diff --git a/codex-rs/core/src/context/mod.rs b/codex-rs/core/src/context/mod.rs index 3e01d5212..5115bda6d 100644 --- a/codex-rs/core/src/context/mod.rs +++ b/codex-rs/core/src/context/mod.rs @@ -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; diff --git a/codex-rs/core/src/context/permissions_instructions.rs b/codex-rs/core/src/context/permissions_instructions.rs index 513390d59..73629a3d5 100644 --- a/codex-rs/core/src/context/permissions_instructions.rs +++ b/codex-rs/core/src/context/permissions_instructions.rs @@ -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) { - ("", "") - } - - fn body(&self) -> String { - PermissionsInstructions::body(self) - } -} diff --git a/codex-rs/prompts/Cargo.toml b/codex-rs/prompts/Cargo.toml index 1fc217988..6302d49e2 100644 --- a/codex-rs/prompts/Cargo.toml +++ b/codex-rs/prompts/Cargo.toml @@ -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 } diff --git a/codex-rs/prompts/src/permissions_instructions.rs b/codex-rs/prompts/src/permissions_instructions.rs index 426fc85b7..46c3e865d 100644 --- a/codex-rs/prompts/src/permissions_instructions.rs +++ b/codex-rs/prompts/src/permissions_instructions.rs @@ -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) { + ("", "") + } + + fn body(&self) -> String { + PermissionsInstructions::body(self) + } +} + fn sandbox_prompt_from_policy( file_system_policy: &FileSystemSandboxPolicy, cwd: &Path,