From ccbf0137db7ca14ab8c2126518aa430aa30888e6 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 19 May 2026 10:42:54 -0700 Subject: [PATCH] [codex] Make contextual user fragments dyn-renderable (#23397) ## Why `ContextualUserFragment` needs to be usable behind `dyn` for render-only paths, but associated constants made the trait non-object-safe. ## What changed - Replaced associated constants with trait methods so `dyn ContextualUserFragment` can render fragments. - Preserved the existing typed `T::matches_text(text)` registration pattern via `type_markers()`. - Kept default `render()` on the main trait so implementations only provide role, markers, and body. - Added unit coverage for rendering a `Box`. ## Verification - `cargo test -p codex-core contextual_user_fragment_is_dyn_compatible` - `just fix -p codex-core` --- .../context/approved_command_prefix_saved.rs | 14 +++-- .../core/src/context/apps_instructions.rs | 14 +++-- .../context/available_plugins_instructions.rs | 17 ++++-- .../context/available_skills_instructions.rs | 14 +++-- .../collaboration_mode_instructions.rs | 14 +++-- .../context/contextual_user_message_tests.rs | 15 ++++++ .../core/src/context/environment_context.rs | 17 ++++-- codex-rs/core/src/context/fragment.rs | 53 ++++++++++++------- codex-rs/core/src/context/goal_context.rs | 14 +++-- .../guardian_followup_review_reminder.rs | 14 +++-- .../src/context/hook_additional_context.rs | 14 +++-- .../context/image_generation_instructions.rs | 14 +++-- ...legacy_apply_patch_exec_command_warning.rs | 14 +++-- .../context/legacy_model_mismatch_warning.rs | 14 +++-- ...gacy_unified_exec_process_limit_warning.rs | 14 +++-- .../src/context/model_switch_instructions.rs | 14 +++-- .../core/src/context/network_rule_saved.rs | 14 +++-- .../src/context/permissions_instructions.rs | 14 +++-- .../context/personality_spec_instructions.rs | 14 +++-- .../core/src/context/plugin_instructions.rs | 14 +++-- .../src/context/realtime_end_instructions.rs | 17 ++++-- .../context/realtime_start_instructions.rs | 17 ++++-- .../realtime_start_with_instructions.rs | 17 ++++-- .../core/src/context/skill_instructions.rs | 14 +++-- .../core/src/context/subagent_notification.rs | 14 +++-- codex-rs/core/src/context/turn_aborted.rs | 14 +++-- .../core/src/context/user_instructions.rs | 14 +++-- .../core/src/context/user_shell_command.rs | 14 +++-- codex-rs/core/src/goals.rs | 2 +- 29 files changed, 350 insertions(+), 99 deletions(-) diff --git a/codex-rs/core/src/context/approved_command_prefix_saved.rs b/codex-rs/core/src/context/approved_command_prefix_saved.rs index 3aac17712a..532a2494c3 100644 --- a/codex-rs/core/src/context/approved_command_prefix_saved.rs +++ b/codex-rs/core/src/context/approved_command_prefix_saved.rs @@ -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) diff --git a/codex-rs/core/src/context/apps_instructions.rs b/codex-rs/core/src/context/apps_instructions.rs index 55865b429a..9a475dc311 100644 --- a/codex-rs/core/src/context/apps_instructions.rs +++ b/codex-rs/core/src/context/apps_instructions.rs @@ -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!( diff --git a/codex-rs/core/src/context/available_plugins_instructions.rs b/codex-rs/core/src/context/available_plugins_instructions.rs index 52f9c8df48..e7f560f6a1 100644 --- a/codex-rs/core/src/context/available_plugins_instructions.rs +++ b/codex-rs/core/src/context/available_plugins_instructions.rs @@ -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![ diff --git a/codex-rs/core/src/context/available_skills_instructions.rs b/codex-rs/core/src/context/available_skills_instructions.rs index 0a99bf62e6..e63d637677 100644 --- a/codex-rs/core/src/context/available_skills_instructions.rs +++ b/codex-rs/core/src/context/available_skills_instructions.rs @@ -21,9 +21,17 @@ impl From 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) diff --git a/codex-rs/core/src/context/collaboration_mode_instructions.rs b/codex-rs/core/src/context/collaboration_mode_instructions.rs index dcf3bac1de..16e3d38198 100644 --- a/codex-rs/core/src/context/collaboration_mode_instructions.rs +++ b/codex-rs/core/src/context/collaboration_mode_instructions.rs @@ -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() diff --git a/codex-rs/core/src/context/contextual_user_message_tests.rs b/codex-rs/core/src/context/contextual_user_message_tests.rs index ee134cc685..7e04854d42 100644 --- a/codex-rs/core/src/context/contextual_user_message_tests.rs +++ b/codex-rs/core/src/context/contextual_user_message_tests.rs @@ -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() { @@ -38,6 +41,18 @@ fn detects_goal_context_fragment() { })); } +#[test] +fn contextual_user_fragment_is_dyn_compatible() { + let fragment: Box = Box::new(GoalContext { + prompt: "Continue working toward the active thread goal.".to_string(), + }); + + assert_eq!( + fragment.render(), + "\nContinue working toward the active thread goal.\n" + ); +} + #[test] fn ignores_regular_user_text() { assert!(!is_contextual_user_fragment(&ContentItem::InputText { diff --git a/codex-rs/core/src/context/environment_context.rs b/codex-rs/core/src/context/environment_context.rs index 272e3c617d..a3052b6642 100644 --- a/codex-rs/core/src/context/environment_context.rs +++ b/codex-rs/core/src/context/environment_context.rs @@ -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(); diff --git a/codex-rs/core/src/context/fragment.rs b/codex-rs/core/src/context/fragment.rs index 1cc8f6d9b8..4a9e284d12 100644 --- a/codex-rs/core/src/context/fragment.rs +++ b/codex-rs/core/src/context/fragment.rs @@ -38,37 +38,34 @@ impl 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 @@ -77,7 +74,7 @@ pub trait ContextualUserFragment { { ResponseItem::Message { id: None, - role: Self::ROLE.to_string(), + role: Self::role().to_string(), content: vec![ContentItem::InputText { text: self.render(), }], @@ -85,3 +82,19 @@ pub trait ContextualUserFragment { } } } + +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 +} diff --git a/codex-rs/core/src/context/goal_context.rs b/codex-rs/core/src/context/goal_context.rs index d259273324..240d4d0e8a 100644 --- a/codex-rs/core/src/context/goal_context.rs +++ b/codex-rs/core/src/context/goal_context.rs @@ -8,9 +8,17 @@ pub(crate) struct GoalContext { } impl ContextualUserFragment for GoalContext { - 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 body(&self) -> String { format!("\n{}\n", self.prompt) diff --git a/codex-rs/core/src/context/guardian_followup_review_reminder.rs b/codex-rs/core/src/context/guardian_followup_review_reminder.rs index 972355ba95..4de750629c 100644 --- a/codex-rs/core/src/context/guardian_followup_review_reminder.rs +++ b/codex-rs/core/src/context/guardian_followup_review_reminder.rs @@ -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!( diff --git a/codex-rs/core/src/context/hook_additional_context.rs b/codex-rs/core/src/context/hook_additional_context.rs index 01c95a6524..1094261665 100644 --- a/codex-rs/core/src/context/hook_additional_context.rs +++ b/codex-rs/core/src/context/hook_additional_context.rs @@ -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() diff --git a/codex-rs/core/src/context/image_generation_instructions.rs b/codex-rs/core/src/context/image_generation_instructions.rs index 01abee9096..7ddb231b8f 100644 --- a/codex-rs/core/src/context/image_generation_instructions.rs +++ b/codex-rs/core/src/context/image_generation_instructions.rs @@ -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!( diff --git a/codex-rs/core/src/context/legacy_apply_patch_exec_command_warning.rs b/codex-rs/core/src/context/legacy_apply_patch_exec_command_warning.rs index d8fd7da8e9..b9ecbf6170 100644 --- a/codex-rs/core/src/context/legacy_apply_patch_exec_command_warning.rs +++ b/codex-rs/core/src/context/legacy_apply_patch_exec_command_warning.rs @@ -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(); diff --git a/codex-rs/core/src/context/legacy_model_mismatch_warning.rs b/codex-rs/core/src/context/legacy_model_mismatch_warning.rs index a6afa9d7d1..9b769e11e9 100644 --- a/codex-rs/core/src/context/legacy_model_mismatch_warning.rs +++ b/codex-rs/core/src/context/legacy_model_mismatch_warning.rs @@ -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( diff --git a/codex-rs/core/src/context/legacy_unified_exec_process_limit_warning.rs b/codex-rs/core/src/context/legacy_unified_exec_process_limit_warning.rs index 463aeaa8aa..54f1a40e3e 100644 --- a/codex-rs/core/src/context/legacy_unified_exec_process_limit_warning.rs +++ b/codex-rs/core/src/context/legacy_unified_exec_process_limit_warning.rs @@ -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( diff --git a/codex-rs/core/src/context/model_switch_instructions.rs b/codex-rs/core/src/context/model_switch_instructions.rs index 0b75afb61a..f15dcd22e0 100644 --- a/codex-rs/core/src/context/model_switch_instructions.rs +++ b/codex-rs/core/src/context/model_switch_instructions.rs @@ -14,9 +14,17 @@ impl ModelSwitchInstructions { } impl ContextualUserFragment for ModelSwitchInstructions { - 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!( diff --git a/codex-rs/core/src/context/network_rule_saved.rs b/codex-rs/core/src/context/network_rule_saved.rs index 8162e6bda2..5b45dbb560 100644 --- a/codex-rs/core/src/context/network_rule_saved.rs +++ b/codex-rs/core/src/context/network_rule_saved.rs @@ -18,9 +18,17 @@ impl NetworkRuleSaved { } impl ContextualUserFragment for NetworkRuleSaved { - 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 { let (action, list_name) = match self.action { diff --git a/codex-rs/core/src/context/permissions_instructions.rs b/codex-rs/core/src/context/permissions_instructions.rs index e982ca17d2..db8f983c16 100644 --- a/codex-rs/core/src/context/permissions_instructions.rs +++ b/codex-rs/core/src/context/permissions_instructions.rs @@ -145,9 +145,17 @@ fn network_access_from_policy(network_policy: NetworkSandboxPolicy) -> NetworkAc } impl ContextualUserFragment for PermissionsInstructions { - 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() diff --git a/codex-rs/core/src/context/personality_spec_instructions.rs b/codex-rs/core/src/context/personality_spec_instructions.rs index 17c9cf595b..590ebde80d 100644 --- a/codex-rs/core/src/context/personality_spec_instructions.rs +++ b/codex-rs/core/src/context/personality_spec_instructions.rs @@ -12,9 +12,17 @@ impl PersonalitySpecInstructions { } impl ContextualUserFragment for PersonalitySpecInstructions { - 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!( diff --git a/codex-rs/core/src/context/plugin_instructions.rs b/codex-rs/core/src/context/plugin_instructions.rs index 0678033bbe..8fbccbfa10 100644 --- a/codex-rs/core/src/context/plugin_instructions.rs +++ b/codex-rs/core/src/context/plugin_instructions.rs @@ -12,9 +12,17 @@ impl PluginInstructions { } impl ContextualUserFragment for PluginInstructions { - 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() diff --git a/codex-rs/core/src/context/realtime_end_instructions.rs b/codex-rs/core/src/context/realtime_end_instructions.rs index 0edd852895..a8c6226f2a 100644 --- a/codex-rs/core/src/context/realtime_end_instructions.rs +++ b/codex-rs/core/src/context/realtime_end_instructions.rs @@ -18,9 +18,20 @@ impl RealtimeEndInstructions { } impl ContextualUserFragment for RealtimeEndInstructions { - const ROLE: &'static str = "developer"; - const START_MARKER: &'static str = REALTIME_CONVERSATION_OPEN_TAG; - const END_MARKER: &'static str = REALTIME_CONVERSATION_CLOSE_TAG; + fn role() -> &'static str { + "developer" + } + + fn markers(&self) -> (&'static str, &'static str) { + Self::type_markers() + } + + fn type_markers() -> (&'static str, &'static str) { + ( + REALTIME_CONVERSATION_OPEN_TAG, + REALTIME_CONVERSATION_CLOSE_TAG, + ) + } fn body(&self) -> String { format!( diff --git a/codex-rs/core/src/context/realtime_start_instructions.rs b/codex-rs/core/src/context/realtime_start_instructions.rs index 6745a74fd3..cc767c202a 100644 --- a/codex-rs/core/src/context/realtime_start_instructions.rs +++ b/codex-rs/core/src/context/realtime_start_instructions.rs @@ -8,9 +8,20 @@ const REALTIME_START_INSTRUCTIONS: &str = include_str!("prompts/realtime/realtim pub(crate) struct RealtimeStartInstructions; impl ContextualUserFragment for RealtimeStartInstructions { - const ROLE: &'static str = "developer"; - const START_MARKER: &'static str = REALTIME_CONVERSATION_OPEN_TAG; - const END_MARKER: &'static str = REALTIME_CONVERSATION_CLOSE_TAG; + fn role() -> &'static str { + "developer" + } + + fn markers(&self) -> (&'static str, &'static str) { + Self::type_markers() + } + + fn type_markers() -> (&'static str, &'static str) { + ( + REALTIME_CONVERSATION_OPEN_TAG, + REALTIME_CONVERSATION_CLOSE_TAG, + ) + } fn body(&self) -> String { format!("\n{}\n", REALTIME_START_INSTRUCTIONS.trim()) diff --git a/codex-rs/core/src/context/realtime_start_with_instructions.rs b/codex-rs/core/src/context/realtime_start_with_instructions.rs index ddaf4528df..ee242f9068 100644 --- a/codex-rs/core/src/context/realtime_start_with_instructions.rs +++ b/codex-rs/core/src/context/realtime_start_with_instructions.rs @@ -16,9 +16,20 @@ impl RealtimeStartWithInstructions { } impl ContextualUserFragment for RealtimeStartWithInstructions { - const ROLE: &'static str = "developer"; - const START_MARKER: &'static str = REALTIME_CONVERSATION_OPEN_TAG; - const END_MARKER: &'static str = REALTIME_CONVERSATION_CLOSE_TAG; + fn role() -> &'static str { + "developer" + } + + fn markers(&self) -> (&'static str, &'static str) { + Self::type_markers() + } + + fn type_markers() -> (&'static str, &'static str) { + ( + REALTIME_CONVERSATION_OPEN_TAG, + REALTIME_CONVERSATION_CLOSE_TAG, + ) + } fn body(&self) -> String { format!("\n{}\n", self.instructions) diff --git a/codex-rs/core/src/context/skill_instructions.rs b/codex-rs/core/src/context/skill_instructions.rs index 22f6ba2255..5c4b4b78a6 100644 --- a/codex-rs/core/src/context/skill_instructions.rs +++ b/codex-rs/core/src/context/skill_instructions.rs @@ -20,9 +20,17 @@ impl From<&SkillInjection> for SkillInstructions { } impl ContextualUserFragment for SkillInstructions { - 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 body(&self) -> String { format!( diff --git a/codex-rs/core/src/context/subagent_notification.rs b/codex-rs/core/src/context/subagent_notification.rs index bdb389f366..0c1358f33c 100644 --- a/codex-rs/core/src/context/subagent_notification.rs +++ b/codex-rs/core/src/context/subagent_notification.rs @@ -18,9 +18,17 @@ impl SubagentNotification { } impl ContextualUserFragment for SubagentNotification { - 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 body(&self) -> String { format!( diff --git a/codex-rs/core/src/context/turn_aborted.rs b/codex-rs/core/src/context/turn_aborted.rs index 34c02b9cf6..6dc6697288 100644 --- a/codex-rs/core/src/context/turn_aborted.rs +++ b/codex-rs/core/src/context/turn_aborted.rs @@ -17,9 +17,17 @@ impl TurnAborted { } impl ContextualUserFragment for TurnAborted { - 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 body(&self) -> String { format!("\n{}\n", self.guidance) diff --git a/codex-rs/core/src/context/user_instructions.rs b/codex-rs/core/src/context/user_instructions.rs index dead5971da..1b3fa40298 100644 --- a/codex-rs/core/src/context/user_instructions.rs +++ b/codex-rs/core/src/context/user_instructions.rs @@ -7,9 +7,17 @@ pub(crate) struct UserInstructions { } impl ContextualUserFragment for UserInstructions { - const ROLE: &'static str = "user"; - const START_MARKER: &'static str = "# AGENTS.md instructions for "; - 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) { + ("# AGENTS.md instructions for ", "") + } fn body(&self) -> String { format!("{}\n\n\n{}\n", self.directory, self.text) diff --git a/codex-rs/core/src/context/user_shell_command.rs b/codex-rs/core/src/context/user_shell_command.rs index 2350687abc..cf5a3337d0 100644 --- a/codex-rs/core/src/context/user_shell_command.rs +++ b/codex-rs/core/src/context/user_shell_command.rs @@ -27,9 +27,17 @@ impl UserShellCommand { } impl ContextualUserFragment for UserShellCommand { - 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 body(&self) -> String { format!( diff --git a/codex-rs/core/src/goals.rs b/codex-rs/core/src/goals.rs index 73e82fdcfd..6843264e44 100644 --- a/codex-rs/core/src/goals.rs +++ b/codex-rs/core/src/goals.rs @@ -1634,7 +1634,7 @@ fn budget_limit_steering_item(goal: &ThreadGoal) -> ResponseInputItem { fn goal_context_input_item(prompt: String) -> ResponseInputItem { let context = GoalContext { prompt }; ResponseInputItem::Message { - role: ::ROLE.to_string(), + role: GoalContext::role().to_string(), content: vec![ContentItem::InputText { text: context.render(), }],