Classify contextual fragments with content kinds (#40180)

## What changed

- Require each `ContextualUserFragment` to provide a stable `<feature>.<name>`
  `ContentItemKind`.
- Add `AnnotatedContent` and `RenderedFragment` so rendered text, its role, and
  its classification can travel together to API boundaries.
- Derive extension-owned world-state classifications from the extension ID and
  keep the skills catalog classification with its fragment implementation.

## Testing

- Verify that an extension-owned world-state section renders with an
  `<extension-id>.instructions` content kind.

GitOrigin-RevId: e46b74a0bb41e0b6112667c9d36bc9e7f2714451
This commit is contained in:
pakrym-oai
2026-08-23 03:38:24 +00:00
committed by copyberry
parent a1d4aea265
commit 422239eb4b
48 changed files with 375 additions and 15 deletions

View File

@@ -1,3 +1,4 @@
use codex_protocol::models::ContentItemKind;
use codex_utils_string::truncate_middle_with_token_budget;
use crate::ContextualUserFragment;
@@ -23,6 +24,10 @@ impl ContextualUserFragment for AdditionalContextUserFragment {
"user"
}
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("generic.user_additional_context".to_string())
}
fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}
@@ -69,6 +74,10 @@ impl ContextualUserFragment for AdditionalContextDeveloperFragment {
"developer"
}
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("generic.developer_additional_context".to_string())
}
fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

View File

@@ -0,0 +1,36 @@
use codex_protocol::models::ContentItem;
use codex_protocol::models::ContentItemKind;
/// Model-visible content paired with its harness-owned classification.
#[derive(Clone, Debug, PartialEq)]
pub struct AnnotatedContent {
content: ContentItem,
kind: ContentItemKind,
}
impl AnnotatedContent {
/// Creates content and its classification together.
pub fn new(content: ContentItem, kind: ContentItemKind) -> Self {
Self { content, kind }
}
/// Creates model-visible input text and its classification together.
pub fn input_text(text: impl Into<String>, kind: ContentItemKind) -> Self {
Self::new(ContentItem::InputText { text: text.into() }, kind)
}
/// Returns the model-visible content.
pub fn content(&self) -> &ContentItem {
&self.content
}
/// Returns the classification associated with the content.
pub fn kind(&self) -> &ContentItemKind {
&self.kind
}
/// Separates the content from its classification at an API boundary.
pub fn into_parts(self) -> (ContentItem, ContentItemKind) {
(self.content, self.kind)
}
}

View File

