use super::*; use crate::SkillLoadOutcome; use crate::config::GhostSnapshotConfig; use crate::environment_selection::ResolvedTurnEnvironments; use codex_model_provider::SharedModelProvider; use codex_model_provider::create_model_provider; use codex_protocol::SessionId; use codex_protocol::models::AdditionalPermissionProfile; use codex_protocol::protocol::ThreadSource; use codex_protocol::protocol::TurnEnvironmentSelection; use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile; use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy; use codex_sandboxing::policy_transforms::effective_network_sandbox_policy; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; pub(super) fn image_generation_tool_auth_allowed(auth_manager: Option<&AuthManager>) -> bool { auth_manager.is_some_and(AuthManager::current_auth_uses_codex_backend) } #[derive(Clone, Debug)] pub(crate) struct TurnSkillsContext { pub(crate) outcome: Arc, pub(crate) implicit_invocation_seen_skills: Arc>>, } impl TurnSkillsContext { pub(crate) fn new(outcome: Arc) -> Self { Self { outcome, implicit_invocation_seen_skills: Arc::new(Mutex::new(HashSet::new())), } } } #[derive(Clone, Debug)] pub(crate) struct TurnEnvironment { pub(crate) environment_id: String, pub(crate) environment: Arc, pub(crate) cwd: AbsolutePathBuf, pub(crate) shell: Option, } impl TurnEnvironment { pub(crate) fn selection(&self) -> TurnEnvironmentSelection { TurnEnvironmentSelection { environment_id: self.environment_id.clone(), cwd: self.cwd.clone(), } } } /// The context needed for a single turn of the thread. #[derive(Debug)] pub struct TurnContext { pub(crate) sub_id: String, pub(crate) trace_id: Option, pub(crate) realtime_active: bool, pub config: Arc, pub(crate) auth_manager: Option>, pub(crate) model_info: ModelInfo, pub(crate) session_telemetry: SessionTelemetry, pub(crate) provider: SharedModelProvider, pub(crate) reasoning_effort: Option, pub(crate) reasoning_summary: ReasoningSummaryConfig, pub(crate) session_source: SessionSource, pub(crate) thread_source: Option, pub(crate) environments: ResolvedTurnEnvironments, /// The session's absolute working directory. All relative paths provided /// by the model as well as sandbox policies are resolved against this path /// instead of `std::env::current_dir()`. #[deprecated(note = "use the selected turn environment cwd instead")] pub(crate) cwd: AbsolutePathBuf, pub(crate) current_date: Option, pub(crate) timezone: Option, pub(crate) app_server_client_name: Option, pub(crate) developer_instructions: Option, pub(crate) compact_prompt: Option, pub(crate) user_instructions: Option, pub(crate) collaboration_mode: CollaborationMode, pub(crate) personality: Option, pub(crate) approval_policy: Constrained, pub(crate) permission_profile: PermissionProfile, pub(crate) network: Option, pub(crate) windows_sandbox_level: WindowsSandboxLevel, pub(crate) shell_environment_policy: ShellEnvironmentPolicy, pub(crate) tools_config: ToolsConfig, pub features: ManagedFeatures, pub(crate) ghost_snapshot: GhostSnapshotConfig, pub(crate) final_output_json_schema: Option, pub(crate) codex_self_exe: Option, pub(crate) codex_linux_sandbox_exe: Option, pub(crate) truncation_policy: TruncationPolicy, pub(crate) dynamic_tools: Vec, pub(crate) turn_metadata_state: Arc, pub(crate) extension_data: Arc, pub(crate) turn_skills: TurnSkillsContext, pub(crate) turn_timing_state: Arc, pub(crate) server_model_warning_emitted: AtomicBool, pub(crate) model_verification_emitted: AtomicBool, } impl TurnContext { pub(crate) fn permission_profile(&self) -> PermissionProfile { self.permission_profile.clone() } pub(crate) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy { self.permission_profile.file_system_sandbox_policy() } pub(crate) fn network_sandbox_policy(&self) -> NetworkSandboxPolicy { self.permission_profile.network_sandbox_policy() } pub(crate) fn sandbox_policy(&self) -> SandboxPolicy { let file_system_sandbox_policy = self.file_system_sandbox_policy(); let network_sandbox_policy = self.network_sandbox_policy(); compatibility_sandbox_policy_for_permission_profile( &self.permission_profile, &file_system_sandbox_policy, network_sandbox_policy, #[allow(deprecated)] &self.cwd, ) } pub(crate) fn effective_reasoning_effort(&self) -> Option { if self.model_info.supports_reasoning_summaries { self.reasoning_effort .or(self.model_info.default_reasoning_level) } else { None } } pub(crate) fn effective_reasoning_effort_for_tracing(&self) -> String { self.effective_reasoning_effort() .map(|effort| effort.to_string()) .unwrap_or_else(|| "default".to_string()) } pub(crate) fn model_context_window(&self) -> Option { let effective_context_window_percent = self.model_info.effective_context_window_percent; self.model_info .resolved_context_window() .map(|context_window| { context_window.saturating_mul(effective_context_window_percent) / 100 }) } pub(crate) fn apps_enabled(&self) -> bool { let uses_codex_backend = self .auth_manager .as_deref() .is_some_and(AuthManager::current_auth_uses_codex_backend); self.features.apps_enabled_for_auth(uses_codex_backend) } pub(crate) async fn with_model( &self, model: String, models_manager: &SharedModelsManager, ) -> Self { let mut config = (*self.config).clone(); config.model = Some(model.clone()); let model_info = models_manager .get_model_info(model.as_str(), &config.to_models_manager_config()) .await; let truncation_policy = model_info.truncation_policy.into(); let supported_reasoning_levels = model_info .supported_reasoning_levels .iter() .map(|preset| preset.effort) .collect::>(); let reasoning_effort = if let Some(current_reasoning_effort) = self.reasoning_effort { if supported_reasoning_levels.contains(¤t_reasoning_effort) { Some(current_reasoning_effort) } else { supported_reasoning_levels .get(supported_reasoning_levels.len().saturating_sub(1) / 2) .copied() .or(model_info.default_reasoning_level) } } else { supported_reasoning_levels .get(supported_reasoning_levels.len().saturating_sub(1) / 2) .copied() .or(model_info.default_reasoning_level) }; config.model_reasoning_effort = reasoning_effort; let collaboration_mode = self.collaboration_mode.with_updates( Some(model.clone()), Some(reasoning_effort), /*developer_instructions*/ None, ); let features = self.features.clone(); let provider_capabilities = self.provider.capabilities(); let tools_config = ToolsConfig::new(&ToolsConfigParams { model_info: &model_info, available_models: &models_manager .list_models(RefreshStrategy::OnlineIfUncached) .await, features: &features, image_generation_tool_auth_allowed: image_generation_tool_auth_allowed( self.auth_manager.as_deref(), ), web_search_mode: self.tools_config.web_search_mode, session_source: self.session_source.clone(), permission_profile: &self.permission_profile, windows_sandbox_level: self.windows_sandbox_level, }) .with_namespace_tools_capability(provider_capabilities.namespace_tools) .with_image_generation_capability(provider_capabilities.image_generation) .with_web_search_capability(provider_capabilities.web_search) .with_unified_exec_shell_mode(self.tools_config.unified_exec_shell_mode.clone()) .with_web_search_config(self.tools_config.web_search_config.clone()) .with_allow_login_shell(self.tools_config.allow_login_shell) .with_environment_mode(self.tools_config.environment_mode) .with_spawn_agent_usage_hint(config.multi_agent_v2.usage_hint_enabled) .with_spawn_agent_usage_hint_text(config.multi_agent_v2.usage_hint_text.clone()) .with_hide_spawn_agent_metadata(config.multi_agent_v2.hide_spawn_agent_metadata) .with_multi_agent_v2_non_code_mode_only(config.multi_agent_v2.non_code_mode_only) .with_goal_tools_allowed(self.tools_config.goal_tools) .with_max_concurrent_threads_per_session( config .features .enabled(Feature::MultiAgentV2) .then_some(config.multi_agent_v2.max_concurrent_threads_per_session), ) .with_wait_agent_min_timeout_ms( config .features .enabled(Feature::MultiAgentV2) .then_some(config.multi_agent_v2.min_wait_timeout_ms), ) .with_wait_agent_max_timeout_ms( config .features .enabled(Feature::MultiAgentV2) .then_some(config.multi_agent_v2.max_wait_timeout_ms), ) .with_wait_agent_default_timeout_ms( config .features .enabled(Feature::MultiAgentV2) .then_some(config.multi_agent_v2.default_wait_timeout_ms), ) .with_agent_type_description(crate::agent::role::spawn_tool_spec::build( &config.agent_roles, )); Self { sub_id: self.sub_id.clone(), trace_id: self.trace_id.clone(), realtime_active: self.realtime_active, config: Arc::new(config), auth_manager: self.auth_manager.clone(), model_info: model_info.clone(), session_telemetry: self .session_telemetry .clone() .with_model(model.as_str(), model_info.slug.as_str()), provider: self.provider.clone(), reasoning_effort, reasoning_summary: self.reasoning_summary, session_source: self.session_source.clone(), thread_source: self.thread_source, environments: self.environments.clone(), #[allow(deprecated)] cwd: self.cwd.clone(), current_date: self.current_date.clone(), timezone: self.timezone.clone(), app_server_client_name: self.app_server_client_name.clone(), developer_instructions: self.developer_instructions.clone(), compact_prompt: self.compact_prompt.clone(), user_instructions: self.user_instructions.clone(), collaboration_mode, personality: self.personality, approval_policy: self.approval_policy.clone(), permission_profile: self.permission_profile.clone(), network: self.network.clone(), windows_sandbox_level: self.windows_sandbox_level, shell_environment_policy: self.shell_environment_policy.clone(), tools_config, features, ghost_snapshot: self.ghost_snapshot.clone(), final_output_json_schema: self.final_output_json_schema.clone(), codex_self_exe: self.codex_self_exe.clone(), codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.clone(), truncation_policy, dynamic_tools: self.dynamic_tools.clone(), turn_metadata_state: self.turn_metadata_state.clone(), extension_data: Arc::clone(&self.extension_data), turn_skills: self.turn_skills.clone(), turn_timing_state: Arc::clone(&self.turn_timing_state), server_model_warning_emitted: AtomicBool::new( self.server_model_warning_emitted.load(Ordering::Relaxed), ), model_verification_emitted: AtomicBool::new( self.model_verification_emitted.load(Ordering::Relaxed), ), } } #[deprecated(note = "resolve paths from the selected turn environment cwd instead")] pub(crate) fn resolve_path(&self, path: Option) -> AbsolutePathBuf { #[allow(deprecated)] path.as_ref() .map_or_else(|| self.cwd.clone(), |path| self.cwd.join(path)) } pub(crate) fn file_system_sandbox_context( &self, additional_permissions: Option, cwd: &AbsolutePathBuf, ) -> FileSystemSandboxContext { let (base_file_system_sandbox_policy, base_network_sandbox_policy) = self.permission_profile.to_runtime_permissions(); let file_system_sandbox_policy = effective_file_system_sandbox_policy( &base_file_system_sandbox_policy, additional_permissions.as_ref(), ); let network_sandbox_policy = effective_network_sandbox_policy( base_network_sandbox_policy, additional_permissions.as_ref(), ); let permissions = PermissionProfile::from_runtime_permissions_with_enforcement( self.permission_profile.enforcement(), &file_system_sandbox_policy, network_sandbox_policy, ); FileSystemSandboxContext { permissions, cwd: Some(cwd.clone()), windows_sandbox_level: self.windows_sandbox_level, windows_sandbox_private_desktop: self .config .permissions .windows_sandbox_private_desktop, use_legacy_landlock: self.features.use_legacy_landlock(), } } fn non_legacy_file_system_sandbox_policy(&self) -> Option { // Omit the derived split filesystem policy when it is equivalent to // the legacy sandbox policy. This keeps turn-context payloads stable // while both fields exist; once callers consume only the split policy, // this comparison and the legacy projection should go away. let legacy_file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd( &self.sandbox_policy(), #[allow(deprecated)] &self.cwd, ); let file_system_sandbox_policy = self.file_system_sandbox_policy(); (file_system_sandbox_policy != legacy_file_system_sandbox_policy) .then_some(file_system_sandbox_policy) } pub(crate) fn compact_prompt(&self) -> &str { self.compact_prompt .as_deref() .unwrap_or(compact::SUMMARIZATION_PROMPT) } pub(crate) fn to_turn_context_item(&self) -> TurnContextItem { TurnContextItem { turn_id: Some(self.sub_id.clone()), trace_id: self.trace_id.clone(), #[allow(deprecated)] cwd: self.cwd.to_path_buf(), current_date: self.current_date.clone(), timezone: self.timezone.clone(), approval_policy: self.approval_policy.value(), sandbox_policy: self.sandbox_policy(), permission_profile: Some(self.permission_profile()), network: self.turn_context_network_item(), file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(), model: self.model_info.slug.clone(), personality: self.personality, collaboration_mode: Some(self.collaboration_mode.clone()), realtime_active: Some(self.realtime_active), effort: self.reasoning_effort, summary: self.reasoning_summary, user_instructions: self.user_instructions.clone(), developer_instructions: self.developer_instructions.clone(), final_output_json_schema: self.final_output_json_schema.clone(), truncation_policy: Some(self.truncation_policy), } } fn turn_context_network_item(&self) -> Option { let network = self .config .config_layer_stack .requirements() .network .as_ref()?; Some(TurnContextNetworkItem { allowed_domains: network .domains .as_ref() .and_then(codex_config::NetworkDomainPermissionsToml::allowed_domains) .unwrap_or_default(), denied_domains: network .domains .as_ref() .and_then(codex_config::NetworkDomainPermissionsToml::denied_domains) .unwrap_or_default(), }) } } fn local_time_context() -> (String, String) { match iana_time_zone::get_timezone() { Ok(timezone) => (Local::now().format("%Y-%m-%d").to_string(), timezone), Err(_) => ( Utc::now().format("%Y-%m-%d").to_string(), "Etc/UTC".to_string(), ), } } impl Session { /// Don't expand the number of mutated arguments on config. We are in the process of getting rid of it. pub(crate) fn build_per_turn_config( session_configuration: &SessionConfiguration, cwd: AbsolutePathBuf, ) -> Config { // todo(aibrahim): store this state somewhere else so we don't need to mut config let config = session_configuration.original_config_do_not_use.clone(); let mut per_turn_config = (*config).clone(); per_turn_config.cwd = cwd; per_turn_config.workspace_roots = session_configuration.workspace_roots.clone(); per_turn_config .permissions .set_workspace_roots(session_configuration.workspace_roots.clone()); per_turn_config.model_reasoning_effort = session_configuration.collaboration_mode.reasoning_effort(); per_turn_config.model_reasoning_summary = session_configuration.model_reasoning_summary; per_turn_config.service_tier = session_configuration.service_tier.clone(); per_turn_config.personality = session_configuration.personality; per_turn_config.approvals_reviewer = session_configuration.approvals_reviewer; session_configuration .apply_permission_profile_to_permissions(&mut per_turn_config.permissions); let permission_profile = session_configuration.permission_profile(); let resolved_web_search_mode = resolve_web_search_mode_for_turn(&per_turn_config.web_search_mode, &permission_profile); if let Err(err) = per_turn_config .web_search_mode .set(resolved_web_search_mode) { let fallback_value = per_turn_config.web_search_mode.value(); tracing::warn!( error = %err, ?resolved_web_search_mode, ?fallback_value, "resolved web_search_mode is disallowed by requirements; keeping constrained value" ); } per_turn_config.features = config.features.clone(); per_turn_config } pub(crate) fn build_effective_session_config( session_configuration: &SessionConfiguration, ) -> Config { let mut config = Self::build_per_turn_config(session_configuration, session_configuration.cwd.clone()); config.model = Some(session_configuration.collaboration_mode.model().to_string()); config.permissions.approval_policy = session_configuration.approval_policy.clone(); config.workspace_roots = session_configuration.workspace_roots.clone(); config .permissions .set_workspace_roots(session_configuration.workspace_roots.clone()); config } #[allow(clippy::too_many_arguments)] pub(crate) fn make_turn_context( thread_id: ThreadId, session_id: SessionId, auth_manager: Option>, session_telemetry: &SessionTelemetry, provider: ModelProviderInfo, session_configuration: &SessionConfiguration, user_shell: &shell::Shell, shell_zsh_path: Option<&PathBuf>, main_execve_wrapper_exe: Option<&PathBuf>, per_turn_config: Config, model_info: ModelInfo, models_manager: &SharedModelsManager, network: Option, environments: ResolvedTurnEnvironments, cwd: AbsolutePathBuf, sub_id: String, skills_outcome: Arc, goal_tools_supported: bool, ) -> TurnContext { let reasoning_effort = session_configuration.collaboration_mode.reasoning_effort(); let reasoning_summary = session_configuration .model_reasoning_summary .unwrap_or(model_info.default_reasoning_summary); let session_telemetry = session_telemetry.clone().with_model( session_configuration.collaboration_mode.model(), model_info.slug.as_str(), ); let session_source = session_configuration.session_source.clone(); let image_generation_tool_auth_allowed = image_generation_tool_auth_allowed(auth_manager.as_deref()); let auth_manager_for_context = auth_manager.clone(); let provider_for_context = create_model_provider(provider, auth_manager); let provider_capabilities = provider_for_context.capabilities(); let session_telemetry_for_context = session_telemetry; let tools_config = ToolsConfig::new(&ToolsConfigParams { model_info: &model_info, available_models: &models_manager.try_list_models().unwrap_or_default(), features: &per_turn_config.features, image_generation_tool_auth_allowed, web_search_mode: Some(per_turn_config.web_search_mode.value()), session_source: session_source.clone(), permission_profile: &session_configuration.permission_profile(), windows_sandbox_level: session_configuration.windows_sandbox_level, }) .with_namespace_tools_capability(provider_capabilities.namespace_tools) .with_image_generation_capability(provider_capabilities.image_generation) .with_web_search_capability(provider_capabilities.web_search) .with_unified_exec_shell_mode_for_session( crate::tools::tool_user_shell_type(user_shell), shell_zsh_path, main_execve_wrapper_exe, ) .with_web_search_config(per_turn_config.web_search_config.clone()) .with_allow_login_shell(per_turn_config.permissions.allow_login_shell) .with_environment_mode(ToolEnvironmentMode::from_count( environments.turn_environments.len(), )) .with_spawn_agent_usage_hint(per_turn_config.multi_agent_v2.usage_hint_enabled) .with_spawn_agent_usage_hint_text(per_turn_config.multi_agent_v2.usage_hint_text.clone()) .with_hide_spawn_agent_metadata(per_turn_config.multi_agent_v2.hide_spawn_agent_metadata) .with_multi_agent_v2_non_code_mode_only(per_turn_config.multi_agent_v2.non_code_mode_only) .with_goal_tools_allowed(goal_tools_supported) .with_max_concurrent_threads_per_session( per_turn_config .features .enabled(Feature::MultiAgentV2) .then_some( per_turn_config .multi_agent_v2 .max_concurrent_threads_per_session, ), ) .with_wait_agent_min_timeout_ms( per_turn_config .features .enabled(Feature::MultiAgentV2) .then_some(per_turn_config.multi_agent_v2.min_wait_timeout_ms), ) .with_wait_agent_max_timeout_ms( per_turn_config .features .enabled(Feature::MultiAgentV2) .then_some(per_turn_config.multi_agent_v2.max_wait_timeout_ms), ) .with_wait_agent_default_timeout_ms( per_turn_config .features .enabled(Feature::MultiAgentV2) .then_some(per_turn_config.multi_agent_v2.default_wait_timeout_ms), ) .with_agent_type_description(crate::agent::role::spawn_tool_spec::build( &per_turn_config.agent_roles, )); let mut per_turn_config = per_turn_config; per_turn_config.service_tier = per_turn_config .service_tier .filter(|service_tier| model_info.supports_service_tier(service_tier)); let per_turn_config = Arc::new(per_turn_config); let turn_metadata_state = Arc::new(TurnMetadataState::new( session_id.to_string(), thread_id.to_string(), session_configuration.thread_source, sub_id.clone(), cwd.clone(), &session_configuration.permission_profile(), session_configuration.windows_sandbox_level, network.is_some(), )); let (current_date, timezone) = local_time_context(); let extension_data = Arc::new(codex_extension_api::ExtensionData::new(sub_id.clone())); TurnContext { sub_id, trace_id: current_span_trace_id(), realtime_active: false, config: per_turn_config.clone(), auth_manager: auth_manager_for_context, model_info: model_info.clone(), session_telemetry: session_telemetry_for_context, provider: provider_for_context, reasoning_effort, reasoning_summary, session_source, thread_source: session_configuration.thread_source, environments, #[allow(deprecated)] cwd, current_date: Some(current_date), timezone: Some(timezone), app_server_client_name: session_configuration.app_server_client_name.clone(), developer_instructions: session_configuration.developer_instructions.clone(), compact_prompt: session_configuration.compact_prompt.clone(), user_instructions: session_configuration.user_instructions.clone(), collaboration_mode: session_configuration.collaboration_mode.clone(), personality: session_configuration.personality, approval_policy: session_configuration.approval_policy.clone(), permission_profile: session_configuration.permission_profile(), network, windows_sandbox_level: session_configuration.windows_sandbox_level, shell_environment_policy: per_turn_config.permissions.shell_environment_policy.clone(), tools_config, features: per_turn_config.features.clone(), ghost_snapshot: per_turn_config.ghost_snapshot.clone(), final_output_json_schema: None, codex_self_exe: per_turn_config.codex_self_exe.clone(), codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(), truncation_policy: model_info.truncation_policy.into(), dynamic_tools: session_configuration.dynamic_tools.clone(), turn_metadata_state, extension_data, turn_skills: TurnSkillsContext::new(skills_outcome), turn_timing_state: Arc::new(TurnTimingState::default()), server_model_warning_emitted: AtomicBool::new(false), model_verification_emitted: AtomicBool::new(false), } } pub(crate) async fn new_turn_with_sub_id( &self, sub_id: String, updates: SessionSettingsUpdate, ) -> CodexResult> { let notify_config_contributors = !self.services.extensions.config_contributors().is_empty(); let update_result: CodexResult<_> = { let mut state = self.state.lock().await; match state.session_configuration.clone().apply(&updates) { Ok(next) => { let mut effective_environments = updates .environments .clone() .unwrap_or_else(|| next.environments.clone()); if updates.environments.is_none() { Self::overlay_runtime_cwd_on_primary_environment( &mut effective_environments, &next.cwd, ); } let turn_environments = self.resolve_turn_environments(&effective_environments)?; let previous_cwd = state.session_configuration.cwd.clone(); let previous_permission_profile = state.session_configuration.permission_profile(); let next_permission_profile = next.permission_profile(); let permission_profile_changed = previous_permission_profile != next_permission_profile; let codex_home = next.codex_home.clone(); let session_source = next.session_source.clone(); let previous_config = notify_config_contributors.then(|| { Self::build_effective_session_config(&state.session_configuration) }); let new_config = notify_config_contributors .then(|| Self::build_effective_session_config(&next)); state.session_configuration = next.clone(); Ok(( next, turn_environments, permission_profile_changed, previous_cwd, codex_home, session_source, previous_config, new_config, )) } Err(err) => Err(CodexErr::InvalidRequest(err.to_string())), } }; let ( session_configuration, turn_environments, permission_profile_changed, previous_cwd, codex_home, session_source, previous_config, new_config, ) = match update_result { Ok(update) => update, Err(err) => { let message = err.to_string(); self.send_event_raw(Event { id: sub_id.clone(), msg: EventMsg::Error(ErrorEvent { message: message.clone(), codex_error_info: Some(CodexErrorInfo::BadRequest), }), }) .await; return Err(CodexErr::InvalidRequest(message)); } }; self.emit_config_changed_contributors(previous_config.as_ref(), new_config.as_ref()); self.maybe_refresh_shell_snapshot_for_cwd( &previous_cwd, &session_configuration.cwd, &codex_home, &session_source, ); if permission_profile_changed { self.refresh_managed_network_proxy_for_current_permission_profile() .await; } Ok(self .new_turn_from_configuration( sub_id, session_configuration, updates.final_output_json_schema, turn_environments, ) .await) } fn resolve_turn_environments( &self, environments: &[TurnEnvironmentSelection], ) -> CodexResult { crate::environment_selection::resolve_environment_selections( self.services.environment_manager.as_ref(), environments, ) } async fn new_turn_from_configuration( &self, sub_id: String, session_configuration: SessionConfiguration, final_output_json_schema: Option>, turn_environments: ResolvedTurnEnvironments, ) -> Arc { let primary_turn_environment = turn_environments.primary(); let cwd = primary_turn_environment .map(|turn_environment| turn_environment.cwd.clone()) .unwrap_or_else(|| session_configuration.cwd.clone()); let per_turn_config = Self::build_per_turn_config(&session_configuration, cwd.clone()); { let mcp_connection_manager = self.services.mcp_connection_manager.read().await; mcp_connection_manager.set_approval_policy(&session_configuration.approval_policy); mcp_connection_manager .set_permission_profile(session_configuration.permission_profile()); } let model_info = self .services .models_manager .get_model_info( session_configuration.collaboration_mode.model(), &per_turn_config.to_models_manager_config(), ) .await; let plugin_outcome = self .services .plugins_manager .plugins_for_config(&per_turn_config.plugins_config_input()) .await; let effective_skill_roots = plugin_outcome.effective_plugin_skill_roots(); let skills_input = skills_load_input_from_config(&per_turn_config, effective_skill_roots); let fs = primary_turn_environment .map(|turn_environment| turn_environment.environment.get_filesystem()); let skills_outcome = Arc::new( self.services .skills_manager .skills_for_config(&skills_input, fs) .await, ); let goal_tools_supported = !per_turn_config.ephemeral && self.state_db().is_some(); let mut turn_context: TurnContext = Self::make_turn_context( self.thread_id(), self.session_id(), Some(Arc::clone(&self.services.auth_manager)), &self.services.session_telemetry, session_configuration.provider.clone(), &session_configuration, self.services.user_shell.as_ref(), self.services.shell_zsh_path.as_ref(), self.services.main_execve_wrapper_exe.as_ref(), per_turn_config, model_info, &self.services.models_manager, self.services .network_proxy .as_ref() .and_then(|started_proxy| { Self::managed_network_proxy_active_for_permission_profile( &session_configuration.permission_profile(), ) .then(|| started_proxy.proxy()) }), turn_environments, cwd, sub_id, skills_outcome, goal_tools_supported, ); turn_context.realtime_active = self.conversation.running_state().await.is_some(); if let Some(final_schema) = final_output_json_schema { turn_context.final_output_json_schema = final_schema; } let turn_context = Arc::new(turn_context); turn_context.turn_metadata_state.spawn_git_enrichment_task(); turn_context } pub(crate) async fn maybe_emit_unknown_model_warning_for_turn(&self, tc: &TurnContext) { if tc.model_info.used_fallback_model_metadata { self.send_event( tc, EventMsg::Warning(WarningEvent { message: format!( "Model metadata for `{}` not found. Defaulting to fallback metadata; this can degrade performance and cause issues.", tc.model_info.slug ), }), ) .await; } } pub(crate) async fn new_default_turn(&self) -> Arc { self.new_default_turn_with_sub_id(self.next_internal_sub_id()) .await } pub(crate) async fn new_default_turn_with_sub_id(&self, sub_id: String) -> Arc { let session_configuration = { let state = self.state.lock().await; state.session_configuration.clone() }; let mut effective_environments = session_configuration.environments.clone(); Self::overlay_runtime_cwd_on_primary_environment( &mut effective_environments, &session_configuration.cwd, ); let turn_environments = match self.resolve_turn_environments(&effective_environments) { Ok(turn_environments) => turn_environments, Err(err) => { warn!("failed to resolve stored session environments: {err}"); ResolvedTurnEnvironments::default() } }; self.new_turn_from_configuration( sub_id, session_configuration, /*final_output_json_schema*/ None, turn_environments, ) .await } fn overlay_runtime_cwd_on_primary_environment( environments: &mut [TurnEnvironmentSelection], runtime_cwd: &AbsolutePathBuf, ) { if let Some(turn_environment) = environments.first_mut() && turn_environment.cwd != *runtime_cwd { turn_environment.cwd = runtime_cwd.clone(); } } }