Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions codex-rs/core/src/context/approved_command_prefix_saved.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,17 @@ impl ApprovedCommandPrefixSaved {
}

impl ContextualUserFragment for ApprovedCommandPrefixSaved {
const ROLE: &'static str = "developer";
const START_MARKER: &'static str = "";
const END_MARKER: &'static str = "";
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 {
format!("Approved command prefix saved:\n{}", self.prefixes)
Expand Down
14 changes: 11 additions & 3 deletions codex-rs/core/src/context/apps_instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@ impl AppsInstructions {
}

impl ContextualUserFragment for AppsInstructions {
const ROLE: &'static str = "developer";
const START_MARKER: &'static str = APPS_INSTRUCTIONS_OPEN_TAG;
const END_MARKER: &'static str = APPS_INSTRUCTIONS_CLOSE_TAG;
fn role() -> &'static str {
"developer"
}

fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

fn type_markers() -> (&'static str, &'static str) {
(APPS_INSTRUCTIONS_OPEN_TAG, APPS_INSTRUCTIONS_CLOSE_TAG)
}

fn body(&self) -> String {
format!(
Expand Down
17 changes: 14 additions & 3 deletions codex-rs/core/src/context/available_plugins_instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,20 @@ impl AvailablePluginsInstructions {
}

impl ContextualUserFragment for AvailablePluginsInstructions {
const ROLE: &'static str = "developer";
const START_MARKER: &'static str = PLUGINS_INSTRUCTIONS_OPEN_TAG;
const END_MARKER: &'static str = PLUGINS_INSTRUCTIONS_CLOSE_TAG;
fn role() -> &'static str {
"developer"
}

fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

fn type_markers() -> (&'static str, &'static str) {
(
PLUGINS_INSTRUCTIONS_OPEN_TAG,
PLUGINS_INSTRUCTIONS_CLOSE_TAG,
)
}

fn body(&self) -> String {
let mut lines = vec![
Expand Down
14 changes: 11 additions & 3 deletions codex-rs/core/src/context/available_skills_instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,17 @@ impl From<AvailableSkills> for AvailableSkillsInstructions {
}

impl ContextualUserFragment for AvailableSkillsInstructions {
const ROLE: &'static str = "developer";
const START_MARKER: &'static str = SKILLS_INSTRUCTIONS_OPEN_TAG;
const END_MARKER: &'static str = SKILLS_INSTRUCTIONS_CLOSE_TAG;
fn role() -> &'static str {
"developer"
}

fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

fn type_markers() -> (&'static str, &'static str) {
(SKILLS_INSTRUCTIONS_OPEN_TAG, SKILLS_INSTRUCTIONS_CLOSE_TAG)
}

fn body(&self) -> String {
render_available_skills_body(&self.skill_root_lines, &self.skill_lines)
Expand Down
14 changes: 11 additions & 3 deletions codex-rs/core/src/context/collaboration_mode_instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,17 @@ impl CollaborationModeInstructions {
}

impl ContextualUserFragment for CollaborationModeInstructions {
const ROLE: &'static str = "developer";
const START_MARKER: &'static str = COLLABORATION_MODE_OPEN_TAG;
const END_MARKER: &'static str = COLLABORATION_MODE_CLOSE_TAG;
fn role() -> &'static str {
"developer"
}

fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

fn type_markers() -> (&'static str, &'static str) {
(COLLABORATION_MODE_OPEN_TAG, COLLABORATION_MODE_CLOSE_TAG)
}

fn body(&self) -> String {
self.instructions.clone()
Expand Down
15 changes: 15 additions & 0 deletions codex-rs/core/src/context/contextual_user_message_tests.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use super::*;
use crate::context::ContextualUserFragment;
use crate::context::GoalContext;
use crate::context::SubagentNotification;
use codex_protocol::items::HookPromptFragment;
use codex_protocol::items::build_hook_prompt_message;
use codex_protocol::models::ResponseItem;
use pretty_assertions::assert_eq;

#[test]
fn detects_environment_context_fragment() {
Expand Down Expand Up @@ -38,6 +41,18 @@ fn detects_goal_context_fragment() {
}));
}

#[test]
fn contextual_user_fragment_is_dyn_compatible() {
let fragment: Box<dyn ContextualUserFragment> = Box::new(GoalContext {
prompt: "Continue working toward the active thread goal.".to_string(),
});

assert_eq!(
fragment.render(),
"<goal_context>\nContinue working toward the active thread goal.\n</goal_context>"
);
}

#[test]
fn ignores_regular_user_text() {
assert!(!is_contextual_user_fragment(&ContentItem::InputText {
Expand Down
17 changes: 14 additions & 3 deletions codex-rs/core/src/context/environment_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,20 @@ impl EnvironmentContext {
}

impl ContextualUserFragment for EnvironmentContext {
const ROLE: &'static str = "user";
const START_MARKER: &'static str = codex_protocol::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG;
const END_MARKER: &'static str = codex_protocol::protocol::ENVIRONMENT_CONTEXT_CLOSE_TAG;
fn role() -> &'static str {
"user"
}

fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

fn type_markers() -> (&'static str, &'static str) {
(
codex_protocol::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG,
codex_protocol::protocol::ENVIRONMENT_CONTEXT_CLOSE_TAG,
)
}

fn body(&self) -> String {
let mut lines = Vec::new();
Expand Down
53 changes: 33 additions & 20 deletions codex-rs/core/src/context/fragment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,37 +38,34 @@ impl<T: ContextualUserFragment> FragmentRegistration for FragmentRegistrationPro
/// in which case the default helpers render only the body and never match
/// arbitrary text.
pub trait ContextualUserFragment {
const ROLE: &'static str;
const START_MARKER: &'static str;
const END_MARKER: &'static str;
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,
{
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
let (start_marker, end_marker) = Self::type_markers();
matches_marked_text(start_marker, end_marker, text)
}

fn render(&self) -> String {
if Self::START_MARKER.is_empty() && Self::END_MARKER.is_empty() {
return self.body();
let (start_marker, end_marker) = self.markers();
let body = self.body();
if start_marker.is_empty() && end_marker.is_empty() {
return body;
}

format!("{}{}{}", Self::START_MARKER, self.body(), Self::END_MARKER)
format!("{start_marker}{body}{end_marker}")
}

fn into(self) -> ResponseItem
Expand All @@ -77,11 +74,27 @@ pub trait ContextualUserFragment {
{
ResponseItem::Message {
id: None,
role: Self::ROLE.to_string(),
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
}
14 changes: 11 additions & 3 deletions codex-rs/core/src/context/goal_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ pub(crate) struct GoalContext {
}

impl ContextualUserFragment for GoalContext {
const ROLE: &'static str = "user";
const START_MARKER: &'static str = "<goal_context>";
const END_MARKER: &'static str = "</goal_context>";
fn role() -> &'static str {
"user"
}

fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

fn type_markers() -> (&'static str, &'static str) {
("<goal_context>", "</goal_context>")
}

fn body(&self) -> String {
format!("\n{}\n", self.prompt)
Expand Down
14 changes: 11 additions & 3 deletions codex-rs/core/src/context/guardian_followup_review_reminder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ use super::ContextualUserFragment;
pub(crate) struct GuardianFollowupReviewReminder;

impl ContextualUserFragment for GuardianFollowupReviewReminder {
const ROLE: &'static str = "developer";
const START_MARKER: &'static str = "";
const END_MARKER: &'static str = "";
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 {
concat!(
Expand Down
14 changes: 11 additions & 3 deletions codex-rs/core/src/context/hook_additional_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,17 @@ impl HookAdditionalContext {
}

impl ContextualUserFragment for HookAdditionalContext {
const ROLE: &'static str = "developer";
const START_MARKER: &'static str = "";
const END_MARKER: &'static str = "";
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 {
self.text.clone()
Expand Down
14 changes: 11 additions & 3 deletions codex-rs/core/src/context/image_generation_instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ impl ImageGenerationInstructions {
}

impl ContextualUserFragment for ImageGenerationInstructions {
const ROLE: &'static str = "developer";
const START_MARKER: &'static str = "";
const END_MARKER: &'static str = "";
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 {
format!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@ use super::ContextualUserFragment;
pub(crate) struct LegacyApplyPatchExecCommandWarning;

impl ContextualUserFragment for LegacyApplyPatchExecCommandWarning {
const ROLE: &'static str = "user";
const START_MARKER: &'static str = "";
const END_MARKER: &'static str = "";
fn role() -> &'static str {
"user"
}

fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

fn type_markers() -> (&'static str, &'static str) {
("", "")
}

fn matches_text(text: &str) -> bool {
let trimmed = text.trim();
Expand Down
14 changes: 11 additions & 3 deletions codex-rs/core/src/context/legacy_model_mismatch_warning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@ use super::ContextualUserFragment;
pub(crate) struct LegacyModelMismatchWarning;

impl ContextualUserFragment for LegacyModelMismatchWarning {
const ROLE: &'static str = "user";
const START_MARKER: &'static str = "";
const END_MARKER: &'static str = "";
fn role() -> &'static str {
"user"
}

fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

fn type_markers() -> (&'static str, &'static str) {
("", "")
}

fn matches_text(text: &str) -> bool {
text.trim().starts_with(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@ use super::ContextualUserFragment;
pub(crate) struct LegacyUnifiedExecProcessLimitWarning;

impl ContextualUserFragment for LegacyUnifiedExecProcessLimitWarning {
const ROLE: &'static str = "user";
const START_MARKER: &'static str = "";
const END_MARKER: &'static str = "";
fn role() -> &'static str {
"user"
}

fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

fn type_markers() -> (&'static str, &'static str) {
("", "")
}

fn matches_text(text: &str) -> bool {
text.trim().starts_with(
Expand Down
Loading
Loading