diff --git a/codex-rs/core/src/guardian/prompt.rs b/codex-rs/core/src/guardian/prompt.rs index 9786a9c0ab..d425379c0e 100644 --- a/codex-rs/core/src/guardian/prompt.rs +++ b/codex-rs/core/src/guardian/prompt.rs @@ -1,9 +1,8 @@ use codex_extension_api::ConversationHistorySnapshot; use codex_features::Feature; +#[cfg(test)] use codex_guardian_context::Budgeted; use codex_guardian_context::CollectedContext; -use codex_guardian_context::ComposedContext; -use codex_guardian_context::ContextPresentation; use codex_guardian_context::ContextProfile; #[cfg(test)] use codex_guardian_context::ConversationTranscriptEntry; @@ -13,10 +12,11 @@ use codex_guardian_context::PlannedActionKind; use codex_guardian_context::SectionError; use codex_guardian_context::SectionHistory; use codex_guardian_context::SectionInput; +#[cfg(test)] pub(crate) use codex_guardian_context::TranscriptCursor as GuardianTranscriptCursor; pub(crate) use codex_guardian_context::TranscriptMode as GuardianPromptMode; -use codex_guardian_context::TranscriptSelection; use codex_guardian_context::default_registry; +use codex_guardian_reviewer::GuardianPromptItems; use codex_protocol::models::ResponseItem; use crate::context::GuardianPermissionContext; @@ -45,12 +45,6 @@ pub(crate) enum GuardianTranscriptHistory<'a> { LoadedParentCheckpoint(&'a dyn ConversationHistorySnapshot), } -pub(crate) struct GuardianPromptItems { - pub(crate) context: ComposedContext, - pub(crate) transcript_cursor: GuardianTranscriptCursor, - pub(crate) node_repl_evidence_sequence: u64, -} - /// Builds the guardian user content items from: /// - a compact transcript for authorization and local context /// - the exact action JSON being proposed for approval @@ -210,45 +204,18 @@ pub(crate) async fn build_guardian_prompt_items_with_parent_turn( permissions.as_ref(), node_repl_context.as_ref(), )?; - let (selection, transcript_cursor) = mode.select( - sections.transcript_entries(), + codex_guardian_reviewer::compose_guardian_prompt( + sections, + &profile, + mode, if use_parent_checkpoint { history.history_version() } else { history.review_history_version() }, - ); - let session_id = session.thread_id.to_string(); - let (transcript_entries, offset, placeholder, presentation) = match selection { - TranscriptSelection::Full(entries) => ( - entries, - 0, - "", - ContextPresentation::SyncFull { - session_id: &session_id, - }, - ), - TranscriptSelection::Delta { entries, offset } => ( - entries, - offset, - "", - ContextPresentation::SyncDelta { - session_id: &session_id, - }, - ), - }; - let mut transcript = profile.prepare_transcript(transcript_entries, offset); - if transcript_entries.is_empty() { - transcript.items.push(Budgeted::required( - codex_guardian_context::TranscriptContent::Text(placeholder.to_owned()), - )); - } - let context = sections.compose(presentation, transcript)?; - Ok(GuardianPromptItems { - context, - transcript_cursor, + &session.thread_id.to_string(), node_repl_evidence_sequence, - }) + ) } /// Exercises the sync profile through the host's existing transcript tests. diff --git a/codex-rs/ext/guardian-reviewer/src/lib.rs b/codex-rs/ext/guardian-reviewer/src/lib.rs index 6a69dbcb16..8bca381652 100644 --- a/codex-rs/ext/guardian-reviewer/src/lib.rs +++ b/codex-rs/ext/guardian-reviewer/src/lib.rs @@ -19,6 +19,7 @@ mod metrics; mod model; mod outcome; mod pool; +mod prompt; mod reporting; mod retry; mod review; @@ -58,6 +59,8 @@ pub use pool::ReviewerRequest; pub use pool::ReviewerSession; pub use pool::ReviewerTasks; pub use pool::SessionDisposition; +pub use prompt::GuardianPromptItems; +pub use prompt::compose_guardian_prompt; pub use review::ReviewHost; diff --git a/codex-rs/ext/guardian-reviewer/src/prompt.rs b/codex-rs/ext/guardian-reviewer/src/prompt.rs new file mode 100644 index 0000000000..6e0bc2c76a --- /dev/null +++ b/codex-rs/ext/guardian-reviewer/src/prompt.rs @@ -0,0 +1,55 @@ +//! Composes synchronous review prompts from the host's captured context sections. +//! Full/delta selection, empty-transcript text, and cursor accounting preserve the existing layout. + +use codex_guardian_context::Budgeted; +use codex_guardian_context::CollectedContext; +use codex_guardian_context::ComposedContext; +use codex_guardian_context::ContextPresentation; +use codex_guardian_context::ContextProfile; +use codex_guardian_context::TranscriptCursor as GuardianTranscriptCursor; +use codex_guardian_context::TranscriptMode as GuardianPromptMode; +use codex_guardian_context::TranscriptSelection; + +pub struct GuardianPromptItems { + pub context: ComposedContext, + pub transcript_cursor: GuardianTranscriptCursor, + pub node_repl_evidence_sequence: u64, +} + +pub fn compose_guardian_prompt( + sections: CollectedContext, + profile: &ContextProfile, + mode: GuardianPromptMode, + history_version: u64, + session_id: &str, + node_repl_evidence_sequence: u64, +) -> anyhow::Result { + let (selection, transcript_cursor) = + mode.select(sections.transcript_entries(), history_version); + let (transcript_entries, offset, placeholder, presentation) = match selection { + TranscriptSelection::Full(entries) => ( + entries, + 0, + "", + ContextPresentation::SyncFull { session_id }, + ), + TranscriptSelection::Delta { entries, offset } => ( + entries, + offset, + "", + ContextPresentation::SyncDelta { session_id }, + ), + }; + let mut transcript = profile.prepare_transcript(transcript_entries, offset); + if transcript_entries.is_empty() { + transcript.items.push(Budgeted::required( + codex_guardian_context::TranscriptContent::Text(placeholder.to_owned()), + )); + } + let context = sections.compose(presentation, transcript)?; + Ok(GuardianPromptItems { + context, + transcript_cursor, + node_repl_evidence_sequence, + }) +}