File size: 8,212 Bytes
52a9af3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | //! Hosts approval decisions and the isolated synchronous reviewer.
//! The extension chooses policy and evidence; core enforces permissions and mandatory
//! review requirements. Each approval retains its issuing context and cancellation.
mod approval_request;
mod coverage;
mod decision;
mod feedback;
mod input_budget;
mod prompt;
pub(crate) use input_budget::PendingReviewContext;
pub(crate) use input_budget::check_pending as check_pending_guardian_input;
pub(crate) use input_budget::finalize as finalize_guardian_input;
mod request_budget;
pub(crate) use request_budget::ExhaustedReviewBudget;
pub(crate) use request_budget::check_prompt as check_guardian_prompt_budget;
pub(crate) use request_budget::observe as observe_guardian_request;
mod review;
mod review_session;
mod reviewer_config;
pub(crate) use reviewer_config::resolve_review_model;
mod runtime;
#[cfg(test)]
pub(crate) mod test_host;
use codex_protocol::items::ModelInvocationContext;
use std::sync::Arc;
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::GuardianAssessmentOutcome;
use crate::environment_selection::TurnEnvironmentSnapshot;
use crate::session::step_context::StepContext;
use crate::session::step_settings::ResolvedStepSettings;
use crate::session::turn_context::TurnContext;
use crate::tools::sandboxing::ApprovalRequestReasons;
pub(crate) use approval_request::GuardianApprovalRequest;
pub(crate) use approval_request::GuardianMcpAnnotations;
pub(crate) use approval_request::GuardianNetworkAccessTrigger;
#[cfg(test)]
pub(crate) use approval_request::guardian_approval_request_to_json;
pub(crate) use codex_guardian_reviewer::guardian_timeout_message;
pub(crate) use decision::decide_approval;
pub(crate) use decision::spawn_approval_decision;
pub(crate) use prompt::BUNDLED_GUARDIAN_POLICY;
pub(crate) use prompt::BUNDLED_GUARDIAN_POLICY_TEMPLATE;
pub(crate) use prompt::guardian_truncate_text;
pub(crate) use review::GuardianReviewOptions;
pub(crate) use review::is_basic_session_source;
pub(crate) use review::new_guardian_review_id;
pub(crate) use review::routes_approval_policy_to_guardian;
pub use review_session::GuardianReviewSession;
pub(crate) use review_session::GuardianReviewSessionManager;
pub use review_session::GuardianReviewState;
pub use review_session::PreparedGuardianContext;
pub use review_session::prepare_review_prewarm;
pub(crate) use review_session::prompt_cache_key_override_for_review_session;
pub(crate) use runtime::ReviewAction;
pub(crate) use codex_guardian_reviewer::REVIEW_TIMEOUT as GUARDIAN_REVIEW_TIMEOUT;
pub(crate) const GUARDIAN_REVIEWER_NAME: &str = "guardian";
pub(crate) const AUTO_REVIEW_DENIED_ACTION_APPROVAL_DEVELOPER_PREFIX: &str =
codex_guardian_context::MANUAL_APPROVAL_DEVELOPER_PREFIX;
const GUARDIAN_MAX_TOOL_ENTRY_TOKENS: usize = codex_guardian_context::ContextProfile::synchronous()
.transcript
.entry_limits
.tool_tokens;
pub(crate) const GUARDIAN_MAX_ROOT_MESSAGE_TOKENS: usize = 900;
pub(crate) const GUARDIAN_MAX_NODE_REPL_TOOL_RESULT_TOKENS: usize = 6_000;
/// Captures review inputs from the issuing step without retaining its MCP bindings or tool router.
/// Background network approvals and Unix interception use the active task's resolved settings.
/// Startup reviewer prewarming intentionally uses turn-only inputs because it has no issuing step.
///
/// MCP elicitation reviews continue to use turn-only inputs.
#[derive(Clone)]
pub(crate) struct GuardianReviewContext {
/// The latest response ID received in this turn when review was requested.
pub(crate) parent_response_id: Option<String>,
turn: Arc<TurnContext>,
environments: TurnEnvironmentSnapshot,
// Model and reasoning inputs are carried for the follow-up Guardian and V2 migrations.
pub(crate) model_info: Arc<ModelInfo>,
pub(crate) reasoning_effort: Option<ReasoningEffort>,
pub(crate) reasoning_summary: ReasoningSummary,
pub(crate) personality: Option<Personality>,
pub(crate) approval_policy: AskForApproval,
pub(crate) approvals_reviewer: ApprovalsReviewer,
}
impl GuardianReviewContext {
pub(crate) fn model_context(&self) -> ModelInvocationContext {
ModelInvocationContext {
model_slug: self.model_info.slug.clone(),
reasoning_effort: self
.reasoning_effort
.as_ref()
.or(self.model_info.default_reasoning_level.as_ref())
.map(ToString::to_string),
}
}
pub(crate) fn from_resolved_settings(
turn: Arc<TurnContext>,
settings: &ResolvedStepSettings,
environments: &TurnEnvironmentSnapshot,
) -> Self {
Self {
parent_response_id: turn
.extension_data
.get::<codex_api::ResponseId>()
.map(|id| id.0.clone()),
environments: environments.clone(),
model_info: Arc::clone(&settings.model_info),
reasoning_effort: settings.reasoning_effort().cloned(),
reasoning_summary: settings.reasoning_summary,
personality: settings.personality(),
approval_policy: settings.approval_policy(),
approvals_reviewer: settings.approvals_reviewer(),
turn,
}
}
pub(crate) fn turn(&self) -> &Arc<TurnContext> {
&self.turn
}
pub(crate) fn environments(&self) -> &TurnEnvironmentSnapshot {
&self.environments
}
}
impl From<&Arc<StepContext>> for GuardianReviewContext {
fn from(step: &Arc<StepContext>) -> Self {
Self {
parent_response_id: step
.turn
.extension_data
.get::<codex_api::ResponseId>()
.map(|id| id.0.clone()),
turn: Arc::clone(&step.turn),
environments: step.environments.clone(),
model_info: Arc::clone(&step.settings.model_info),
reasoning_effort: step.settings.reasoning_effort().cloned(),
reasoning_summary: step.settings.reasoning_summary,
personality: step.settings.personality(),
approval_policy: step.settings.approval_policy(),
approvals_reviewer: step.settings.approvals_reviewer(),
}
}
}
impl From<Arc<TurnContext>> for GuardianReviewContext {
fn from(turn: Arc<TurnContext>) -> Self {
Self {
parent_response_id: turn
.extension_data
.get::<codex_api::ResponseId>()
.map(|id| id.0.clone()),
environments: turn.environments.clone(),
model_info: Arc::clone(turn.model_info()),
reasoning_effort: turn.reasoning_effort().cloned(),
reasoning_summary: turn.reasoning_summary(),
personality: turn.personality(),
approval_policy: turn.approval_policy(),
approvals_reviewer: turn.config.approvals_reviewer,
turn,
}
}
}
impl From<&Arc<TurnContext>> for GuardianReviewContext {
fn from(turn: &Arc<TurnContext>) -> Self {
Self::from(Arc::clone(turn))
}
}
#[cfg(test)]
use codex_guardian_reviewer::guardian_output_schema;
pub(crate) use approval_request::format_guardian_action_pretty;
#[cfg(test)]
use approval_request::guardian_assessment_action;
#[cfg(test)]
use approval_request::guardian_request_turn_id;
#[cfg(test)]
use codex_guardian_reviewer::GuardianReviewOutcome;
#[cfg(test)]
use prompt::GuardianPromptMode;
#[cfg(test)]
use prompt::GuardianTranscriptCursor;
#[cfg(test)]
use prompt::build_guardian_prompt_items;
#[cfg(test)]
use prompt::build_guardian_prompt_items_with_parent_turn;
#[cfg(test)]
use prompt::render_guardian_transcript_entries;
#[cfg(test)]
use review::run_guardian_review_session_with_retry as run_guardian_review_session_for_test;
#[cfg(test)]
use review_session::build_guardian_review_session_config as build_guardian_review_session_config_for_test;
#[cfg(test)]
mod tests;
|