mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
## What changed - Retain captured root conversation sources until synchronous preparation applies request limits. Keep asynchronous snapshots limited to the bounded messages. - Move approval-reason truncation and tool-description escaping and truncation from composition into preparation. - Extract synchronous image preparation and admission into `guardian-context`, preserving the prompt token cap and model context-window checks. GitOrigin-RevId: d3a864c77d8f16bd3e77df436d80c4d2738c43cc
374 lines
15 KiB
Rust
374 lines
15 KiB
Rust
//! Shared context sections for synchronous Guardian review and asynchronous scoring.
|
|
//!
|
|
//! Transcript collection and bounded host-owned history are also available directly,
|
|
//! without section composition.
|
|
//! Contributor failures abort collection without returning partial context.
|
|
//! Sections preserve source-specific evidence and share prompt framing, while
|
|
//! profiles retain the consumer-specific transcript policy. Shared full/delta selection
|
|
//! proposes cursors; hosts own their admission, compaction and request lifecycles.
|
|
//! JSON records cache their rendering and shared-estimator cost through admission.
|
|
//! Line mode admits rendered text to preserve its existing whole-entry truncation.
|
|
//! Registered contributors declare their scope once and are collected only for
|
|
//! matching context consumers. History and collection settings are borrowed for
|
|
//! each request so the default registry can be reused without retaining state.
|
|
|
|
use std::sync::Arc;
|
|
use std::sync::LazyLock;
|
|
|
|
use codex_protocol::models::ResponseItem;
|
|
|
|
use authorization::RootConversationSection;
|
|
use authorization::TrustedUserAnswersSection;
|
|
use retained_instructions::RetainedUserInstructionsSection;
|
|
use sender_user_messages::SenderUserMessagesSection;
|
|
use transcript::ConversationTranscriptSection;
|
|
|
|
mod native_tool_call;
|
|
|
|
pub use action::ActionPresentation;
|
|
pub use action::PlannedAction;
|
|
pub use action::PlannedActionKind;
|
|
pub use action::action_for_review;
|
|
pub use authorization::GuardianRootMessage;
|
|
pub use authorization::RootConversationContent;
|
|
pub use preparation::root::RootAssistantCandidate;
|
|
pub use preparation::root::RootAssistantContent;
|
|
pub use preparation::root::RootConversationCandidates;
|
|
pub use preparation::root::RootConversationInput;
|
|
pub use preparation::root::RootConversationSource;
|
|
pub use preparation::root::RootDeliveredMessage;
|
|
pub use preparation::root::RootMessageCandidate;
|
|
pub use preparation::root::RootRetainedAssistant;
|
|
pub use section::ContextSection;
|
|
|
|
pub use entry::ConversationTranscriptEntry;
|
|
pub use entry::ConversationTranscriptEntryKind;
|
|
pub use entry::RetainedTranscriptSource;
|
|
pub use entry::TranscriptContent;
|
|
pub use history::TranscriptHistory;
|
|
pub use transcript::ConversationTranscriptConfig;
|
|
pub use transcript::ConversationTranscriptOptions;
|
|
pub use transcript::MANUAL_APPROVAL_DEVELOPER_PREFIX;
|
|
pub use transcript::TranscriptEntryLimits;
|
|
pub use transcript::TranscriptRetentionConfig;
|
|
pub use transcript::collect_transcript;
|
|
pub use transcript::is_inherited_manual_approval;
|
|
pub use truncation::truncate_text;
|
|
|
|
mod verified_answers;
|
|
pub use verified_answers::RenderedVerifiedAnswers;
|
|
pub use verified_answers::TrustedUserAnswer;
|
|
pub use verified_answers::collect_verified_answer_candidates;
|
|
pub use verified_answers::render_original_verified_answer;
|
|
pub use verified_answers::render_verified_answer;
|
|
pub use verified_answers::render_verified_answers;
|
|
|
|
mod retained_instructions;
|
|
mod sender_user_messages;
|
|
pub use retained_instructions::RetainedInstructionItem;
|
|
pub use retained_instructions::retained_assistant_message;
|
|
|
|
mod action;
|
|
mod enforcement;
|
|
pub(crate) use enforcement::BudgetPriority;
|
|
pub use enforcement::Budgeted;
|
|
pub use enforcement::HistoryTruncation;
|
|
pub(crate) use enforcement::Retention;
|
|
mod budget;
|
|
mod composition;
|
|
mod cursor;
|
|
pub use budget::DEFAULT_MAX_INPUT_TOKENS;
|
|
pub use budget::REQUEST_TOKENS_BOUNDARIES;
|
|
pub use budget::REQUEST_TOKENS_METRIC;
|
|
pub use budget::RequestBudget;
|
|
pub use budget::SECTION_COST_BOUNDARIES;
|
|
pub use budget::SECTION_COST_METRIC;
|
|
pub use budget::SectionCost;
|
|
pub use budget::effective_input_token_limit;
|
|
pub use budget::estimate_input_tokens;
|
|
pub use budget::estimate_input_tokens_from_serialized_len;
|
|
pub use budget::request_token_bucket;
|
|
pub use cursor::TranscriptCursor;
|
|
pub use cursor::TranscriptMode;
|
|
pub use cursor::TranscriptSelection;
|
|
mod preparation;
|
|
mod profile;
|
|
pub use codex_protocol::TranscriptFormat;
|
|
pub use composition::CollectedContext;
|
|
pub use composition::ComposedContext;
|
|
pub use composition::ContextPresentation;
|
|
pub use composition::PreparedTranscript;
|
|
pub use preparation::prepare_sync_images;
|
|
pub use preparation::root::PreparedRootContext;
|
|
pub use preparation::root::prepare_root_context;
|
|
pub use profile::ContextProfile;
|
|
mod authorization;
|
|
mod entry;
|
|
mod history;
|
|
mod images;
|
|
mod node_repl;
|
|
pub use node_repl::NodeReplContext;
|
|
pub use node_repl::NodeReplResponse;
|
|
pub use node_repl::NodeReplReviewEvidenceMode;
|
|
pub use node_repl::RenderedNodeReplEvidence;
|
|
mod permissions;
|
|
pub use images::TranscriptImageInput;
|
|
pub use images::TranscriptImages;
|
|
mod guardian_tool_descriptions;
|
|
pub use guardian_tool_descriptions::GuardianToolDescriptions;
|
|
mod trusted_skills;
|
|
mod trusted_tool;
|
|
pub use trusted_skills::TrustedSkills;
|
|
pub use trusted_tool::TrustedTool;
|
|
pub use trusted_tool::TrustedToolSource;
|
|
mod reviews;
|
|
pub use reviews::MAX_PREVIOUS_REVIEWS;
|
|
pub use reviews::PreviousReview;
|
|
pub use reviews::PreviousReviews;
|
|
pub use reviews::RenderedReviewEvidence;
|
|
pub use reviews::ReviewEvidence;
|
|
pub use reviews::render_review_evidence;
|
|
pub use truncation::TruncationObservation;
|
|
mod section;
|
|
pub use permissions::PermissionContext;
|
|
mod transcript;
|
|
mod transcript_record;
|
|
pub use transcript_record::TRANSCRIPT_JSON_INSTRUCTIONS;
|
|
pub use transcript_record::TranscriptRecord;
|
|
mod truncation;
|
|
|
|
/// Consumer for which a Guardian context is composed.
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum ContextTarget {
|
|
/// The reusable synchronous Guardian reviewer.
|
|
Sync,
|
|
/// The asynchronous Guardian action scorer.
|
|
Async,
|
|
}
|
|
|
|
/// Consumers to which a context section contributes.
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum SectionScope {
|
|
/// Include the section in both synchronous review and asynchronous scoring.
|
|
Shared,
|
|
/// Include the section only in synchronous review.
|
|
SyncOnly,
|
|
/// Include the section only in asynchronous scoring.
|
|
AsyncOnly,
|
|
}
|
|
|
|
impl SectionScope {
|
|
/// Whether this section is included for the requested context consumer.
|
|
pub fn includes(self, target: ContextTarget) -> bool {
|
|
match self {
|
|
Self::Shared => true,
|
|
Self::SyncOnly => matches!(target, ContextTarget::Sync),
|
|
Self::AsyncOnly => matches!(target, ContextTarget::Async),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Borrowed host inputs available while one Guardian context section is built.
|
|
#[derive(Clone, Copy)]
|
|
pub struct SectionInput<'a> {
|
|
/// Consumer for which the host is collecting context sections.
|
|
pub target: ContextTarget,
|
|
/// Parent conversation history available to this contribution.
|
|
pub history: &'a dyn SectionHistory,
|
|
/// Evidence sources and per-entry limits for this collection.
|
|
pub transcript: &'a ConversationTranscriptConfig,
|
|
/// Bounded root evidence resolved by the host; empty when not applicable.
|
|
pub root_conversation: &'a [GuardianRootMessage],
|
|
/// Sync originals retained until request preparation; preferred over the bounded view.
|
|
pub root_conversation_candidates: Option<&'a RootConversationInput>,
|
|
/// Role-labeled sync answer candidates or already-bounded answers from other consumers.
|
|
pub trusted_user_answers: &'a [TrustedUserAnswer],
|
|
/// Exact action JSON; sync composition bounds the reason and descriptions.
|
|
pub planned_action: Option<&'a PlannedAction>,
|
|
/// Sync-only restrictions resolved from the parent execution environment.
|
|
pub permissions: Option<&'a PermissionContext>,
|
|
/// Size-validated, host-attested reviews selected against the action's authorization snapshot.
|
|
pub previous_reviews: Option<&'a PreviousReviews>,
|
|
/// Metadata verified by the host for the exact action being classified.
|
|
pub trusted_tool: Option<&'a TrustedTool>,
|
|
/// Current-turn and delegated skill paths verified and bounded by the host.
|
|
pub trusted_skill_paths: &'a [String],
|
|
/// Optional consumer image policy; no history images are added implicitly.
|
|
pub images: Option<TranscriptImageInput<'a>>,
|
|
/// Sync-only frozen REPL snapshot selected by the host's delivery cursor.
|
|
pub node_repl: Option<&'a NodeReplContext<'a>>,
|
|
}
|
|
|
|
/// Supplies repeatable, zero-copy access to a host-owned conversation snapshot.
|
|
///
|
|
/// Implementations return a fresh iterator for every call so independently
|
|
/// registered contributors can inspect the same history without cloning its
|
|
/// response items or taking ownership away from the host.
|
|
pub trait SectionHistory: Send + Sync {
|
|
/// Returns borrowed response items in their original conversation order.
|
|
fn items(&self) -> Box<dyn Iterator<Item = &ResponseItem> + Send + '_>;
|
|
|
|
/// Original source metadata stays attached to the exact unshortened history item.
|
|
/// Legacy providers cannot establish completeness from a message ID alone.
|
|
fn items_with_sources(
|
|
&self,
|
|
) -> Box<dyn Iterator<Item = (&ResponseItem, Option<&codex_history::RetainedSource>)> + Send + '_>
|
|
{
|
|
Box::new(self.items().map(|item| (item, None)))
|
|
}
|
|
|
|
/// Bounded host-owned facts from the same snapshot as the current items.
|
|
fn retained_context(&self) -> Option<&codex_history::RetainedContext> {
|
|
None
|
|
}
|
|
|
|
/// Renders an original complete assistant message without request-size selection.
|
|
/// Sync preparation applies admission after collection; hosts preserve their fragment boundary.
|
|
fn render_original_assistant(
|
|
&self,
|
|
message: &codex_history::RetainedUserMessage,
|
|
) -> Option<GuardianRootMessage> {
|
|
message
|
|
.complete
|
|
.then(|| GuardianRootMessage::Assistant(message.text.clone()))
|
|
}
|
|
|
|
/// Renders one bounded retained assistant message through the host's
|
|
/// contextual-fragment boundary. Hosts with no fragment layer use the
|
|
/// shared role-labeled rendering while preserving the same size limit.
|
|
fn render_retained_assistant(
|
|
&self,
|
|
message: &codex_history::RetainedUserMessage,
|
|
) -> Option<GuardianRootMessage> {
|
|
retained_assistant_message(message)
|
|
}
|
|
}
|
|
|
|
impl SectionHistory for Vec<ResponseItem> {
|
|
fn items(&self) -> Box<dyn Iterator<Item = &ResponseItem> + Send + '_> {
|
|
Box::new(self.iter())
|
|
}
|
|
}
|
|
|
|
impl<const LENGTH: usize> SectionHistory for [ResponseItem; LENGTH] {
|
|
fn items(&self) -> Box<dyn Iterator<Item = &ResponseItem> + Send + '_> {
|
|
Box::new(self.iter())
|
|
}
|
|
}
|
|
|
|
/// Supplies one independently scoped section to Guardian context assembly.
|
|
///
|
|
/// Implementations declare whether they apply to synchronous review,
|
|
/// asynchronous scoring, or both. The registry filters contributors by scope
|
|
/// before invoking them. Contributors distinguish sections that do not apply
|
|
/// from required evidence that could not be collected.
|
|
/// Keep request-specific settings and history in [`SectionInput`] so the same
|
|
/// contributor can serve concurrent reviews without retaining stale state.
|
|
pub trait SectionContributor: Send + Sync {
|
|
/// Guardian consumers that should receive this contribution.
|
|
fn scope(&self) -> SectionScope;
|
|
|
|
/// Builds this section using the host's current conversation snapshot.
|
|
///
|
|
/// Return `Ok(None)` only when this section is optional or does not apply.
|
|
/// Missing required evidence must return `Err`; callers must not review a
|
|
/// partial context as though collection succeeded.
|
|
fn contribute(&self, input: &SectionInput<'_>) -> Result<Option<ContextSection>, SectionError>;
|
|
}
|
|
|
|
/// A section could not provide the evidence needed for a valid review context.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub enum SectionError {
|
|
/// One native message or tool-call ID identifies different evidence.
|
|
ConflictingNativeMessage,
|
|
/// Evidence required by this contributor for the current input is missing.
|
|
MissingRequiredEvidence { section: &'static str },
|
|
/// A section cannot be delivered by the requested consumer.
|
|
UnsupportedDelivery { section: &'static str },
|
|
/// Supplied evidence exceeds the section's count or rendered-size limit.
|
|
EvidenceLimitExceeded { section: &'static str },
|
|
}
|
|
|
|
impl std::fmt::Display for SectionError {
|
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::ConflictingNativeMessage => {
|
|
formatter.write_str("conflicting native messages or tool calls share an ID")
|
|
}
|
|
Self::MissingRequiredEvidence { section } => {
|
|
write!(formatter, "missing required evidence for section {section}")
|
|
}
|
|
Self::UnsupportedDelivery { section } => {
|
|
write!(formatter, "unsupported delivery for section {section}")
|
|
}
|
|
Self::EvidenceLimitExceeded { section } => {
|
|
write!(formatter, "evidence exceeds limits for section {section}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for SectionError {}
|
|
|
|
/// Ordered collection of independently scoped Guardian section contributors.
|
|
#[derive(Clone, Default)]
|
|
pub struct SectionRegistry {
|
|
contributors: Vec<Arc<dyn SectionContributor>>,
|
|
}
|
|
|
|
/// Shared, process-lifetime registry of built-in Guardian sections.
|
|
///
|
|
/// Contributors store no conversation or configuration state. Each collection
|
|
/// borrows the current history and settings from [`SectionInput`], so callers
|
|
/// can reuse this registry across threads, model changes, and review targets.
|
|
pub fn default_registry() -> &'static SectionRegistry {
|
|
static REGISTRY: LazyLock<SectionRegistry> = LazyLock::new(|| {
|
|
let mut registry = SectionRegistry::default();
|
|
registry.register(reviews::PreviousReviewsSection);
|
|
registry.register(trusted_tool::TrustedToolSection);
|
|
registry.register(trusted_skills::TrustedSkillsSection);
|
|
registry.register(RootConversationSection);
|
|
registry.register(SenderUserMessagesSection);
|
|
registry.register(RetainedUserInstructionsSection);
|
|
registry.register(TrustedUserAnswersSection);
|
|
registry.register(ConversationTranscriptSection);
|
|
registry.register(images::TranscriptImagesSection);
|
|
registry.register(node_repl::NodeReplEvidenceSection);
|
|
registry.register(permissions::PermissionContextSection);
|
|
registry.register(action::PlannedActionSection(SectionScope::Shared));
|
|
registry.register(action::PlannedActionSection(SectionScope::SyncOnly));
|
|
registry
|
|
});
|
|
®ISTRY
|
|
}
|
|
|
|
impl SectionRegistry {
|
|
/// Adds a contributor to the end of the section collection order.
|
|
pub fn register(&mut self, contributor: impl SectionContributor + 'static) {
|
|
self.contributors.push(Arc::new(contributor));
|
|
}
|
|
|
|
/// Collects evidence for host transcript selection and shared composition.
|
|
pub fn prepare(&self, input: &SectionInput<'_>) -> Result<CollectedContext, SectionError> {
|
|
Ok(CollectedContext {
|
|
sections: self.collect(input)?,
|
|
})
|
|
}
|
|
|
|
/// Collects applicable sections in their original registration order.
|
|
///
|
|
/// Stops at the first error without returning any partial context. The host
|
|
/// decides whether to fall back to synchronous review or deny approval.
|
|
pub fn collect(&self, input: &SectionInput<'_>) -> Result<Vec<ContextSection>, SectionError> {
|
|
self.contributors
|
|
.iter()
|
|
.filter(|contributor| contributor.scope().includes(input.target))
|
|
.filter_map(|contributor| contributor.contribute(input).transpose())
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "registry_tests.rs"]
|
|
mod registry_tests;
|