Download codex-rs/core/src/agent/child_config.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 13 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/core/src/agent/child_config.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/core/src/agent/child_config.rs
-
curl -L -o child_config.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/core/src/agent/child_config.rs
13 kB
| //! Prepares child configuration from the invoking turn and requested overrides. | |
| //! | |
| //! Spawn and reload share live runtime policy; role and model precedence, full-history | |
| //! inheritance, and validation messages remain the same for each multi-agent version. | |
| use crate::agent::role::DEFAULT_ROLE_NAME; | |
| use crate::agent::role::apply_role_to_config; | |
| use crate::config::Config; | |
| use crate::session::session::Session; | |
| use crate::session::turn_context::TurnContext; | |
| use codex_models_manager::manager::RefreshStrategy; | |
| use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE; | |
| use codex_protocol::models::BaseInstructions; | |
| use codex_protocol::openai_models::ModelPreset; | |
| use codex_protocol::openai_models::ReasoningEffort; | |
| use codex_protocol::openai_models::ReasoningEffortPreset; | |
| use codex_protocol::protocol::MultiAgentVersion; | |
| pub(crate) const MAX_SPAWN_AGENT_MODEL_OVERRIDES: usize = 5; | |
| pub(crate) fn model_supports_multi_agent_backend( | |
| model: &ModelPreset, | |
| multi_agent_version: MultiAgentVersion, | |
| ) -> bool { | |
| multi_agent_version != MultiAgentVersion::V2 | |
| || model.multi_agent_version != Some(MultiAgentVersion::Disabled) | |
| } | |
| /// Selects the existing spawn tool's role-inheritance rules. | |
| pub(crate) enum SpawnConfigVersion { | |
| V1, | |
| V2, | |
| } | |
| pub(crate) struct SpawnConfigOptions<'a> { | |
| pub(crate) version: SpawnConfigVersion, | |
| pub(crate) full_history_fork: bool, | |
| pub(crate) role_name: Option<&'a str>, | |
| pub(crate) model: Option<&'a str>, | |
| pub(crate) reasoning_effort: Option<ReasoningEffort>, | |
| } | |
| pub(crate) struct PreparedSpawnConfig { | |
| pub(crate) config: Config, | |
| pub(crate) role_name: Option<String>, | |
| } | |
| /// Resolves child settings before starting the thread, retaining the invoking tool's precedence. | |
| pub(crate) async fn prepare_agent_spawn_config( | |
| session: &Session, | |
| turn: &TurnContext, | |
| options: SpawnConfigOptions<'_>, | |
| ) -> Result<PreparedSpawnConfig, String> { | |
| let mut config = build_agent_spawn_config(&session.get_base_instructions().await, turn)?; | |
| if options.version == SpawnConfigVersion::V1 && options.full_history_fork { | |
| reject_full_fork_agent_type_override(options.role_name)?; | |
| } | |
| apply_requested_spawn_agent_model_overrides( | |
| session, | |
| turn, | |
| &mut config, | |
| options.model, | |
| options.reasoning_effort, | |
| ) | |
| .await?; | |
| if !options.full_history_fork | |
| || (options.version == SpawnConfigVersion::V2 && options.role_name.is_some()) | |
| { | |
| apply_spawn_agent_role(session, &mut config, options.role_name).await?; | |
| if options.version == SpawnConfigVersion::V2 | |
| && options.full_history_fork | |
| && config.developer_instructions.is_none() | |
| { | |
| config | |
| .developer_instructions | |
| .clone_from(&turn.developer_instructions); | |
| } | |
| } | |
| apply_spawn_agent_service_tier(session, &mut config).await?; | |
| apply_spawn_agent_runtime_overrides(&mut config, turn)?; | |
| // Remember an applied configured default so cold reload reapplies its restrictions. | |
| let role_name = options | |
| .role_name | |
| .or_else(|| { | |
| (options.version == SpawnConfigVersion::V2 | |
| && !options.full_history_fork | |
| && config | |
| .agent_roles | |
| .get(DEFAULT_ROLE_NAME) | |
| .is_some_and(|role| role.config_file.is_some())) | |
| .then_some(DEFAULT_ROLE_NAME) | |
| }) | |
| .map(str::to_owned); | |
| Ok(PreparedSpawnConfig { config, role_name }) | |
| } | |
| /// Builds the base config snapshot for a newly spawned sub-agent. | |
| /// | |
| /// The returned config starts from the parent's effective config and then refreshes the | |
| /// runtime-owned fields carried by the turn, including model selection, reasoning settings, | |
| /// approval policy, sandbox, and cwd. Role-specific overrides are layered | |
| /// after this step; skipping this helper and cloning stale config state directly can send the child | |
| /// agent out with the wrong provider or runtime policy. | |
| pub(crate) fn build_agent_spawn_config( | |
| base_instructions: &BaseInstructions, | |
| turn: &TurnContext, | |
| ) -> Result<Config, String> { | |
| let mut config = build_agent_shared_config(turn)?; | |
| config.base_instructions = Some(base_instructions.text.clone()); | |
| config.base_instructions_provenance = base_instructions.provenance.clone(); | |
| Ok(config) | |
| } | |
| pub(crate) fn build_agent_resume_config(turn: &TurnContext) -> Result<Config, String> { | |
| let mut config = build_agent_shared_config(turn)?; | |
| // For resume, keep base instructions sourced from rollout/session metadata. | |
| config.base_instructions = None; | |
| config.base_instructions_provenance = None; | |
| Ok(config) | |
| } | |
| fn build_agent_shared_config(turn: &TurnContext) -> Result<Config, String> { | |
| let base_config = turn.config.clone(); | |
| let mut config = (*base_config).clone(); | |
| // Preserve activation for history forks without freezing the parent's model-owned prompts. | |
| // Fresh child startup restores configured preferences from the retained snapshot. | |
| config.token_budget = turn.configured_token_budget.clone(); | |
| config.model = Some(turn.model_info().slug.clone()); | |
| config.model_provider = turn.provider.info().clone(); | |
| config.model_reasoning_effort = turn | |
| .reasoning_effort() | |
| .or(turn.model_info().default_reasoning_level.as_ref()) | |
| .cloned(); | |
| config.model_reasoning_summary = Some(turn.reasoning_summary()); | |
| config.developer_instructions = turn.developer_instructions.clone(); | |
| if turn.multi_agent_version == MultiAgentVersion::V2 | |
| && let Some(developer_instructions) = turn | |
| .config | |
| .multi_agent_v2 | |
| .subagent_developer_instructions | |
| .clone() | |
| { | |
| config.developer_instructions = Some(developer_instructions); | |
| } | |
| apply_spawn_agent_runtime_overrides(&mut config, turn)?; | |
| Ok(config) | |
| } | |
| fn reject_full_fork_agent_type_override(agent_type: Option<&str>) -> Result<(), String> { | |
| if agent_type.is_some() { | |
| return Err( | |
| "Full-history forked agents inherit the parent agent type; omit agent_type, or spawn without a full-history fork.".to_string()); | |
| } | |
| Ok(()) | |
| } | |
| /// Copies runtime-only turn state onto a child config before it is handed to `AgentControl`. | |
| /// | |
| /// These values are chosen by the live turn rather than persisted config, so leaving them stale can | |
| /// make a child agent disagree with its parent about approval policy, cwd, or sandboxing. | |
| fn apply_spawn_agent_runtime_overrides( | |
| config: &mut Config, | |
| turn: &TurnContext, | |
| ) -> Result<(), String> { | |
| config | |
| .permissions | |
| .approval_policy | |
| .set(turn.approval_policy()) | |
| .map_err(|err| format!("approval_policy is invalid: {err}"))?; | |
| config.approvals_reviewer = turn.config.approvals_reviewer; | |
| let turn_cwd = turn.cwd.clone(); | |
| config.cwd = turn_cwd; | |
| config | |
| .permissions | |
| .set_permission_profile_from_session_snapshot( | |
| turn.config | |
| .permissions | |
| .permission_profile_state() | |
| .snapshot(), | |
| ) | |
| .map_err(|err| format!("permission_profile is invalid: {err}"))?; | |
| Ok(()) | |
| } | |
| async fn apply_requested_spawn_agent_model_overrides( | |
| session: &Session, | |
| turn: &TurnContext, | |
| config: &mut Config, | |
| requested_model: Option<&str>, | |
| requested_reasoning_effort: Option<ReasoningEffort>, | |
| ) -> Result<(), String> { | |
| let requested_model = requested_model.or(turn.config.agent_default_subagent_model.as_deref()); | |
| let requested_reasoning_effort = requested_reasoning_effort | |
| .or_else(|| turn.config.agent_default_subagent_reasoning_effort.clone()); | |
| if requested_model.is_none() && requested_reasoning_effort.is_none() { | |
| return Ok(()); | |
| } | |
| if let Some(requested_model) = requested_model { | |
| let available_models = session | |
| .services | |
| .models_manager | |
| .list_models(RefreshStrategy::Offline, config.http_client_factory()) | |
| .await; | |
| let selected_model_name = find_spawn_agent_model_name( | |
| &available_models, | |
| requested_model, | |
| turn.multi_agent_version, | |
| )?; | |
| let selected_model_info = session | |
| .services | |
| .models_manager | |
| .get_model_info(&selected_model_name, &config.to_models_manager_config()) | |
| .await; | |
| config.model = Some(selected_model_name.clone()); | |
| if let Some(reasoning_effort) = requested_reasoning_effort { | |
| validate_spawn_agent_reasoning_effort( | |
| &selected_model_name, | |
| &selected_model_info.supported_reasoning_levels, | |
| &reasoning_effort, | |
| )?; | |
| config.model_reasoning_effort = Some(reasoning_effort); | |
| } else { | |
| config.model_reasoning_effort = selected_model_info.default_reasoning_level; | |
| } | |
| return Ok(()); | |
| } | |
| if let Some(reasoning_effort) = requested_reasoning_effort { | |
| validate_spawn_agent_reasoning_effort( | |
| &turn.model_info().slug, | |
| &turn.model_info().supported_reasoning_levels, | |
| &reasoning_effort, | |
| )?; | |
| config.model_reasoning_effort = Some(reasoning_effort); | |
| } | |
| Ok(()) | |
| } | |
| pub(crate) async fn apply_spawn_agent_service_tier( | |
| session: &Session, | |
| config: &mut Config, | |
| ) -> Result<(), String> { | |
| let Some(service_tier) = session.services.agent_control.root_service_tier() else { | |
| config.service_tier = None; | |
| return Ok(()); | |
| }; | |
| if service_tier == SERVICE_TIER_DEFAULT_REQUEST_VALUE { | |
| config.service_tier = Some(service_tier); | |
| return Ok(()); | |
| } | |
| let model = config.model.clone().ok_or_else(|| { | |
| "spawn_agent could not resolve the child model for service tier validation".to_string() | |
| })?; | |
| let model_info = session | |
| .services | |
| .models_manager | |
| .get_model_info(model.as_str(), &config.to_models_manager_config()) | |
| .await; | |
| config.service_tier = model_info | |
| .supports_service_tier(service_tier.as_str()) | |
| .then_some(service_tier); | |
| Ok(()) | |
| } | |
| async fn apply_spawn_agent_role( | |
| session: &Session, | |
| config: &mut Config, | |
| role_name: Option<&str>, | |
| ) -> Result<(), String> { | |
| let previous_model = config.model.clone(); | |
| let previous_reasoning_effort = config.model_reasoning_effort.clone(); | |
| apply_role_to_config(config, role_name).await?; | |
| if config.model == previous_model && config.model_reasoning_effort == previous_reasoning_effort | |
| { | |
| return Ok(()); | |
| } | |
| let Some(reasoning_effort) = config.model_reasoning_effort.clone() else { | |
| return Ok(()); | |
| }; | |
| let model = config.model.clone().ok_or_else(|| { | |
| "spawn_agent could not resolve the child model for reasoning effort validation".to_string() | |
| })?; | |
| let model_info = session | |
| .services | |
| .models_manager | |
| .get_model_info(&model, &config.to_models_manager_config()) | |
| .await; | |
| if model_info.used_fallback_model_metadata { | |
| return Ok(()); | |
| } | |
| validate_spawn_agent_reasoning_effort( | |
| &model, | |
| &model_info.supported_reasoning_levels, | |
| &reasoning_effort, | |
| ) | |
| } | |
| fn find_spawn_agent_model_name( | |
| available_models: &[ModelPreset], | |
| requested_model: &str, | |
| multi_agent_version: MultiAgentVersion, | |
| ) -> Result<String, String> { | |
| available_models | |
| .iter() | |
| .find(|model| { | |
| model.model == requested_model | |
| && model_supports_multi_agent_backend(model, multi_agent_version) | |
| }) | |
| .map(|model| model.model.clone()) | |
| .ok_or_else(|| { | |
| let available = available_models | |
| .iter() | |
| .filter(|model| model.show_in_picker) | |
| .filter(|model| model_supports_multi_agent_backend(model, multi_agent_version)) | |
| .take(MAX_SPAWN_AGENT_MODEL_OVERRIDES) | |
| .map(|model| model.model.as_str()) | |
| .collect::<Vec<_>>() | |
| .join(", "); | |
| format!( | |
| "Unknown model `{requested_model}` for spawn_agent. Available models: {available}" | |
| ) | |
| }) | |
| } | |
| fn validate_spawn_agent_reasoning_effort( | |
| model: &str, | |
| supported_reasoning_levels: &[ReasoningEffortPreset], | |
| requested_reasoning_effort: &ReasoningEffort, | |
| ) -> Result<(), String> { | |
| if supported_reasoning_levels | |
| .iter() | |
| .any(|preset| &preset.effort == requested_reasoning_effort) | |
| { | |
| return Ok(()); | |
| } | |
| let supported = supported_reasoning_levels | |
| .iter() | |
| .map(|preset| preset.effort.to_string()) | |
| .collect::<Vec<_>>() | |
| .join(", "); | |
| Err(format!( | |
| "Reasoning effort `{requested_reasoning_effort}` is not supported for model `{model}`. Supported reasoning efforts: {supported}" | |
| )) | |
| } | |