diff --git a/codex-rs/tui/src/app_command.rs b/codex-rs/tui/src/app_command.rs index 2d2f9a41c1c9..4e0b07157a90 100644 --- a/codex-rs/tui/src/app_command.rs +++ b/codex-rs/tui/src/app_command.rs @@ -25,8 +25,87 @@ use serde_json::Value; use crate::permission_compat::legacy_compatible_permission_profile; +#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq, Serialize)] -pub(crate) struct AppCommand(Op); +pub(crate) enum AppCommand { + Interrupt, + CleanBackgroundTerminals, + RealtimeConversationStart(ConversationStartParams), + RealtimeConversationAudio(ConversationAudioParams), + RealtimeConversationText(ConversationTextParams), + RealtimeConversationClose, + RunUserShellCommand { + command: String, + }, + UserTurn { + items: Vec, + cwd: PathBuf, + approval_policy: AskForApproval, + approvals_reviewer: Option, + permission_profile: PermissionProfile, + model: String, + effort: Option, + summary: Option, + service_tier: Option>, + final_output_json_schema: Option, + collaboration_mode: Option, + personality: Option, + }, + OverrideTurnContext { + cwd: Option, + approval_policy: Option, + approvals_reviewer: Option, + permission_profile: Option, + windows_sandbox_level: Option, + model: Option, + effort: Option>, + summary: Option, + service_tier: Option>, + collaboration_mode: Option, + personality: Option, + }, + ExecApproval { + id: String, + turn_id: Option, + decision: ReviewDecision, + }, + PatchApproval { + id: String, + decision: ReviewDecision, + }, + ResolveElicitation { + server_name: String, + request_id: McpRequestId, + decision: ElicitationAction, + content: Option, + meta: Option, + }, + UserInputAnswer { + id: String, + response: RequestUserInputResponse, + }, + RequestPermissionsResponse { + id: String, + response: RequestPermissionsResponse, + }, + ReloadUserConfig, + ListSkills { + cwds: Vec, + force_reload: bool, + }, + Compact, + SetThreadName { + name: String, + }, + Shutdown, + ThreadRollback { + num_turns: u32, + }, + Review { + review_request: ReviewRequest, + }, + Other(Op), +} #[allow(clippy::large_enum_variant)] #[allow(dead_code)] @@ -112,28 +191,28 @@ pub(crate) enum AppCommandView<'a> { impl AppCommand { pub(crate) fn interrupt() -> Self { - Self(Op::Interrupt) + Self::Interrupt } pub(crate) fn clean_background_terminals() -> Self { - Self(Op::CleanBackgroundTerminals) + Self::CleanBackgroundTerminals } pub(crate) fn realtime_conversation_start(params: ConversationStartParams) -> Self { - Self(Op::RealtimeConversationStart(params)) + Self::RealtimeConversationStart(params) } #[cfg_attr(target_os = "linux", allow(dead_code))] pub(crate) fn realtime_conversation_audio(params: ConversationAudioParams) -> Self { - Self(Op::RealtimeConversationAudio(params)) + Self::RealtimeConversationAudio(params) } pub(crate) fn realtime_conversation_close() -> Self { - Self(Op::RealtimeConversationClose) + Self::RealtimeConversationClose } pub(crate) fn run_user_shell_command(command: String) -> Self { - Self(Op::RunUserShellCommand { command }) + Self::RunUserShellCommand { command } } #[allow(clippy::too_many_arguments)] @@ -150,21 +229,12 @@ impl AppCommand { collaboration_mode: Option, personality: Option, ) -> Self { - let legacy_profile = - legacy_compatible_permission_profile(&permission_profile, cwd.as_path()); - let sandbox_policy = legacy_profile - .to_legacy_sandbox_policy(cwd.as_path()) - .unwrap_or_else(|err| { - unreachable!("legacy-compatible permissions must project to legacy policy: {err}") - }); - Self(Op::UserTurn { + Self::UserTurn { items, - environments: None, cwd, approval_policy, approvals_reviewer: None, - sandbox_policy, - permission_profile: Some(permission_profile), + permission_profile, model, effort, summary, @@ -172,7 +242,7 @@ impl AppCommand { final_output_json_schema, collaboration_mode, personality, - }) + } } #[allow(clippy::too_many_arguments)] @@ -189,11 +259,10 @@ impl AppCommand { collaboration_mode: Option, personality: Option, ) -> Self { - Self(Op::OverrideTurnContext { + Self::OverrideTurnContext { cwd, approval_policy, approvals_reviewer, - sandbox_policy: None, permission_profile, windows_sandbox_level, model, @@ -202,7 +271,7 @@ impl AppCommand { service_tier, collaboration_mode, personality, - }) + } } pub(crate) fn exec_approval( @@ -210,15 +279,15 @@ impl AppCommand { turn_id: Option, decision: ReviewDecision, ) -> Self { - Self(Op::ExecApproval { + Self::ExecApproval { id, turn_id, decision, - }) + } } pub(crate) fn patch_approval(id: String, decision: ReviewDecision) -> Self { - Self(Op::PatchApproval { id, decision }) + Self::PatchApproval { id, decision } } pub(crate) fn resolve_elicitation( @@ -228,79 +297,189 @@ impl AppCommand { content: Option, meta: Option, ) -> Self { - Self(Op::ResolveElicitation { + Self::ResolveElicitation { server_name, request_id, decision, content, meta, - }) + } } pub(crate) fn user_input_answer(id: String, response: RequestUserInputResponse) -> Self { - Self(Op::UserInputAnswer { id, response }) + Self::UserInputAnswer { id, response } } pub(crate) fn request_permissions_response( id: String, response: RequestPermissionsResponse, ) -> Self { - Self(Op::RequestPermissionsResponse { id, response }) + Self::RequestPermissionsResponse { id, response } } pub(crate) fn reload_user_config() -> Self { - Self(Op::ReloadUserConfig) + Self::ReloadUserConfig } pub(crate) fn list_skills(cwds: Vec, force_reload: bool) -> Self { - Self(Op::ListSkills { cwds, force_reload }) + Self::ListSkills { cwds, force_reload } } pub(crate) fn compact() -> Self { - Self(Op::Compact) + Self::Compact } pub(crate) fn set_thread_name(name: String) -> Self { - Self(Op::SetThreadName { name }) + Self::SetThreadName { name } } pub(crate) fn thread_rollback(num_turns: u32) -> Self { - Self(Op::ThreadRollback { num_turns }) + Self::ThreadRollback { num_turns } } pub(crate) fn review(review_request: ReviewRequest) -> Self { - Self(Op::Review { review_request }) + Self::Review { review_request } } pub(crate) fn into_core(self) -> Op { - self.0 + match self { + Self::Interrupt => Op::Interrupt, + Self::CleanBackgroundTerminals => Op::CleanBackgroundTerminals, + Self::RealtimeConversationStart(params) => Op::RealtimeConversationStart(params), + Self::RealtimeConversationAudio(params) => Op::RealtimeConversationAudio(params), + Self::RealtimeConversationText(params) => Op::RealtimeConversationText(params), + Self::RealtimeConversationClose => Op::RealtimeConversationClose, + Self::RunUserShellCommand { command } => Op::RunUserShellCommand { command }, + Self::UserTurn { + items, + cwd, + approval_policy, + approvals_reviewer, + permission_profile, + model, + effort, + summary, + service_tier, + final_output_json_schema, + collaboration_mode, + personality, + } => { + let legacy_profile = + legacy_compatible_permission_profile(&permission_profile, cwd.as_path()); + let sandbox_policy = legacy_profile + .to_legacy_sandbox_policy(cwd.as_path()) + .unwrap_or_else(|err| { + unreachable!( + "legacy-compatible permissions must project to legacy policy: {err}" + ) + }); + Op::UserTurn { + items, + environments: None, + cwd, + approval_policy, + approvals_reviewer, + sandbox_policy, + permission_profile: Some(permission_profile), + model, + effort, + summary, + service_tier, + final_output_json_schema, + collaboration_mode, + personality, + } + } + Self::OverrideTurnContext { + cwd, + approval_policy, + approvals_reviewer, + permission_profile, + windows_sandbox_level, + model, + effort, + summary, + service_tier, + collaboration_mode, + personality, + } => Op::OverrideTurnContext { + cwd, + approval_policy, + approvals_reviewer, + sandbox_policy: None, + permission_profile, + windows_sandbox_level, + model, + effort, + summary, + service_tier, + collaboration_mode, + personality, + }, + Self::ExecApproval { + id, + turn_id, + decision, + } => Op::ExecApproval { + id, + turn_id, + decision, + }, + Self::PatchApproval { id, decision } => Op::PatchApproval { id, decision }, + Self::ResolveElicitation { + server_name, + request_id, + decision, + content, + meta, + } => Op::ResolveElicitation { + server_name, + request_id, + decision, + content, + meta, + }, + Self::UserInputAnswer { id, response } => Op::UserInputAnswer { id, response }, + Self::RequestPermissionsResponse { id, response } => { + Op::RequestPermissionsResponse { id, response } + } + Self::ReloadUserConfig => Op::ReloadUserConfig, + Self::ListSkills { cwds, force_reload } => Op::ListSkills { cwds, force_reload }, + Self::Compact => Op::Compact, + Self::SetThreadName { name } => Op::SetThreadName { name }, + Self::Shutdown => Op::Shutdown, + Self::ThreadRollback { num_turns } => Op::ThreadRollback { num_turns }, + Self::Review { review_request } => Op::Review { review_request }, + Self::Other(op) => op, + } } pub(crate) fn is_review(&self) -> bool { - matches!(self.view(), AppCommandView::Review { .. }) + matches!(self, Self::Review { .. }) } pub(crate) fn view(&self) -> AppCommandView<'_> { - match &self.0 { - Op::Interrupt => AppCommandView::Interrupt, - Op::CleanBackgroundTerminals => AppCommandView::CleanBackgroundTerminals, - Op::RealtimeConversationStart(params) => { + match self { + Self::Interrupt => AppCommandView::Interrupt, + Self::CleanBackgroundTerminals => AppCommandView::CleanBackgroundTerminals, + Self::RealtimeConversationStart(params) => { AppCommandView::RealtimeConversationStart(params) } - Op::RealtimeConversationAudio(params) => { + Self::RealtimeConversationAudio(params) => { AppCommandView::RealtimeConversationAudio(params) } - Op::RealtimeConversationText(params) => { + Self::RealtimeConversationText(params) => { AppCommandView::RealtimeConversationText(params) } - Op::RealtimeConversationClose => AppCommandView::RealtimeConversationClose, - Op::RunUserShellCommand { command } => AppCommandView::RunUserShellCommand { command }, - Op::UserTurn { + Self::RealtimeConversationClose => AppCommandView::RealtimeConversationClose, + Self::RunUserShellCommand { command } => { + AppCommandView::RunUserShellCommand { command } + } + Self::UserTurn { items, cwd, approval_policy, approvals_reviewer, - sandbox_policy: _, permission_profile, model, effort, @@ -309,16 +488,12 @@ impl AppCommand { final_output_json_schema, collaboration_mode, personality, - environments: _, } => AppCommandView::UserTurn { items, cwd, approval_policy: *approval_policy, approvals_reviewer, - permission_profile: match permission_profile.as_ref() { - Some(permission_profile) => permission_profile, - None => unreachable!("AppCommand::user_turn always sets permission_profile"), - }, + permission_profile, model, effort: *effort, summary, @@ -327,11 +502,10 @@ impl AppCommand { collaboration_mode, personality, }, - Op::OverrideTurnContext { + Self::OverrideTurnContext { cwd, approval_policy, approvals_reviewer, - sandbox_policy: _, permission_profile, windows_sandbox_level, model, @@ -353,7 +527,7 @@ impl AppCommand { collaboration_mode, personality, }, - Op::ExecApproval { + Self::ExecApproval { id, turn_id, decision, @@ -362,8 +536,8 @@ impl AppCommand { turn_id, decision, }, - Op::PatchApproval { id, decision } => AppCommandView::PatchApproval { id, decision }, - Op::ResolveElicitation { + Self::PatchApproval { id, decision } => AppCommandView::PatchApproval { id, decision }, + Self::ResolveElicitation { server_name, request_id, decision, @@ -376,38 +550,180 @@ impl AppCommand { content, meta, }, - Op::UserInputAnswer { id, response } => { + Self::UserInputAnswer { id, response } => { AppCommandView::UserInputAnswer { id, response } } - Op::RequestPermissionsResponse { id, response } => { + Self::RequestPermissionsResponse { id, response } => { AppCommandView::RequestPermissionsResponse { id, response } } - Op::ReloadUserConfig => AppCommandView::ReloadUserConfig, - Op::ListSkills { cwds, force_reload } => AppCommandView::ListSkills { + Self::ReloadUserConfig => AppCommandView::ReloadUserConfig, + Self::ListSkills { cwds, force_reload } => AppCommandView::ListSkills { cwds, force_reload: *force_reload, }, - Op::Compact => AppCommandView::Compact, - Op::SetThreadName { name } => AppCommandView::SetThreadName { name }, - Op::Shutdown => AppCommandView::Shutdown, - Op::ThreadRollback { num_turns } => AppCommandView::ThreadRollback { + Self::Compact => AppCommandView::Compact, + Self::SetThreadName { name } => AppCommandView::SetThreadName { name }, + Self::Shutdown => AppCommandView::Shutdown, + Self::ThreadRollback { num_turns } => AppCommandView::ThreadRollback { num_turns: *num_turns, }, - Op::Review { review_request } => AppCommandView::Review { review_request }, - op => AppCommandView::Other(op), + Self::Review { review_request } => AppCommandView::Review { review_request }, + Self::Other(op) => AppCommandView::Other(op), } } } impl From for AppCommand { fn from(value: Op) -> Self { - Self(value) + match value { + Op::Interrupt => Self::Interrupt, + Op::CleanBackgroundTerminals => Self::CleanBackgroundTerminals, + Op::RealtimeConversationStart(params) => Self::RealtimeConversationStart(params), + Op::RealtimeConversationAudio(params) => Self::RealtimeConversationAudio(params), + Op::RealtimeConversationText(params) => Self::RealtimeConversationText(params), + Op::RealtimeConversationClose => Self::RealtimeConversationClose, + Op::RunUserShellCommand { command } => Self::RunUserShellCommand { command }, + Op::UserTurn { + items, + environments, + cwd, + approval_policy, + approvals_reviewer, + sandbox_policy, + permission_profile: Some(permission_profile), + model, + effort, + summary, + service_tier, + final_output_json_schema, + collaboration_mode, + personality, + } => { + if environments.is_none() + && legacy_compatible_permission_profile(&permission_profile, cwd.as_path()) + .to_legacy_sandbox_policy(cwd.as_path()) + .is_ok_and(|compatible_policy| compatible_policy == sandbox_policy) + { + Self::UserTurn { + items, + cwd, + approval_policy, + approvals_reviewer, + permission_profile, + model, + effort, + summary, + service_tier, + final_output_json_schema, + collaboration_mode, + personality, + } + } else { + Self::Other(Op::UserTurn { + items, + cwd, + approval_policy, + approvals_reviewer, + sandbox_policy, + permission_profile: Some(permission_profile), + model, + effort, + summary, + service_tier, + final_output_json_schema, + collaboration_mode, + personality, + environments, + }) + } + } + Op::OverrideTurnContext { + cwd, + approval_policy, + approvals_reviewer, + sandbox_policy, + permission_profile, + windows_sandbox_level, + model, + effort, + summary, + service_tier, + collaboration_mode, + personality, + } => { + if sandbox_policy.is_none() { + Self::OverrideTurnContext { + cwd, + approval_policy, + approvals_reviewer, + permission_profile, + windows_sandbox_level, + model, + effort, + summary, + service_tier, + collaboration_mode, + personality, + } + } else { + Self::Other(Op::OverrideTurnContext { + cwd, + approval_policy, + approvals_reviewer, + sandbox_policy, + permission_profile, + windows_sandbox_level, + model, + effort, + summary, + service_tier, + collaboration_mode, + personality, + }) + } + } + Op::ExecApproval { + id, + turn_id, + decision, + } => Self::ExecApproval { + id, + turn_id, + decision, + }, + Op::PatchApproval { id, decision } => Self::PatchApproval { id, decision }, + Op::ResolveElicitation { + server_name, + request_id, + decision, + content, + meta, + } => Self::ResolveElicitation { + server_name, + request_id, + decision, + content, + meta, + }, + Op::UserInputAnswer { id, response } => Self::UserInputAnswer { id, response }, + Op::RequestPermissionsResponse { id, response } => { + Self::RequestPermissionsResponse { id, response } + } + Op::ReloadUserConfig => Self::ReloadUserConfig, + Op::ListSkills { cwds, force_reload } => Self::ListSkills { cwds, force_reload }, + Op::Compact => Self::Compact, + Op::SetThreadName { name } => Self::SetThreadName { name }, + Op::Shutdown => Self::Shutdown, + Op::ThreadRollback { num_turns } => Self::ThreadRollback { num_turns }, + Op::Review { review_request } => Self::Review { review_request }, + op => Self::Other(op), + } } } impl From<&Op> for AppCommand { fn from(value: &Op) -> Self { - Self(value.clone()) + Self::from(value.clone()) } } @@ -419,6 +735,6 @@ impl From<&AppCommand> for AppCommand { impl From for Op { fn from(value: AppCommand) -> Self { - value.0 + value.into_core() } }