Codex Handbook
core/src/session/review.rs 185 lines
use super::*;use codex_core_skills::HostLoadedSkills;use codex_protocol::openai_models::ToolMode;use std::sync::atomic::AtomicBool;/// Spawn a review thread using the given prompt.pub(super) async fn spawn_review_thread(    sess: Arc<Session>,    config: Arc<Config>,    parent_turn_context: Arc<TurnContext>,    sub_id: String,    resolved: crate::review_prompts::ResolvedReviewRequest,) {    let model = config        .review_model        .clone()        .unwrap_or_else(|| parent_turn_context.model_info.slug.clone());    let review_model_info = sess        .services        .models_manager        .get_model_info(&model, &config.to_models_manager_config())        .await;    // For reviews, disable web_search and view_image regardless of global settings.    let mut review_features = sess.features.clone();    let _ = review_features.disable(Feature::WebSearchRequest);    let _ = review_features.disable(Feature::WebSearchCached);    let _ = review_features.disable(Feature::Goals);    let review_web_search_mode = WebSearchMode::Disabled;    let available_models = sess        .services        .models_manager        .list_models(RefreshStrategy::OnlineIfUncached)        .await;    let unified_exec_shell_mode = UnifiedExecShellMode::for_session(        codex_tools::unified_exec_feature_mode_for_features(review_features.get()),        crate::tools::tool_user_shell_type(sess.services.user_shell.as_ref()),        sess.services.shell_zsh_path.as_ref(),        sess.services.main_execve_wrapper_exe.as_ref(),    );    let review_prompt = resolved.prompt.clone();    let provider = parent_turn_context.provider.clone();    let auth_manager = parent_turn_context.auth_manager.clone();    let model_info = review_model_info.clone();    // Build per‑turn client with the requested model/family.    let mut per_turn_config = (*config).clone();    per_turn_config.model = Some(model.clone());    per_turn_config.features = review_features.clone();    let tool_mode = model_info.tool_mode.unwrap_or_else(|| {        if per_turn_config.features.enabled(Feature::CodeModeOnly) {            ToolMode::CodeModeOnly        } else if per_turn_config.features.enabled(Feature::CodeMode) {            ToolMode::CodeMode        } else {            ToolMode::Direct        }    });    if let Err(err) = per_turn_config.web_search_mode.set(review_web_search_mode) {        let fallback_value = per_turn_config.web_search_mode.value();        tracing::warn!(            error = %err,            ?review_web_search_mode,            ?fallback_value,            "review web_search_mode is disallowed by requirements; keeping constrained value"        );    }    let session_telemetry = parent_turn_context        .session_telemetry        .clone()        .with_model(model.as_str(), review_model_info.slug.as_str());    let auth_manager_for_context = auth_manager.clone();    let provider_for_context = provider.clone();    let session_telemetry_for_context = session_telemetry.clone();    let reasoning_effort = per_turn_config.model_reasoning_effort.clone();    let reasoning_summary = per_turn_config        .model_reasoning_summary        .unwrap_or(model_info.default_reasoning_summary);    let session_source = parent_turn_context.session_source.clone();    let forked_from_thread_id = {        let state = sess.state.lock().await;        state.session_configuration.forked_from_thread_id    };    let per_turn_config = Arc::new(per_turn_config);    let review_turn_id = sub_id.to_string();    let turn_metadata_state = Arc::new(TurnMetadataState::new(        sess.session_id().to_string(),        sess.thread_id().to_string(),        forked_from_thread_id,        parent_turn_context.parent_thread_id,        &session_source,        review_turn_id.clone(),        #[allow(deprecated)]        parent_turn_context.cwd.clone(),        &parent_turn_context.permission_profile,        parent_turn_context.windows_sandbox_level,        parent_turn_context.network.is_some(),    ));    let extension_data = Arc::new(codex_extension_api::ExtensionData::new(        review_turn_id.clone(),    ));    extension_data.insert(HostLoadedSkills::new(        parent_turn_context.turn_skills.outcome.clone(),    ));    let review_turn_context = TurnContext {        sub_id: review_turn_id.clone(),        trace_id: current_span_trace_id(),        realtime_active: parent_turn_context.realtime_active,        config: per_turn_config,        auth_manager: auth_manager_for_context,        model_info: model_info.clone(),        comp_hash: model_info.comp_hash.clone(),        tool_mode,        session_telemetry: session_telemetry_for_context,        provider: provider_for_context,        reasoning_effort,        reasoning_summary,        session_source,        parent_thread_id: parent_turn_context.parent_thread_id,        thread_source: parent_turn_context.thread_source.clone(),        environments: parent_turn_context.environments.clone(),        available_models,        unified_exec_shell_mode,        features: review_features,        ghost_snapshot: parent_turn_context.ghost_snapshot.clone(),        current_date: parent_turn_context.current_date.clone(),        timezone: parent_turn_context.timezone.clone(),        app_server_client_name: parent_turn_context.app_server_client_name.clone(),        developer_instructions: None,        user_instructions: None,        compact_prompt: parent_turn_context.compact_prompt.clone(),        collaboration_mode: parent_turn_context.collaboration_mode.clone(),        multi_agent_version: MultiAgentVersion::Disabled,        personality: parent_turn_context.personality,        approval_policy: parent_turn_context.approval_policy.clone(),        permission_profile: parent_turn_context.permission_profile(),        network: parent_turn_context.network.clone(),        windows_sandbox_level: parent_turn_context.windows_sandbox_level,        shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),        #[allow(deprecated)]        cwd: parent_turn_context.cwd.clone(),        final_output_json_schema: None,        codex_self_exe: parent_turn_context.codex_self_exe.clone(),        codex_linux_sandbox_exe: parent_turn_context.codex_linux_sandbox_exe.clone(),        dynamic_tools: parent_turn_context.dynamic_tools.clone(),        truncation_policy: model_info.truncation_policy.into(),        turn_metadata_state,        extension_data,        turn_skills: TurnSkillsContext::new(parent_turn_context.turn_skills.outcome.clone()),        turn_timing_state: Arc::new(TurnTimingState::default()),        terminal_error: Arc::new(Mutex::new(None)),        server_model_warning_emitted: AtomicBool::new(false),        model_verification_emitted: AtomicBool::new(false),    };    // Seed the child task with the review prompt as the initial user message.    let input = vec![TurnInput::UserInput {        content: vec![UserInput::Text {            text: review_prompt,            // Review prompt is synthesized; no UI element ranges to preserve.            text_elements: Vec::new(),        }],        client_id: None,    }];    let tc = Arc::new(review_turn_context);    if tc.environments.single_local_environment_cwd().is_some() {        tc.turn_metadata_state.spawn_git_enrichment_task();    }    // TODO(ccunningham): Review turns currently rely on `spawn_task` for TurnComplete but do not    // emit a parent TurnStarted. Consider giving review a full parent turn lifecycle    // (TurnStarted + TurnComplete) for consistency with other standalone tasks.    sess.spawn_task(tc.clone(), input, ReviewTask::new()).await;    // Announce entering review mode so UIs can switch modes.    let review_request = ReviewRequest {        target: resolved.target,        user_facing_hint: Some(resolved.user_facing_hint),    };    sess.send_event(&tc, EventMsg::EnteredReviewMode(review_request))        .await;}