@@ -1,7 +1,33 @@
use crate::AnnotatedContent;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ContentItemKind;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ResponseItem;
/// A rendered contextual fragment and the role that owns its annotated content.
#[derive(Clone, Debug, PartialEq)]
pub struct RenderedFragment {
role: &'static str,
content: AnnotatedContent,
}
impl RenderedFragment {
/// Returns the response role associated with this fragment.
pub fn role(&self) -> &'static str {
self.role
}
/// Returns this fragment's model-visible content and classification.
pub fn annotated_content(&self) -> &AnnotatedContent {
&self.content
}
/// Separates the role and annotated content at an API boundary.
pub fn into_parts(self) -> (&'static str, AnnotatedContent) {
(self.role, self.content)
}
}
/// Context payload that is injected as a message fragment.
///
/// Implementations own the response role and provide the exact fragment body.
@@ -14,6 +40,9 @@ use codex_protocol::models::ResponseItem;
pub trait ContextualUserFragment {
fn role(&self) -> &'static str;
/// Returns a stable `<feature>.<name>` classification, using `generic` for shared fragments.
fn content_kind(&self) -> ContentItemKind;
/// Whether this fragment must be recorded as its own response item.
fn requires_separate_message(&self) -> bool {
false
@@ -45,6 +74,14 @@ pub trait ContextualUserFragment {
format!("{start_marker}{body}{end_marker}")
}
/// Renders the role, model-visible content, and classification together.
fn render_fragment(&self) -> RenderedFragment {
RenderedFragment {
role: self.role(),
content: AnnotatedContent::input_text(self.render(), self.content_kind()),
}
}
fn into(self) -> ResponseItem
where
Self: Sized,

View File

@@ -1,6 +1,9 @@
mod additional_context;
mod annotated_content;
mod fragment;
pub use additional_context::AdditionalContextDeveloperFragment;
pub use additional_context::AdditionalContextUserFragment;
pub use annotated_content::AnnotatedContent;
pub use fragment::ContextualUserFragment;
pub use fragment::RenderedFragment;

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
pub(crate) const APPROVED_COMMAND_PREFIX_SAVED_MESSAGE_PREFIX: &str =
"Approved command prefix saved:";
@@ -17,6 +18,10 @@ impl ApprovedCommandPrefixSaved {
}
impl ContextualUserFragment for ApprovedCommandPrefixSaved {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("permissions.approved_command_prefix_saved".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -3,11 +3,16 @@ use codex_protocol::protocol::APPS_INSTRUCTIONS_CLOSE_TAG;
use codex_protocol::protocol::APPS_INSTRUCTIONS_OPEN_TAG;
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct AppsInstructions;
impl ContextualUserFragment for AppsInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("apps.instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -2,11 +2,16 @@ use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_CLOSE_TAG;
use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_OPEN_TAG;
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct AvailablePluginsInstructions;
impl ContextualUserFragment for AvailablePluginsInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("plugins.usage_instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -2,6 +2,7 @@ use chrono::DateTime;
use chrono::Utc;
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
pub(crate) struct CurrentTimeReminder {
current_time: DateTime<Utc>,
@@ -20,6 +21,10 @@ impl CurrentTimeReminder {
}
impl ContextualUserFragment for CurrentTimeReminder {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("current_time.reminder".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -2,10 +2,15 @@ use codex_protocol::protocol::ENVIRONMENTS_INSTRUCTIONS_CLOSE_TAG;
use codex_protocol::protocol::ENVIRONMENTS_INSTRUCTIONS_OPEN_TAG;
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
pub(crate) struct EnvironmentsInstructions;
impl ContextualUserFragment for EnvironmentsInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("environments.instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,9 +1,14 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct GuardianFollowupReviewReminder;
impl ContextualUserFragment for GuardianFollowupReviewReminder {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("guardian.followup_review_reminder".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,9 +1,14 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct GuardianNodeReplPolicy;
impl ContextualUserFragment for GuardianNodeReplPolicy {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("guardian.node_repl_policy".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -8,6 +8,7 @@ use serde_json::json;
use super::ContextualUserFragment;
use crate::codex_thread::GuardianAuthorizationVersion;
use crate::guardian::guardian_truncate_text;
use codex_protocol::models::ContentItemKind;
const MAX_RETAINED_REVIEWS: usize = 8;
// Including markers, each rendered fragment stays below 1,000 approximate tokens.
@@ -108,6 +109,10 @@ pub struct GuardianReviewEvidenceFragment {
}
impl ContextualUserFragment for GuardianReviewEvidenceFragment {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("guardian.review_evidence".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct HookAdditionalContext {
@@ -12,6 +13,10 @@ impl HookAdditionalContext {
}
impl ContextualUserFragment for HookAdditionalContext {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("hooks.additional_context".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ImageResizeNoticeSource {
@@ -32,6 +33,10 @@ impl ImageResizeNotice {
}
impl ContextualUserFragment for ImageResizeNotice {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("images.resize_notice".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,6 +1,7 @@
use codex_protocol::AgentPath;
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct InterAgentCompletionMessage {
@@ -20,6 +21,10 @@ impl InterAgentCompletionMessage {
}
impl ContextualUserFragment for InterAgentCompletionMessage {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("multi_agent.inter_agent_completion_message".to_string())
}
fn role(&self) -> &'static str {
"assistant"
}

View File

@@ -1,6 +1,7 @@
use codex_protocol::AgentPath;
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InterAgentMessageType {
@@ -42,6 +43,10 @@ impl InterAgentMessage {
}
impl ContextualUserFragment for InterAgentMessage {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("multi_agent.inter_agent_message".to_string())
}
fn role(&self) -> &'static str {
"assistant"
}

View File

@@ -1,6 +1,7 @@
//! Hidden user-context fragment for extension-owned model steering.
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
use std::error::Error;
use std::fmt;
@@ -76,6 +77,10 @@ impl InternalModelContextFragment {
}
impl ContextualUserFragment for InternalModelContextFragment {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("generic.internal_model_context".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -1,10 +1,15 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
// This warning is not produced anymore but fragment definition is used to filter messaged from old sessions
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct LegacyApplyPatchExecCommandWarning;
impl ContextualUserFragment for LegacyApplyPatchExecCommandWarning {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("apply_patch.legacy_exec_command_warning".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -1,10 +1,15 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
// This warning is not produced anymore but fragment definition is used to filter messaged from old sessions
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct LegacyModelMismatchWarning;
impl ContextualUserFragment for LegacyModelMismatchWarning {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("model_switch.legacy_mismatch_warning".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -1,10 +1,15 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
// This warning is not produced anymore but fragment definition is used to filter messaged from old sessions
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct LegacyUnifiedExecProcessLimitWarning;
impl ContextualUserFragment for LegacyUnifiedExecProcessLimitWarning {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("unified_exec.legacy_process_limit_warning".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ModelSwitchInstructions {
@@ -14,6 +15,10 @@ impl ModelSwitchInstructions {
}
impl ContextualUserFragment for ModelSwitchInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("model_switch.instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,5 +1,6 @@
use super::ContextualUserFragment;
use codex_protocol::config_types::MultiAgentMode;
use codex_protocol::models::ContentItemKind;
use codex_protocol::protocol::MULTI_AGENT_MODE_CLOSE_TAG;
use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG;
@@ -25,6 +26,10 @@ impl MultiAgentModeInstructions {
}
impl ContextualUserFragment for MultiAgentModeInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("multi_agent.mode_instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct MultiAgentRoleInstructions {
@@ -23,6 +24,10 @@ impl MultiAgentRoleInstructions {
}
impl ContextualUserFragment for MultiAgentRoleInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("multi_agent.role_instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
/// Configured multi-agent instructions emitted as a standalone developer message.
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -15,6 +16,10 @@ impl MultiAgentUsageHint {
}
impl ContextualUserFragment for MultiAgentUsageHint {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("multi_agent.usage_hint".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,6 +1,7 @@
use super::ContextualUserFragment;
use codex_protocol::approvals::NetworkPolicyAmendment;
use codex_protocol::approvals::NetworkPolicyRuleAction;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct NetworkRuleSaved {
@@ -18,6 +19,10 @@ impl NetworkRuleSaved {
}
impl ContextualUserFragment for NetworkRuleSaved {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("network_proxy.rule_saved".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -6,6 +6,7 @@ use std::sync::PoisonError;
use codex_features::Feature;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ContentItemKind;
use codex_protocol::user_input::UserInput;
use codex_protocol::user_input::UserInput::Image;
use codex_protocol::user_input::UserInput::Text;
@@ -358,6 +359,10 @@ impl NodeReplReviewEvidenceFragment {
}
impl ContextualUserFragment for NodeReplReviewEvidenceFragment {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("guardian.node_repl_review_evidence".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct PersonalitySpecInstructions {
@@ -12,6 +13,10 @@ impl PersonalitySpecInstructions {
}
impl ContextualUserFragment for PersonalitySpecInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("personality.spec_instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct PluginInstructions {
@@ -12,6 +13,10 @@ impl PluginInstructions {
}
impl ContextualUserFragment for PluginInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("plugins.instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
const MAX_REALTIME_DELEGATION_FIELD_BYTES: usize = 4 * 1024;
const TRUNCATION_MARKER: &str = "";
@@ -31,6 +32,10 @@ impl<'a> RealtimeDelegation<'a> {
}
impl ContextualUserFragment for RealtimeDelegation<'_> {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("realtime_conversation.delegation".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -1,5 +1,6 @@
use super::ContextualUserFragment;
use codex_prompts::END_INSTRUCTIONS;
use codex_protocol::models::ContentItemKind;
use codex_protocol::protocol::REALTIME_CONVERSATION_CLOSE_TAG;
use codex_protocol::protocol::REALTIME_CONVERSATION_OPEN_TAG;
@@ -21,6 +22,10 @@ impl RealtimeEndInstructions {
}
impl ContextualUserFragment for RealtimeEndInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("realtime_conversation.end_instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,5 +1,6 @@
use super::ContextualUserFragment;
use codex_prompts::START_INSTRUCTIONS;
use codex_protocol::models::ContentItemKind;
use codex_protocol::protocol::REALTIME_CONVERSATION_CLOSE_TAG;
use codex_protocol::protocol::REALTIME_CONVERSATION_OPEN_TAG;
@@ -7,6 +8,10 @@ use codex_protocol::protocol::REALTIME_CONVERSATION_OPEN_TAG;
pub(crate) struct RealtimeStartInstructions;
impl ContextualUserFragment for RealtimeStartInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("realtime_conversation.start_instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
use codex_protocol::protocol::REALTIME_CONVERSATION_CLOSE_TAG;
use codex_protocol::protocol::REALTIME_CONVERSATION_OPEN_TAG;
@@ -16,6 +17,10 @@ impl RealtimeStartWithInstructions {
}
impl ContextualUserFragment for RealtimeStartWithInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("realtime_conversation.custom_start_instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
use codex_tools::DiscoverableTool;
const RECOMMENDED_PLUGINS_INTRO: &str =
@@ -26,6 +27,10 @@ impl RecommendedPluginsInstructions {
}
impl ContextualUserFragment for RecommendedPluginsInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("plugins.recommendations".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RolloutBudgetContext {
@@ -6,6 +7,10 @@ pub(crate) struct RolloutBudgetContext {
}
impl ContextualUserFragment for RolloutBudgetContext {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("rollout_budget.remaining_tokens".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,3 +1,4 @@
use codex_protocol::models::ContentItemKind;
use codex_protocol::protocol::AgentStatus;
use super::ContextualUserFragment;
@@ -18,6 +19,10 @@ impl SubagentNotification {
}
impl ContextualUserFragment for SubagentNotification {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("multi_agent.subagent_notification".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -2,6 +2,7 @@ use super::ContextualUserFragment;
use super::world_state::PreviousSectionState;
use super::world_state::WorldStateSection;
use codex_protocol::AgentPath;
use codex_protocol::models::ContentItemKind;
use codex_protocol::protocol::CONTEXT_WINDOW_CLOSE_TAG;
use codex_protocol::protocol::CONTEXT_WINDOW_GUIDANCE_CLOSE_TAG;
use codex_protocol::protocol::CONTEXT_WINDOW_GUIDANCE_OPEN_TAG;
@@ -36,6 +37,10 @@ impl TokenBudgetContext {
}
impl ContextualUserFragment for TokenBudgetContext {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("token_budget.context_window".to_string())
}
fn role(&self) -> &'static str {
"developer"
}
@@ -101,6 +106,10 @@ impl ContextWindowGuidance {
}
impl ContextualUserFragment for ContextWindowGuidance {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("token_budget.context_window_guidance".to_string())
}
fn role(&self) -> &'static str {
"developer"
}
@@ -139,6 +148,10 @@ impl TokenBudgetRemainingContext {
}
impl ContextualUserFragment for TokenBudgetRemainingContext {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("token_budget.remaining_tokens".to_string())
}
fn role(&self) -> &'static str {
"developer"
}
@@ -175,6 +188,10 @@ impl TokenBudgetReminder {
}
impl ContextualUserFragment for TokenBudgetReminder {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("token_budget.reminder".to_string())
}
fn role(&self) -> &'static str {
"developer"
}
@@ -206,6 +223,10 @@ impl AutoCompactFallbackPrompt {
}
impl ContextualUserFragment for AutoCompactFallbackPrompt {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("compaction.auto_fallback_prompt".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct TurnAborted {
@@ -17,6 +18,10 @@ impl TurnAborted {
}
impl ContextualUserFragment for TurnAborted {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("generic.turn_aborted".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -1,4 +1,5 @@
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct UserInstructions {
@@ -7,6 +8,10 @@ pub(crate) struct UserInstructions {
}
impl ContextualUserFragment for UserInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("agents_md.instructions".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -1,6 +1,7 @@
use std::time::Duration;
use super::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct UserShellCommand {
@@ -27,6 +28,10 @@ impl UserShellCommand {
}
impl ContextualUserFragment for UserShellCommand {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("shell.user_command".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -4,6 +4,7 @@ use super::WorldStateSection;
use crate::context::ContextualUserFragment;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
use codex_protocol::models::ContentItemKind;
use codex_protocol::openai_models::CollaborationModeMessages;
use codex_protocol::protocol::COLLABORATION_MODE_CLOSE_TAG;
use codex_protocol::protocol::COLLABORATION_MODE_OPEN_TAG;
@@ -125,6 +126,10 @@ struct CollaborationModeInstructions {
}
impl ContextualUserFragment for CollaborationModeInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("collaboration_mode.instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -6,6 +6,7 @@ use crate::context::environment_context::NetworkContext;
use crate::context::environment_context::push_xml_escaped_text;
use crate::environment_selection::TurnEnvironmentSnapshot;
use crate::session::turn_context::TurnContext;
use codex_protocol::models::ContentItemKind;
use codex_utils_path_uri::PathUri;
use serde::Deserialize;
use serde::Serialize;
@@ -154,6 +155,10 @@ impl WorldStateSection for EnvironmentsState {
}
impl ContextualUserFragment for EnvironmentsState {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("environments.environment_context".to_string())
}
fn role(&self) -> &'static str {
"user"
}
@@ -188,6 +193,10 @@ enum EnvironmentUpdate {
}
impl ContextualUserFragment for RenderedEnvironments {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("environments.environment_context".to_string())
}
fn role(&self) -> &'static str {
"user"
}

View File

@@ -3,6 +3,7 @@ use super::WorldStateHash;
use super::WorldStateSection;
use crate::context::ContextualUserFragment;
use codex_config::Sourced;
use codex_protocol::models::ContentItemKind;
use codex_utils_string::approx_bytes_for_tokens;
use codex_utils_string::approx_tokens_from_byte_count;
use serde::Deserialize;
@@ -21,6 +22,10 @@ pub(crate) struct ManagedDeveloperInstructions {
}
impl ContextualUserFragment for ManagedDeveloperInstructions {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("managed_config.developer_instructions".to_string())
}
fn role(&self) -> &'static str {
"developer"
}

View File

@@ -22,6 +22,7 @@ use codex_extension_api::PreviousWorldStateSection;
use codex_extension_api::RenderedWorldStateFragment;
use codex_extension_api::WorldStateSectionContribution;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ContentItemKind;
use codex_protocol::models::ResponseItem;
use indexmap::IndexMap;
use serde::Deserialize;
@@ -167,25 +168,35 @@ impl ErasedWorldStateSection for ExtensionWorldStateSection {
PreviousSectionState::Unknown => PreviousWorldStateSection::Unknown,
PreviousSectionState::Known(previous) => PreviousWorldStateSection::Known(previous),
};
self.0
.render_diff(previous)
.map(|fragment| Box::new(WorldStateContextFragment(fragment)) as _)
self.0.render_diff(previous).map(|fragment| {
Box::new(WorldStateContextFragment {
fragment,
content_kind: ContentItemKind(format!("{}.instructions", self.0.id())),
}) as _
})
}
}
struct WorldStateContextFragment(RenderedWorldStateFragment);
struct WorldStateContextFragment {
fragment: RenderedWorldStateFragment,
content_kind: ContentItemKind,
}
impl ContextualUserFragment for WorldStateContextFragment {
fn content_kind(&self) -> ContentItemKind {
self.content_kind.clone()
}
fn role(&self) -> &'static str {
self.0.role()
self.fragment.role()
}
fn markers(&self) -> (&'static str, &'static str) {
self.0.markers()
self.fragment.markers()
}
fn body(&self) -> String {
self.0.body().to_string()
self.fragment.body().to_string()
}
fn type_markers() -> (&'static str, &'static str) {

View File

@@ -4,6 +4,7 @@ use super::WorldStateSection;
use crate::context::ContextualUserFragment;
use crate::context::environment_context::push_xml_escaped_text;
use codex_extension_api::RenderedWorldStateFragment;
use codex_protocol::models::ContentItemKind;
use codex_protocol::protocol::TOOLS_CLOSE_TAG;
use codex_protocol::protocol::TOOLS_OPEN_TAG;
use std::collections::BTreeMap;
@@ -97,9 +98,14 @@ impl WorldStateSection for ToolsState {
)
}
};
Some(Box::new(WorldStateContextFragment(
RenderedWorldStateFragment::new("developer", (TOOLS_OPEN_TAG, TOOLS_CLOSE_TAG), body),
)))
Some(Box::new(WorldStateContextFragment {
fragment: RenderedWorldStateFragment::new(
"developer",
(TOOLS_OPEN_TAG, TOOLS_CLOSE_TAG),
body,
),
content_kind: ContentItemKind("tools.deferred_namespaces".to_string()),
}))
}
}

View File

@@ -1,4 +1,6 @@
use super::*;
use codex_context_fragments::AnnotatedContent;
use codex_protocol::models::ContentItemKind;
use pretty_assertions::assert_eq;
use serde::Deserialize;
use serde::Serialize;
@@ -36,6 +38,10 @@ impl WorldStateSection for TestSection {
struct TestFragment(String);
impl ContextualUserFragment for TestFragment {
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("generic.test".to_string())
}
fn role(&self) -> &'static str {
"user"
}
@@ -157,6 +163,38 @@ fn extension_owned_section_uses_its_snapshot_and_renderer() {
);
}
#[test]
fn extension_owned_section_uses_its_stable_id_as_content_kind_feature() {
let mut world_state = WorldState::default();
world_state.add_extension_section(WorldStateSectionContribution::new(
"extension_test",
json!({"value": "after"}),
|_| {
Some(RenderedWorldStateFragment::new(
"developer",
("<extension_test>", "</extension_test>"),
"after",
))
},
));
let rendered = world_state.render_diff(&WorldStateSnapshot::default());
assert_eq!(
rendered
.into_iter()
.map(|fragment| fragment.render_fragment().into_parts())
.collect::<Vec<_>>(),
vec![(
"developer",
AnnotatedContent::input_text(
"<extension_test>after</extension_test>",
ContentItemKind("extension_test.instructions".to_string()),
),
)]
);
}
#[test]
fn missing_retained_fragment_is_rendered_again() {
let mut world_state = WorldState::default();

View File

@@ -9,7 +9,6 @@ use codex_exec_server::FileSystemSandboxContext;
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
use codex_exec_server::ResolvedSelectedCapabilityRoot;
use codex_extension_api::ConfigContributor;
use codex_extension_api::ContentItemKind;
use codex_extension_api::ContextContributor;
use codex_extension_api::ContextualUserFragment;
use codex_extension_api::ExtensionData;
@@ -238,10 +237,7 @@ where
rendered
.fragment
.map(|fragment| {
PromptFragment::developer_capability(
fragment.render(),
ContentItemKind("skills.catalog".to_string()),
)
PromptFragment::developer_capability(fragment.render(), fragment.content_kind())
})
.into_iter()
.collect()

View File

@@ -1,4 +1,5 @@
use codex_extension_api::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
use codex_protocol::protocol::SKILLS_INSTRUCTIONS_CLOSE_TAG;
use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG;
@@ -40,6 +41,10 @@ impl ContextualUserFragment for AvailableSkillsInstructions {
"developer"
}
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("skills.catalog".to_string())
}
fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}
@@ -73,6 +78,10 @@ impl ContextualUserFragment for SkillInstructions {
"user"
}
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("skills.selected_skill_instructions".to_string())
}
fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

View File

@@ -2,6 +2,7 @@ use codex_context_fragments::ContextualUserFragment;
use codex_execpolicy::Policy;
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::models::ContentItemKind;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::format_allow_prefixes;
use codex_protocol::openai_models::ApprovalMessages;
@@ -177,6 +178,10 @@ impl ContextualUserFragment for PermissionsInstructions {
"developer"
}
fn content_kind(&self) -> ContentItemKind {
ContentItemKind("generic.permissions_instructions".to_string())
}
fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}