From 1a25d8b6e561d90c883b7792b8d2a37d41b7605a Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 18 May 2026 19:56:00 -0700 Subject: [PATCH 1/2] [3 of 7] Remove UserTurn (#23075) **Stack position:** [3 of 7] ## Summary This PR finishes the input-op consolidation by moving the remaining `Op::UserTurn` callers onto `Op::UserInput` and deleting `Op::UserTurn`. This touches a lot of files, but it is a low-risk mechanical migration. ## Stack 1. [1 of 7] [Add thread settings to UserInput](https://github.com/openai/codex/pull/23080) 2. [2 of 7] [Remove UserInputWithTurnContext](https://github.com/openai/codex/pull/23081) 3. [3 of 7] [Remove UserTurn](https://github.com/openai/codex/pull/23075) (this PR) 4. [4 of 7] [Placeholder for OverrideTurnContext cleanup](https://github.com/openai/codex/pull/23087) 5. [5 of 7] [Replace OverrideTurnContext with ThreadSettings](https://github.com/openai/codex/pull/22508) 6. [6 of 7] [Add app-server thread settings API](https://github.com/openai/codex/pull/22509) 7. [7 of 7] [Sync TUI thread settings](https://github.com/openai/codex/pull/22510) --- codex-rs/core/src/guardian/review_session.rs | 35 +-- codex-rs/core/src/session/handlers.rs | 55 +---- codex-rs/core/src/session/tests.rs | 35 +-- codex-rs/core/tests/common/test_codex.rs | 42 ++-- codex-rs/core/tests/suite/apply_patch_cli.rs | 31 +-- codex-rs/core/tests/suite/approvals.rs | 63 ++--- codex-rs/core/tests/suite/client.rs | 70 +++--- codex-rs/core/tests/suite/code_mode.rs | 31 +-- .../tests/suite/collaboration_instructions.rs | 96 ++++---- codex-rs/core/tests/suite/compact.rs | 31 +-- codex-rs/core/tests/suite/exec_policy.rs | 64 ++--- codex-rs/core/tests/suite/image_rollout.rs | 62 ++--- codex-rs/core/tests/suite/items.rs | 26 +-- codex-rs/core/tests/suite/json_result.rs | 31 +-- .../core/tests/suite/mcp_turn_metadata.rs | 34 +-- codex-rs/core/tests/suite/model_switching.rs | 31 +-- .../core/tests/suite/model_visible_layout.rs | 157 +++++++------ codex-rs/core/tests/suite/models_cache_ttl.rs | 31 +-- .../core/tests/suite/models_etag_responses.rs | 31 +-- codex-rs/core/tests/suite/pending_input.rs | 31 +-- codex-rs/core/tests/suite/personality.rs | 32 +-- codex-rs/core/tests/suite/prompt_caching.rs | 153 ++++++------ codex-rs/core/tests/suite/remote_env.rs | 31 +-- codex-rs/core/tests/suite/remote_models.rs | 157 ++++--------- .../core/tests/suite/request_permissions.rs | 32 +-- .../tests/suite/request_permissions_tool.rs | 32 +-- .../core/tests/suite/request_user_input.rs | 101 ++++---- .../suite/responses_api_proxy_headers.rs | 31 +-- codex-rs/core/tests/suite/rmcp_client.rs | 31 +-- .../tests/suite/safety_check_downgrade.rs | 31 +-- codex-rs/core/tests/suite/shell_snapshot.rs | 124 +++++----- codex-rs/core/tests/suite/skill_approval.rs | 31 +-- codex-rs/core/tests/suite/skills.rs | 31 +-- codex-rs/core/tests/suite/sqlite_state.rs | 31 +-- codex-rs/core/tests/suite/tool_harness.rs | 155 +++++++------ codex-rs/core/tests/suite/tool_parallelism.rs | 62 ++--- codex-rs/core/tests/suite/truncation.rs | 31 +-- codex-rs/core/tests/suite/unified_exec.rs | 218 ++++++++++-------- codex-rs/core/tests/suite/user_shell_cmd.rs | 31 +-- codex-rs/core/tests/suite/view_image.rs | 31 +-- .../core/tests/suite/websocket_fallback.rs | 31 +-- codex-rs/protocol/src/protocol.rs | 69 ------ 42 files changed, 1289 insertions(+), 1174 deletions(-) diff --git a/codex-rs/core/src/guardian/review_session.rs b/codex-rs/core/src/guardian/review_session.rs index afb5882a69..389373c8de 100644 --- a/codex-rs/core/src/guardian/review_session.rs +++ b/codex-rs/core/src/guardian/review_session.rs @@ -706,22 +706,29 @@ async fn run_review_on_session( let submit_result = run_before_review_deadline( deadline, params.external_cancel.as_ref(), - Box::pin(review_session.codex.submit(Op::UserTurn { - environments: None, + Box::pin(review_session.codex.submit(Op::UserInput { items: prompt_items.items, - #[allow(deprecated)] - cwd: params.parent_turn.cwd.to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: legacy_sandbox_policy, - permission_profile: Some(guardian_permission_profile), - model: params.model.clone(), - effort: params.reasoning_effort, - summary: Some(params.reasoning_summary), - service_tier: None, + environments: None, final_output_json_schema: Some(params.schema.clone()), - collaboration_mode: None, - personality: params.personality, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + #[allow(deprecated)] + cwd: Some(params.parent_turn.cwd.to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(legacy_sandbox_policy), + permission_profile: Some(guardian_permission_profile), + summary: Some(params.reasoning_summary), + personality: params.personality, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: params.model.clone(), + reasoning_effort: params.reasoning_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, })), ) .await; diff --git a/codex-rs/core/src/session/handlers.rs b/codex-rs/core/src/session/handlers.rs index f8a4fa2920..e01ee28f99 100644 --- a/codex-rs/core/src/session/handlers.rs +++ b/codex-rs/core/src/session/handlers.rs @@ -49,9 +49,6 @@ use codex_protocol::request_permissions::RequestPermissionsResponse; use codex_protocol::request_user_input::RequestUserInputResponse; use crate::context_manager::is_user_turn_boundary; -use codex_protocol::config_types::CollaborationMode; -use codex_protocol::config_types::ModeKind; -use codex_protocol::config_types::Settings; use codex_protocol::dynamic_tools::DynamicToolResponse; use codex_protocol::items::UserMessageItem; use codex_protocol::mcp::RequestId as ProtocolRequestId; @@ -114,56 +111,6 @@ pub(super) async fn user_input_or_turn_inner( mirror_user_text_to_realtime: Option<()>, ) { let (items, updates, responsesapi_client_metadata) = match op { - Op::UserTurn { - cwd, - approval_policy, - approvals_reviewer, - sandbox_policy, - permission_profile, - model, - effort, - summary, - service_tier, - final_output_json_schema, - items, - collaboration_mode, - personality, - environments, - } => { - let collaboration_mode = collaboration_mode.or_else(|| { - Some(CollaborationMode { - mode: ModeKind::Default, - settings: Settings { - model: model.clone(), - reasoning_effort: effort, - developer_instructions: None, - }, - }) - }); - ( - items, - SessionSettingsUpdate { - cwd: Some(cwd), - approval_policy: Some(approval_policy), - approvals_reviewer, - sandbox_policy: Some(sandbox_policy), - workspace_roots: None, - profile_workspace_roots: None, - permission_profile, - active_permission_profile: None, - windows_sandbox_level: None, - collaboration_mode, - reasoning_summary: summary, - service_tier, - final_output_json_schema: Some(final_output_json_schema), - environments, - personality, - app_server_client_name: None, - app_server_client_version: None, - }, - None, - ) - } Op::UserInput { items, environments, @@ -826,7 +773,7 @@ pub(super) async fn submission_loop( .await; false } - Op::UserInput { .. } | Op::UserTurn { .. } => { + Op::UserInput { .. } => { user_input_or_turn(&sess, sub.id.clone(), sub.op).await; false } diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index a6d82e211d..4f090a8a3a 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -5197,7 +5197,7 @@ fn submission_dispatch_span_uses_debug_for_realtime_audio() { } #[test] -fn op_kind_distinguishes_turn_ops() { +fn op_kind_for_input_and_context_ops() { assert_eq!( Op::OverrideTurnContext { cwd: None, @@ -5237,24 +5237,31 @@ async fn user_turn_updates_approvals_reviewer() { handlers::user_input_or_turn( &session, "sub-1".to_string(), - Op::UserTurn { - environments: None, + Op::UserInput { items: vec![UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), }], - cwd: config.cwd.to_path_buf(), - approval_policy: config.permissions.approval_policy.value(), - approvals_reviewer: Some(codex_config::types::ApprovalsReviewer::AutoReview), - sandbox_policy: config.legacy_sandbox_policy(), - permission_profile: None, - model: turn_context.model_info.slug.clone(), - effort: config.model_reasoning_effort, - summary: config.model_reasoning_summary, - service_tier: None, + environments: None, final_output_json_schema: None, - collaboration_mode: None, - personality: config.personality, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(config.cwd.to_path_buf()), + approval_policy: Some(config.permissions.approval_policy.value()), + approvals_reviewer: Some(codex_config::types::ApprovalsReviewer::AutoReview), + sandbox_policy: Some(config.legacy_sandbox_policy()), + summary: config.model_reasoning_summary, + personality: config.personality, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: turn_context.model_info.slug.clone(), + reasoning_effort: config.model_reasoning_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, }, ) .await; diff --git a/codex-rs/core/tests/common/test_codex.rs b/codex-rs/core/tests/common/test_codex.rs index 2007104868..a1ba7b8fb2 100644 --- a/codex-rs/core/tests/common/test_codex.rs +++ b/codex-rs/core/tests/common/test_codex.rs @@ -190,8 +190,14 @@ pub enum ApplyPatchModelOutput { ShellCommandViaHeredoc, } -/// Returns the permission fields required by `Op::UserTurn` for tests that -/// construct the op directly. +/// A collection of different ways the model can output an apply_patch call +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ShellModelOutput { + ShellCommand, + // UnifiedExec has its own set of tests +} + +/// Returns the permission fields required by test thread-settings overrides. pub fn turn_permission_fields( permission_profile: PermissionProfile, cwd: &Path, @@ -754,24 +760,30 @@ impl TestCodex { turn_permission_fields(permission_profile, self.config.cwd.as_path()); let session_model = self.session_configured.model.clone(); self.codex - .submit(Op::UserTurn { - environments, + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.into(), text_elements: Vec::new(), }], + environments, final_output_json_schema: None, - cwd: self.config.cwd.to_path_buf(), - approval_policy, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(self.config.cwd.to_path_buf()), + approval_policy: Some(approval_policy), + sandbox_policy: Some(sandbox_policy), + permission_profile, + service_tier, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/apply_patch_cli.rs b/codex-rs/core/tests/suite/apply_patch_cli.rs index 05379517e5..1e325e1bb5 100644 --- a/codex-rs/core/tests/suite/apply_patch_cli.rs +++ b/codex-rs/core/tests/suite/apply_patch_cli.rs @@ -83,24 +83,29 @@ async fn submit_without_wait_with_turn_permissions( let test = harness.test(); let session_model = test.session_configured.model.clone(); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: harness.cwd().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(harness.cwd().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; Ok(()) diff --git a/codex-rs/core/tests/suite/approvals.rs b/codex-rs/core/tests/suite/approvals.rs index 5045755e32..f08262eb6f 100644 --- a/codex-rs/core/tests/suite/approvals.rs +++ b/codex-rs/core/tests/suite/approvals.rs @@ -644,24 +644,29 @@ async fn submit_turn( let session_model = test.session_configured.model.clone(); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd.path().to_path_buf(), - approval_policy, - approvals_reviewer: Some(ApprovalsReviewer::User), - sandbox_policy, - permission_profile: None, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd.path().to_path_buf()), + approval_policy: Some(approval_policy), + approvals_reviewer: Some(ApprovalsReviewer::User), + sandbox_policy: Some(sandbox_policy), + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -2578,24 +2583,30 @@ async fn matched_prefix_rule_runs_unsandboxed_under_zsh_fork() -> Result<()> { let (sandbox_policy, permission_profile) = turn_permission_fields(permission_profile, test.cwd.path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "run allowed touch under zsh fork".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd.path().to_path_buf(), - approval_policy, - approvals_reviewer: Some(ApprovalsReviewer::User), - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd.path().to_path_buf()), + approval_policy: Some(approval_policy), + approvals_reviewer: Some(ApprovalsReviewer::User), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 818e0eba7b..f6731a0ea4 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1758,12 +1758,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), ) .await; - let TestCodex { - codex, - config, - session_configured, - .. - } = test_codex().with_model("gpt-5.4").build(&server).await?; + let TestCodex { codex, config, .. } = test_codex().with_model("gpt-5.4").build(&server).await?; let collaboration_mode = CollaborationMode { mode: ModeKind::Default, @@ -1775,28 +1770,26 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re }; codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello".into(), text_elements: Vec::new(), }], - cwd: config.cwd.to_path_buf(), - approval_policy: config.permissions.approval_policy.value(), - approvals_reviewer: None, - sandbox_policy: config.legacy_sandbox_policy(), - permission_profile: None, - model: session_configured.model.clone(), - effort: Some(ReasoningEffort::Low), - summary: Some( - config - .model_reasoning_summary - .unwrap_or(ReasoningSummary::Auto), - ), - service_tier: None, - collaboration_mode: Some(collaboration_mode), + environments: None, final_output_json_schema: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(config.cwd.to_path_buf()), + approval_policy: Some(config.permissions.approval_policy.value()), + sandbox_policy: Some(config.legacy_sandbox_policy()), + summary: Some( + config + .model_reasoning_summary + .unwrap_or(ReasoningSummary::Auto), + ), + collaboration_mode: Some(collaboration_mode), + ..Default::default() + }, }) .await?; @@ -1898,24 +1891,29 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default() .await?; codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello".into(), text_elements: Vec::new(), }], - cwd: config.cwd.to_path_buf(), - approval_policy: config.permissions.approval_policy.value(), - approvals_reviewer: None, - sandbox_policy: config.legacy_sandbox_policy(), - permission_profile: None, - model: session_configured.model, - effort: None, - summary: Some(ReasoningSummary::Concise), - service_tier: None, - collaboration_mode: None, + environments: None, final_output_json_schema: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(config.cwd.to_path_buf()), + approval_policy: Some(config.permissions.approval_policy.value()), + sandbox_policy: Some(config.legacy_sandbox_policy()), + summary: Some(ReasoningSummary::Concise), + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_configured.model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await .unwrap(); diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index beae9e4afb..19373b52e4 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -2584,24 +2584,29 @@ text( turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "use exec to inspect and call hidden tools".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: test.session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: test.session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index 2b0c20ab26..bd01dc4e7d 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -178,28 +178,26 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> { let collaboration_mode = collab_mode_with_instructions(Some(collab_text)); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello".into(), text_elements: Vec::new(), }], - cwd: test.config.cwd.to_path_buf(), - approval_policy: test.config.permissions.approval_policy.value(), - approvals_reviewer: None, - sandbox_policy: test.config.legacy_sandbox_policy(), - permission_profile: None, - model: test.session_configured.model.clone(), - effort: None, - summary: Some( - test.config - .model_reasoning_summary - .unwrap_or(codex_protocol::config_types::ReasoningSummary::Auto), - ), - service_tier: None, - collaboration_mode: Some(collaboration_mode), + environments: None, final_output_json_schema: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.config.cwd.to_path_buf()), + approval_policy: Some(test.config.permissions.approval_policy.value()), + sandbox_policy: Some(test.config.legacy_sandbox_policy()), + summary: Some( + test.config + .model_reasoning_summary + .unwrap_or(codex_protocol::config_types::ReasoningSummary::Auto), + ), + collaboration_mode: Some(collaboration_mode), + ..Default::default() + }, }) .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; @@ -230,28 +228,26 @@ async fn collaboration_instructions_omitted_when_disabled() -> Result<()> { let collaboration_mode = collab_mode_with_instructions(Some("turn instructions")); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello".into(), text_elements: Vec::new(), }], - cwd: test.config.cwd.to_path_buf(), - approval_policy: test.config.permissions.approval_policy.value(), - approvals_reviewer: None, - sandbox_policy: test.config.legacy_sandbox_policy(), - permission_profile: None, - model: test.session_configured.model.clone(), - effort: None, - summary: Some( - test.config - .model_reasoning_summary - .unwrap_or(codex_protocol::config_types::ReasoningSummary::Auto), - ), - service_tier: None, - collaboration_mode: Some(collaboration_mode), + environments: None, final_output_json_schema: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.config.cwd.to_path_buf()), + approval_policy: Some(test.config.permissions.approval_policy.value()), + sandbox_policy: Some(test.config.legacy_sandbox_policy()), + summary: Some( + test.config + .model_reasoning_summary + .unwrap_or(codex_protocol::config_types::ReasoningSummary::Auto), + ), + collaboration_mode: Some(collaboration_mode), + ..Default::default() + }, }) .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; @@ -355,28 +351,26 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu .await?; test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello".into(), text_elements: Vec::new(), }], - cwd: test.config.cwd.to_path_buf(), - approval_policy: test.config.permissions.approval_policy.value(), - approvals_reviewer: None, - sandbox_policy: test.config.legacy_sandbox_policy(), - permission_profile: None, - model: test.session_configured.model.clone(), - effort: None, - summary: Some( - test.config - .model_reasoning_summary - .unwrap_or(codex_protocol::config_types::ReasoningSummary::Auto), - ), - service_tier: None, - collaboration_mode: Some(turn_mode), + environments: None, final_output_json_schema: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.config.cwd.to_path_buf()), + approval_policy: Some(test.config.permissions.approval_policy.value()), + sandbox_policy: Some(test.config.legacy_sandbox_policy()), + summary: Some( + test.config + .model_reasoning_summary + .unwrap_or(codex_protocol::config_types::ReasoningSummary::Auto), + ), + collaboration_mode: Some(turn_mode), + ..Default::default() + }, }) .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; diff --git a/codex-rs/core/tests/suite/compact.rs b/codex-rs/core/tests/suite/compact.rs index d1384ff800..2c789a271d 100644 --- a/codex-rs/core/tests/suite/compact.rs +++ b/codex-rs/core/tests/suite/compact.rs @@ -88,24 +88,29 @@ fn ev_shell_command_call(call_id: &str, command: &str) -> serde_json::Value { fn disabled_permission_user_turn(text: impl Into, cwd: PathBuf, model: String) -> Op { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); - Op::UserTurn { - environments: None, + Op::UserInput { items: vec![UserInput::Text { text: text.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, } } diff --git a/codex-rs/core/tests/suite/exec_policy.rs b/codex-rs/core/tests/suite/exec_policy.rs index 3f6c9cfdc0..f1e01c22ce 100644 --- a/codex-rs/core/tests/suite/exec_policy.rs +++ b/codex-rs/core/tests/suite/exec_policy.rs @@ -46,24 +46,31 @@ async fn submit_user_turn( let (sandbox_policy, permission_profile) = turn_permission_fields(permission_profile, test.config.cwd.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd_path().to_path_buf()), + approval_policy: Some(approval_policy), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: collaboration_mode.or({ + Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }) + }), + ..Default::default() + }, }) .await?; Ok(()) @@ -126,24 +133,29 @@ async fn execpolicy_blocks_shell_invocation() -> Result<()> { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "run shell command".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd_path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/image_rollout.rs b/codex-rs/core/tests/suite/image_rollout.rs index 3404edbb1e..085ca96f37 100644 --- a/codex-rs/core/tests/suite/image_rollout.rs +++ b/codex-rs/core/tests/suite/image_rollout.rs @@ -113,8 +113,7 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu turn_permission_fields(PermissionProfile::Disabled, cwd.path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![ UserInput::LocalImage { path: abs_path.clone(), @@ -125,18 +124,24 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu text_elements: Vec::new(), }, ], + environments: None, final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -204,8 +209,7 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()> turn_permission_fields(PermissionProfile::Disabled, cwd.path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![ UserInput::Image { image_url: image_url.clone(), @@ -216,18 +220,24 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()> text_elements: Vec::new(), }, ], + environments: None, final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/items.rs b/codex-rs/core/tests/suite/items.rs index 0015a418ca..82e01e2451 100644 --- a/codex-rs/core/tests/suite/items.rs +++ b/codex-rs/core/tests/suite/items.rs @@ -44,30 +44,28 @@ use std::path::PathBuf; fn disabled_plan_turn( text: &str, - model: String, + _model: String, collaboration_mode: CollaborationMode, ) -> anyhow::Result { let cwd = std::env::current_dir()?; let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); - Ok(Op::UserTurn { - environments: None, + Ok(Op::UserInput { items: vec![UserInput::Text { text: text.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: Some(collaboration_mode), - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(collaboration_mode), + ..Default::default() + }, }) } diff --git a/codex-rs/core/tests/suite/json_result.rs b/codex-rs/core/tests/suite/json_result.rs index c4ce8ac908..e81d7aa82a 100644 --- a/codex-rs/core/tests/suite/json_result.rs +++ b/codex-rs/core/tests/suite/json_result.rs @@ -75,24 +75,29 @@ async fn codex_returns_json_result(model: String) -> anyhow::Result<()> { // 1) Normal user input – should hit server once. codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello world".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: Some(serde_json::from_str(SCHEMA)?), - cwd: cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/mcp_turn_metadata.rs b/codex-rs/core/tests/suite/mcp_turn_metadata.rs index 17fe33dc8b..16e6cbe37d 100644 --- a/codex-rs/core/tests/suite/mcp_turn_metadata.rs +++ b/codex-rs/core/tests/suite/mcp_turn_metadata.rs @@ -64,27 +64,35 @@ async fn submit_user_turn( approval_policy: AskForApproval, collaboration_mode: Option, ) -> Result<()> { + let session_model = test.session_configured.model.clone(); let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, test.cwd.path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: text.to_string(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd.path().to_path_buf(), - approval_policy, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: test.session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd.path().to_path_buf()), + approval_policy: Some(approval_policy), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: collaboration_mode.or({ + Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }) + }), + ..Default::default() + }, }) .await?; Ok(()) diff --git a/codex-rs/core/tests/suite/model_switching.rs b/codex-rs/core/tests/suite/model_switching.rs index adce6e36eb..1c9358a8a3 100644 --- a/codex-rs/core/tests/suite/model_switching.rs +++ b/codex-rs/core/tests/suite/model_switching.rs @@ -42,21 +42,26 @@ use wiremock::MockServer; fn read_only_user_turn(test: &TestCodex, items: Vec, model: String) -> Op { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::read_only(), test.cwd_path()); - Op::UserTurn { - environments: None, + Op::UserInput { items, + environments: None, final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model, - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd_path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model, + reasoning_effort: test.config.model_reasoning_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, } } diff --git a/codex-rs/core/tests/suite/model_visible_layout.rs b/codex-rs/core/tests/suite/model_visible_layout.rs index d9c636b35f..9cf83424c9 100644 --- a/codex-rs/core/tests/suite/model_visible_layout.rs +++ b/codex-rs/core/tests/suite/model_visible_layout.rs @@ -117,24 +117,29 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> { turn_permission_fields(PermissionProfile::read_only(), first_turn_cwd.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "first turn".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: first_turn_cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: first_sandbox_policy, - permission_profile: first_permission_profile, - model: test.session_configured.model.clone(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(first_turn_cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(first_sandbox_policy), + permission_profile: first_permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: test.session_configured.model.clone(), + reasoning_effort: test.config.model_reasoning_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; wait_for_event(&test.codex, |event| { @@ -147,24 +152,30 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> { preturn_context_diff_cwd.as_path(), ); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "second turn with context updates".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: preturn_context_diff_cwd, - approval_policy: AskForApproval::OnRequest, - approvals_reviewer: None, - sandbox_policy: second_sandbox_policy, - permission_profile: second_permission_profile, - model: test.session_configured.model.clone(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: Some(Personality::Friendly), + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(preturn_context_diff_cwd), + approval_policy: Some(AskForApproval::OnRequest), + sandbox_policy: Some(second_sandbox_policy), + permission_profile: second_permission_profile, + personality: Some(Personality::Friendly), + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: test.session_configured.model.clone(), + reasoning_effort: test.config.model_reasoning_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; wait_for_event(&test.codex, |event| { @@ -230,24 +241,29 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R turn_permission_fields(PermissionProfile::read_only(), cwd_one.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "first turn in agents_one".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd_one.clone(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: first_sandbox_policy, - permission_profile: first_permission_profile, - model: test.session_configured.model.clone(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd_one.clone()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(first_sandbox_policy), + permission_profile: first_permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: test.session_configured.model.clone(), + reasoning_effort: test.config.model_reasoning_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; wait_for_event(&test.codex, |event| { @@ -258,24 +274,29 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R let (second_sandbox_policy, second_permission_profile) = turn_permission_fields(PermissionProfile::read_only(), cwd_two.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "second turn in agents_two".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd_two, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: second_sandbox_policy, - permission_profile: second_permission_profile, - model: test.session_configured.model.clone(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd_two), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(second_sandbox_policy), + permission_profile: second_permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: test.session_configured.model.clone(), + reasoning_effort: test.config.model_reasoning_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; wait_for_event(&test.codex, |event| { @@ -377,24 +398,30 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul ); resumed .codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "resume and change personality".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: resume_override_cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: resumed.session_configured.model.clone(), - effort: resumed.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: Some(Personality::Friendly), + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(resume_override_cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + personality: Some(Personality::Friendly), + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: resumed.session_configured.model.clone(), + reasoning_effort: resumed.config.model_reasoning_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; wait_for_event(&resumed.codex, |event| { diff --git a/codex-rs/core/tests/suite/models_cache_ttl.rs b/codex-rs/core/tests/suite/models_cache_ttl.rs index 8463ee1cf1..3b729738df 100644 --- a/codex-rs/core/tests/suite/models_cache_ttl.rs +++ b/codex-rs/core/tests/suite/models_cache_ttl.rs @@ -92,24 +92,29 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> { turn_permission_fields(PermissionProfile::Disabled, test.cwd_path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hi".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: codex_protocol::protocol::AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: test.session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd_path().to_path_buf()), + approval_policy: Some(codex_protocol::protocol::AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: test.session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/models_etag_responses.rs b/codex-rs/core/tests/suite/models_etag_responses.rs index 346c503c25..49f34d8644 100644 --- a/codex-rs/core/tests/suite/models_etag_responses.rs +++ b/codex-rs/core/tests/suite/models_etag_responses.rs @@ -104,24 +104,29 @@ async fn refresh_models_on_models_etag_mismatch_and_avoid_duplicate_models_fetch .await; codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "please run a tool".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd_path, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd_path), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/pending_input.rs b/codex-rs/core/tests/suite/pending_input.rs index d511c8f0b6..b057b86171 100644 --- a/codex-rs/core/tests/suite/pending_input.rs +++ b/codex-rs/core/tests/suite/pending_input.rs @@ -113,24 +113,29 @@ async fn submit_danger_full_access_user_turn(test: &TestCodex, text: &str) { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: text.to_string(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.config.cwd.to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: test.session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.config.cwd.to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: test.session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await .unwrap_or_else(|err| panic!("submit user turn: {err}")); diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 09eb61fa69..8924ca2a25 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -60,24 +60,30 @@ fn read_only_text_turn_with_personality( ) -> Op { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::read_only(), test.cwd_path()); - Op::UserTurn { - environments: None, + Op::UserInput { items: vec![UserInput::Text { text: text.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model, - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd_path().to_path_buf()), + approval_policy: Some(approval_policy), + sandbox_policy: Some(sandbox_policy), + permission_profile, + personality, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model, + reasoning_effort: test.config.model_reasoning_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, } } diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 2023778a11..accf3fb789 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -714,7 +714,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res .await?; wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - // Second turn using per-turn overrides via UserTurn + // Second turn using per-turn thread-settings overrides. let new_cwd = TempDir::new().unwrap(); let writable = TempDir::new().unwrap(); let permission_profile = PermissionProfile::workspace_write_with( @@ -726,24 +726,24 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res let (sandbox_policy, permission_profile) = turn_permission_fields(permission_profile, new_cwd.path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello 2".into(), text_elements: Vec::new(), }], - cwd: new_cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: "o3".to_string(), - effort: Some(ReasoningEffort::High), - summary: Some(ReasoningSummary::Detailed), - service_tier: None, - collaboration_mode: None, + environments: None, final_output_json_schema: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(new_cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + model: Some("o3".to_string()), + effort: Some(Some(ReasoningEffort::High)), + summary: Some(ReasoningSummary::Detailed), + ..Default::default() + }, }) .await?; wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; @@ -840,47 +840,57 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a let default_summary = config.model_reasoning_summary; codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello 1".into(), text_elements: Vec::new(), }], - cwd: default_cwd.to_path_buf(), - approval_policy: default_approval_policy, - approvals_reviewer: None, - sandbox_policy: default_sandbox_policy.clone(), - permission_profile: None, - model: default_model.clone(), - effort: default_effort, - summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)), - service_tier: None, - collaboration_mode: None, + environments: None, final_output_json_schema: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(default_cwd.to_path_buf()), + approval_policy: Some(default_approval_policy), + sandbox_policy: Some(default_sandbox_policy.clone()), + summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)), + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: default_model.clone(), + reasoning_effort: default_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello 2".into(), text_elements: Vec::new(), }], - cwd: default_cwd.to_path_buf(), - approval_policy: default_approval_policy, - approvals_reviewer: None, - sandbox_policy: default_sandbox_policy.clone(), - permission_profile: None, - model: default_model.clone(), - effort: default_effort, - summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)), - service_tier: None, - collaboration_mode: None, + environments: None, final_output_json_schema: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(default_cwd.to_path_buf()), + approval_policy: Some(default_approval_policy), + sandbox_policy: Some(default_sandbox_policy.clone()), + summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)), + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: default_model.clone(), + reasoning_effort: default_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; @@ -969,24 +979,29 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu let default_summary = config.model_reasoning_summary; codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello 1".into(), text_elements: Vec::new(), }], - cwd: default_cwd.to_path_buf(), - approval_policy: default_approval_policy, - approvals_reviewer: None, - sandbox_policy: default_sandbox_policy.clone(), - permission_profile: None, - model: default_model, - effort: default_effort, - summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)), - service_tier: None, - collaboration_mode: None, + environments: None, final_output_json_schema: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(default_cwd.to_path_buf()), + approval_policy: Some(default_approval_policy), + sandbox_policy: Some(default_sandbox_policy.clone()), + summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)), + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: default_model, + reasoning_effort: default_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; @@ -994,24 +1009,30 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, default_cwd.as_path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello 2".into(), text_elements: Vec::new(), }], - cwd: default_cwd.to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: "o3".to_string(), - effort: Some(ReasoningEffort::High), - summary: Some(ReasoningSummary::Detailed), - service_tier: None, - collaboration_mode: None, + environments: None, final_output_json_schema: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(default_cwd.to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + summary: Some(ReasoningSummary::Detailed), + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: "o3".to_string(), + reasoning_effort: Some(ReasoningEffort::High), + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; diff --git a/codex-rs/core/tests/suite/remote_env.rs b/codex-rs/core/tests/suite/remote_env.rs index e19379ffc8..873796ff6c 100644 --- a/codex-rs/core/tests/suite/remote_env.rs +++ b/codex-rs/core/tests/suite/remote_env.rs @@ -67,24 +67,29 @@ async fn submit_turn_with_approval_and_environments( environments: Vec, ) -> Result<()> { test.codex - .submit(Op::UserTurn { - environments: Some(environments), + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.into(), text_elements: Vec::new(), }], + environments: Some(environments), final_output_json_schema: None, - cwd: test.cwd.path().to_path_buf(), - approval_policy: AskForApproval::OnRequest, - approvals_reviewer: Some(ApprovalsReviewer::User), - sandbox_policy: SandboxPolicy::new_read_only_policy(), - permission_profile: None, - model: test.session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::OnRequest), + approvals_reviewer: Some(ApprovalsReviewer::User), + sandbox_policy: Some(SandboxPolicy::new_read_only_policy()), + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: test.session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index d1caf24836..adc73d8a79 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -142,9 +142,7 @@ async fn remote_models_config_context_window_override_clamps_to_max_context_wind ) .await; - let TestCodex { - codex, cwd, config, .. - } = test_codex() + let TestCodex { codex, .. } = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { config.model = Some(requested_model.to_string()); @@ -154,24 +152,15 @@ async fn remote_models_config_context_window_override_clamps_to_max_context_wind .await?; codex - .submit(Op::UserTurn { + .submit(Op::UserInput { items: vec![UserInput::Text { text: "check context window".into(), text_elements: Vec::new(), }], - final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: config.permissions.approval_policy.value(), - approvals_reviewer: None, - sandbox_policy: config.legacy_sandbox_policy(), - model: requested_model.to_string(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - permission_profile: None, - personality: None, environments: None, + final_output_json_schema: None, + responsesapi_client_metadata: None, + thread_settings: Default::default(), }) .await?; @@ -220,9 +209,7 @@ async fn remote_models_config_override_above_max_uses_max_context_window() -> Re ) .await; - let TestCodex { - codex, cwd, config, .. - } = test_codex() + let TestCodex { codex, .. } = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { config.model = Some(requested_model.to_string()); @@ -232,24 +219,15 @@ async fn remote_models_config_override_above_max_uses_max_context_window() -> Re .await?; codex - .submit(Op::UserTurn { + .submit(Op::UserInput { items: vec![UserInput::Text { text: "check context window".into(), text_elements: Vec::new(), }], - final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: config.permissions.approval_policy.value(), - approvals_reviewer: None, - sandbox_policy: config.legacy_sandbox_policy(), - model: requested_model.to_string(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - permission_profile: None, - personality: None, environments: None, + final_output_json_schema: None, + responsesapi_client_metadata: None, + thread_settings: Default::default(), }) .await?; @@ -298,9 +276,7 @@ async fn remote_models_use_context_window_when_config_override_is_absent() -> Re ) .await; - let TestCodex { - codex, cwd, config, .. - } = test_codex() + let TestCodex { codex, .. } = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { config.model = Some(requested_model.to_string()); @@ -309,24 +285,15 @@ async fn remote_models_use_context_window_when_config_override_is_absent() -> Re .await?; codex - .submit(Op::UserTurn { + .submit(Op::UserInput { items: vec![UserInput::Text { text: "check context window".into(), text_elements: Vec::new(), }], - final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: config.permissions.approval_policy.value(), - approvals_reviewer: None, - sandbox_policy: config.legacy_sandbox_policy(), - model: requested_model.to_string(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - permission_profile: None, - personality: None, environments: None, + final_output_json_schema: None, + responsesapi_client_metadata: None, + thread_settings: Default::default(), }) .await?; @@ -388,9 +355,7 @@ async fn remote_models_long_model_slug_is_sent_with_high_reasoning() -> Result<( ) .await; - let TestCodex { - codex, cwd, config, .. - } = test_codex() + let TestCodex { codex, .. } = test_codex() .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) .with_config(|config| { config.model = Some(requested_model.to_string()); @@ -399,24 +364,15 @@ async fn remote_models_long_model_slug_is_sent_with_high_reasoning() -> Result<( .await?; codex - .submit(Op::UserTurn { + .submit(Op::UserInput { items: vec![UserInput::Text { text: "check model slug".into(), text_elements: Vec::new(), }], - final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: config.permissions.approval_policy.value(), - approvals_reviewer: None, - sandbox_policy: config.legacy_sandbox_policy(), - permission_profile: None, - model: requested_model.to_string(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, environments: None, + final_output_json_schema: None, + responsesapi_client_metadata: None, + thread_settings: Default::default(), }) .await?; @@ -452,36 +408,21 @@ async fn namespaced_model_slug_uses_catalog_metadata_without_fallback_warning() ) .await; - let TestCodex { - codex, cwd, config, .. - } = test_codex() + let TestCodex { codex, .. } = test_codex() .with_model(requested_model) .build(&server) .await?; codex - .submit(Op::UserTurn { + .submit(Op::UserInput { items: vec![UserInput::Text { text: "check namespaced model metadata".into(), text_elements: Vec::new(), }], - final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: config.permissions.approval_policy.value(), - approvals_reviewer: None, - sandbox_policy: config.legacy_sandbox_policy(), - permission_profile: None, - model: requested_model.to_string(), - effort: None, - summary: Some( - config - .model_reasoning_summary - .unwrap_or(ReasoningSummary::Auto), - ), - service_tier: None, - collaboration_mode: None, - personality: None, environments: None, + final_output_json_schema: None, + responsesapi_client_metadata: None, + thread_settings: Default::default(), }) .await?; @@ -633,24 +574,22 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path()); codex - .submit(Op::UserTurn { + .submit(Op::UserInput { items: vec![UserInput::Text { text: "run call".into(), text_elements: Vec::new(), }], - final_output_json_schema: None, - cwd: cwd_path, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: REMOTE_MODEL_SLUG.to_string(), - effort: None, - summary: Some(ReasoningSummary::Auto), - service_tier: None, - collaboration_mode: None, - personality: None, environments: None, + final_output_json_schema: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd_path), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + summary: Some(ReasoningSummary::Auto), + ..Default::default() + }, }) .await?; @@ -865,24 +804,22 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path()); codex - .submit(Op::UserTurn { + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello remote".into(), text_elements: Vec::new(), }], - final_output_json_schema: None, - cwd: cwd_path, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: model.to_string(), - effort: None, - summary: Some(ReasoningSummary::Auto), - service_tier: None, - collaboration_mode: None, - personality: None, environments: None, + final_output_json_schema: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd_path), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + summary: Some(ReasoningSummary::Auto), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/request_permissions.rs b/codex-rs/core/tests/suite/request_permissions.rs index cf021f29e4..9800104685 100644 --- a/codex-rs/core/tests/suite/request_permissions.rs +++ b/codex-rs/core/tests/suite/request_permissions.rs @@ -190,24 +190,30 @@ async fn submit_turn( let (sandbox_policy, permission_profile) = turn_permission_fields(permission_profile, test.cwd.path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd.path().to_path_buf(), - approval_policy, - approvals_reviewer: Some(ApprovalsReviewer::User), - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd.path().to_path_buf()), + approval_policy: Some(approval_policy), + approvals_reviewer: Some(ApprovalsReviewer::User), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; Ok(()) diff --git a/codex-rs/core/tests/suite/request_permissions_tool.rs b/codex-rs/core/tests/suite/request_permissions_tool.rs index cb13243966..84e101e73a 100644 --- a/codex-rs/core/tests/suite/request_permissions_tool.rs +++ b/codex-rs/core/tests/suite/request_permissions_tool.rs @@ -142,24 +142,30 @@ async fn submit_turn( let (sandbox_policy, permission_profile) = turn_permission_fields(permission_profile, test.config.cwd.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd.path().to_path_buf(), - approval_policy, - approvals_reviewer, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd.path().to_path_buf()), + approval_policy: Some(approval_policy), + approvals_reviewer, + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; Ok(()) diff --git a/codex-rs/core/tests/suite/request_user_input.rs b/codex-rs/core/tests/suite/request_user_input.rs index 6ddd9d0b5e..e0ec684797 100644 --- a/codex-rs/core/tests/suite/request_user_input.rs +++ b/codex-rs/core/tests/suite/request_user_input.rs @@ -132,36 +132,33 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul ]); let second_mock = responses::mount_sse_once(&server, second_response).await; - let session_model = session_configured.model.clone(); let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, cwd.path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "please confirm".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: Some(CollaborationMode { - mode, - settings: Settings { - model: session_configured.model.clone(), - reasoning_effort: None, - developer_instructions: None, - }, - }), - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(CollaborationMode { + mode, + settings: Settings { + model: session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -282,31 +279,29 @@ async fn request_user_input_interrupt_emits_deferred_token_count() -> anyhow::Re let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, cwd.path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "please confirm".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: Some(CollaborationMode { - mode: ModeKind::Plan, - settings: Settings { - model: session_configured.model, - reasoning_effort: None, - developer_instructions: None, - }, - }), - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(CollaborationMode { + mode: ModeKind::Plan, + settings: Settings { + model: session_configured.model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -388,24 +383,22 @@ where turn_permission_fields(PermissionProfile::Disabled, cwd.path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "please confirm".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: Some(collaboration_mode), - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(collaboration_mode), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/responses_api_proxy_headers.rs b/codex-rs/core/tests/suite/responses_api_proxy_headers.rs index 09e99f78c3..f396a825a1 100644 --- a/codex-rs/core/tests/suite/responses_api_proxy_headers.rs +++ b/codex-rs/core/tests/suite/responses_api_proxy_headers.rs @@ -130,24 +130,29 @@ async fn submit_turn_with_timeout(test: &TestCodex, prompt: &str) -> Result<()> let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::workspace_write(), cwd.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd, - approval_policy: AskForApproval::OnRequest, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd), + approval_policy: Some(AskForApproval::OnRequest), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 7ead0364dc..7c811a88e0 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -105,24 +105,29 @@ fn read_only_user_turn_with_model( let cwd = fixture.cwd.path().to_path_buf(); let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::read_only(), cwd.as_path()); - Op::UserTurn { + Op::UserInput { items: vec![UserInput::Text { text: text.into(), text_elements: Vec::new(), }], - final_output_json_schema: None, - cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, environments: None, + final_output_json_schema: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, } } diff --git a/codex-rs/core/tests/suite/safety_check_downgrade.rs b/codex-rs/core/tests/suite/safety_check_downgrade.rs index c937d49ad4..ab985ce0f7 100644 --- a/codex-rs/core/tests/suite/safety_check_downgrade.rs +++ b/codex-rs/core/tests/suite/safety_check_downgrade.rs @@ -37,24 +37,29 @@ const CYBER_POLICY_MESSAGE: &str = fn disabled_text_turn(test: &TestCodex, text: &str) -> Op { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, test.cwd_path()); - Op::UserTurn { - environments: None, + Op::UserInput { items: vec![UserInput::Text { text: text.to_string(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: REQUESTED_MODEL.to_string(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd_path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: REQUESTED_MODEL.to_string(), + reasoning_effort: test.config.model_reasoning_effort, + developer_instructions: None, + }, + }), + ..Default::default() + }, } } diff --git a/codex-rs/core/tests/suite/shell_snapshot.rs b/codex-rs/core/tests/suite/shell_snapshot.rs index 49ef4956cd..14241ffafb 100644 --- a/codex-rs/core/tests/suite/shell_snapshot.rs +++ b/codex-rs/core/tests/suite/shell_snapshot.rs @@ -159,24 +159,29 @@ async fn run_snapshot_command_with_options( turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "run unified exec with shell snapshot".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -254,24 +259,29 @@ async fn run_shell_command_snapshot_with_options( turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "run shell_command with shell snapshot".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -329,24 +339,29 @@ async fn run_tool_turn_on_harness( let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -567,24 +582,29 @@ async fn shell_command_snapshot_still_intercepts_apply_patch() -> Result<()> { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "apply patch via shell_command with snapshot".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd.clone(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd.clone()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/skill_approval.rs b/codex-rs/core/tests/suite/skill_approval.rs index 72244d534e..ad8ad6a91a 100644 --- a/codex-rs/core/tests/suite/skill_approval.rs +++ b/codex-rs/core/tests/suite/skill_approval.rs @@ -46,24 +46,29 @@ async fn submit_turn_with_policies( let (sandbox_policy, permission_profile) = turn_permission_fields(permission_profile, test.cwd_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.to_string(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: test.session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd_path().to_path_buf()), + approval_policy: Some(approval_policy), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: test.session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; Ok(()) diff --git a/codex-rs/core/tests/suite/skills.rs b/codex-rs/core/tests/suite/skills.rs index 6434f9c533..9173f60c4a 100644 --- a/codex-rs/core/tests/suite/skills.rs +++ b/codex-rs/core/tests/suite/skills.rs @@ -74,8 +74,7 @@ async fn user_turn_includes_skill_instructions() -> Result<()> { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![ UserInput::Text { text: "please use $demo".to_string(), @@ -86,18 +85,24 @@ async fn user_turn_includes_skill_instructions() -> Result<()> { path: skill_path.clone(), }, ], + environments: None, final_output_json_schema: None, - cwd: test.config.cwd.to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.config.cwd.to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index ab0b4c27ea..f576e55a3d 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -405,24 +405,29 @@ async fn mcp_call_marks_thread_memory_mode_polluted_when_configured() -> Result< turn_permission_fields(PermissionProfile::read_only(), cwd.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "call the rmcp echo tool".to_string(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: test.session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: test.session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; wait_for_event(&test.codex, |event| { diff --git a/codex-rs/core/tests/suite/tool_harness.rs b/codex-rs/core/tests/suite/tool_harness.rs index 38843f2d83..2227e5b34e 100644 --- a/codex-rs/core/tests/suite/tool_harness.rs +++ b/codex-rs/core/tests/suite/tool_harness.rs @@ -102,24 +102,29 @@ async fn shell_command_tool_executes_command_and_streams_output() -> anyhow::Res turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "please run the shell command".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd_path, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd_path), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -178,24 +183,29 @@ async fn update_plan_tool_emits_plan_update_event() -> anyhow::Result<()> { turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "please update the plan".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd_path, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd_path), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -264,24 +274,29 @@ async fn update_plan_tool_rejects_malformed_payload() -> anyhow::Result<()> { turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "please update the plan".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd_path, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd_path), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -360,24 +375,29 @@ async fn apply_patch_tool_executes_and_emits_patch_events() -> anyhow::Result<() turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "please apply a patch".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd_path, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd_path), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -493,24 +513,29 @@ async fn apply_patch_reports_parse_diagnostics() -> anyhow::Result<()> { turn_permission_fields(PermissionProfile::Disabled, cwd_path.as_path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "please apply a patch".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd_path, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd_path), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/tool_parallelism.rs b/codex-rs/core/tests/suite/tool_parallelism.rs index 47282c283d..6663991e02 100644 --- a/codex-rs/core/tests/suite/tool_parallelism.rs +++ b/codex-rs/core/tests/suite/tool_parallelism.rs @@ -37,24 +37,29 @@ async fn run_turn(test: &TestCodex, prompt: &str) -> anyhow::Result<()> { turn_permission_fields(PermissionProfile::Disabled, test.cwd.path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -358,24 +363,29 @@ async fn shell_tools_start_before_response_completed_when_stream_delayed() -> an let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, test.cwd.path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "stream delayed completion".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/truncation.rs b/codex-rs/core/tests/suite/truncation.rs index 447bca6731..be9267934d 100644 --- a/codex-rs/core/tests/suite/truncation.rs +++ b/codex-rs/core/tests/suite/truncation.rs @@ -515,24 +515,29 @@ async fn mcp_image_output_preserves_image_and_no_text_summary() -> Result<()> { fixture .codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "call the rmcp image tool".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: fixture.cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile: Some(permission_profile), - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(fixture.cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile: Some(permission_profile), + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index 6b962116af..98755201c9 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -192,24 +192,29 @@ async fn submit_unified_exec_turn( turn_permission_fields(permission_profile, test.config.cwd.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: prompt.into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: test.config.cwd.to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.config.cwd.to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -278,24 +283,29 @@ async fn unified_exec_intercepts_apply_patch_exec_command() -> Result<()> { turn_permission_fields(PermissionProfile::Disabled, &cwd); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "apply patch via unified exec".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -2120,24 +2130,29 @@ async fn unified_exec_keeps_long_running_session_after_turn_end() -> Result<()> turn_permission_fields(PermissionProfile::Disabled, &turn_cwd); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "keep unified exec process after turn end".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: turn_cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(turn_cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -2218,24 +2233,29 @@ async fn unified_exec_interrupt_preserves_long_running_session() -> Result<()> { turn_permission_fields(PermissionProfile::Disabled, &turn_cwd); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "interrupt long-running unified exec".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: turn_cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(turn_cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -2685,25 +2705,29 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> { turn_permission_fields(PermissionProfile::read_only(), &turn_cwd); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "summarize large output".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: turn_cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - // Important! - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(turn_cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -2803,24 +2827,29 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::read_only(), &turn_cwd); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "read the fixture files".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: turn_cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(turn_cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; @@ -2936,24 +2965,29 @@ async fn unified_exec_python_prompt_under_seatbelt() -> Result<()> { turn_permission_fields(PermissionProfile::read_only(), &turn_cwd); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "start python under seatbelt".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: turn_cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(turn_cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/user_shell_cmd.rs b/codex-rs/core/tests/suite/user_shell_cmd.rs index 4fe3586d7f..536ee7b495 100644 --- a/codex-rs/core/tests/suite/user_shell_cmd.rs +++ b/codex-rs/core/tests/suite/user_shell_cmd.rs @@ -174,24 +174,29 @@ async fn user_shell_command_does_not_replace_active_turn() -> anyhow::Result<()> fixture .codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "run model shell command".to_string(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd, - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: fixture.session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: fixture.session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/core/tests/suite/view_image.rs b/codex-rs/core/tests/suite/view_image.rs index d78e8761fd..e631e537c6 100644 --- a/codex-rs/core/tests/suite/view_image.rs +++ b/codex-rs/core/tests/suite/view_image.rs @@ -72,21 +72,26 @@ const VIEW_IMAGE_TURN_COMPLETE_TIMEOUT: Duration = Duration::from_secs(30); fn disabled_user_turn(test: &TestCodex, items: Vec, model: String) -> Op { let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path()); - Op::UserTurn { - environments: None, + Op::UserInput { items, + environments: None, final_output_json_schema: None, - cwd: test.config.cwd.to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(test.config.cwd.to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, } } diff --git a/codex-rs/core/tests/suite/websocket_fallback.rs b/codex-rs/core/tests/suite/websocket_fallback.rs index f073ce7ab8..32a6e30caf 100644 --- a/codex-rs/core/tests/suite/websocket_fallback.rs +++ b/codex-rs/core/tests/suite/websocket_fallback.rs @@ -152,24 +152,29 @@ async fn websocket_fallback_hides_first_websocket_retry_stream_error() -> Result turn_permission_fields(PermissionProfile::Disabled, cwd.path()); codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "hello".into(), text_elements: Vec::new(), }], + environments: None, final_output_json_schema: None, - cwd: cwd.path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + cwd: Some(cwd.path().to_path_buf()), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_configured.model.clone(), + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 77d862086c..cec929b07f 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -518,74 +518,6 @@ pub enum Op { thread_settings: ThreadSettingsOverrides, }, - /// Similar to [`Op::UserInput`], but contains additional context required - /// for a turn of a [`crate::codex_thread::CodexThread`]. - UserTurn { - /// User input items, see `InputItem` - items: Vec, - - /// `cwd` to use with the [`SandboxPolicy`] and potentially tool calls - /// such as `local_shell`. - cwd: PathBuf, - - /// Policy to use for command approval. - approval_policy: AskForApproval, - - /// Reviewer to use for approval requests raised during this turn. - /// - /// When omitted, the session keeps the current setting - approvals_reviewer: Option, - - /// Policy to use for tool calls such as `local_shell`. - sandbox_policy: SandboxPolicy, - - /// Full permissions profile to use for tool calls such as `local_shell`. - /// - /// When omitted, `sandbox_policy` is used as a legacy compatibility - /// projection. - #[serde(default, skip_serializing_if = "Option::is_none")] - permission_profile: Option, - - /// Must be a valid model slug for the configured client session - /// associated with this conversation. - model: String, - - /// Will only be honored if the model is configured to use reasoning. - #[serde(skip_serializing_if = "Option::is_none")] - effort: Option, - - /// Will only be honored if the model is configured to use reasoning. - /// - /// When omitted, the session keeps the current setting (which allows core to - /// fall back to the selected model's default on new sessions). - #[serde(default, skip_serializing_if = "Option::is_none")] - summary: Option, - - /// Optional service tier override for this turn. - /// - /// Use `Some(Some(_))` to set a specific tier for this turn, `Some(None)` to - /// explicitly clear the tier for this turn, or `None` to keep the existing - /// session preference. - #[serde(default, skip_serializing_if = "Option::is_none")] - service_tier: Option>, - - // The JSON schema to use for the final assistant message - final_output_json_schema: Option, - - /// EXPERIMENTAL - set a pre-set collaboration mode. - /// Takes precedence over model, effort, and developer instructions if set. - #[serde(skip_serializing_if = "Option::is_none")] - collaboration_mode: Option, - - /// Optional personality override for this turn. - #[serde(skip_serializing_if = "Option::is_none")] - personality: Option, - - /// Optional turn-scoped environments. - #[serde(default, skip_serializing_if = "Option::is_none")] - environments: Option>, - }, - /// Inter-agent communication that should be recorded as assistant history /// while still using the normal thread submission lifecycle. InterAgentCommunication { @@ -843,7 +775,6 @@ impl Op { Self::RealtimeConversationClose => "realtime_conversation_close", Self::RealtimeConversationListVoices => "realtime_conversation_list_voices", Self::UserInput { .. } => "user_input", - Self::UserTurn { .. } => "user_turn", Self::InterAgentCommunication { .. } => "inter_agent_communication", Self::OverrideTurnContext { .. } => "override_turn_context", Self::ExecApproval { .. } => "exec_approval", From 9d90b4214c70321286b71a5493a7405cbb9c8fbf Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 18 May 2026 20:07:42 -0700 Subject: [PATCH 2/2] Replace OverrideTurnContext with ThreadSettings --- .../thread_processor_tests.rs | 12 + .../src/request_processors/turn_processor.rs | 2 +- codex-rs/core/src/codex_thread.rs | 21 +- codex-rs/core/src/session/handlers.rs | 245 ++++++++------- codex-rs/core/src/session/mod.rs | 9 +- codex-rs/core/src/session/session.rs | 2 + codex-rs/core/src/session/tests.rs | 70 ++--- codex-rs/core/src/session/turn.rs | 1 + codex-rs/core/tests/common/lib.rs | 25 ++ .../tests/suite/collaboration_instructions.rs | 287 ++++++------------ codex-rs/core/tests/suite/compact.rs | 24 +- codex-rs/core/tests/suite/compact_remote.rs | 44 +-- .../core/tests/suite/compact_resume_fork.rs | 23 +- codex-rs/core/tests/suite/model_overrides.rs | 50 ++- codex-rs/core/tests/suite/model_switching.rs | 65 ++-- .../core/tests/suite/model_visible_layout.rs | 22 +- codex-rs/core/tests/suite/override_updates.rs | 74 ++--- .../core/tests/suite/permissions_messages.rs | 90 ++---- codex-rs/core/tests/suite/personality.rs | 88 ++---- codex-rs/core/tests/suite/prompt_caching.rs | 39 +-- codex-rs/core/tests/suite/remote_models.rs | 44 +-- codex-rs/core/tests/suite/resume.rs | 23 +- codex-rs/core/tests/suite/review.rs | 24 +- codex-rs/docs/protocol_v1.md | 2 +- codex-rs/mcp-server/src/codex_tool_runner.rs | 1 + codex-rs/memories/write/src/startup_tests.rs | 22 +- codex-rs/protocol/src/protocol.rs | 110 +++---- codex-rs/rollout-trace/src/protocol_event.rs | 2 + codex-rs/rollout/src/policy.rs | 1 + 29 files changed, 553 insertions(+), 869 deletions(-) diff --git a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs index f9841421ca..aa33fc623f 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs @@ -62,6 +62,9 @@ mod thread_processor_behavior_tests { use codex_model_provider_info::ModelProviderInfo; use codex_model_provider_info::WireApi; use codex_protocol::ThreadId; + use codex_protocol::config_types::CollaborationMode; + use codex_protocol::config_types::ModeKind; + use codex_protocol::config_types::Settings; use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS; use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY; use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE; @@ -662,7 +665,16 @@ mod thread_processor_behavior_tests { profile_workspace_roots: Vec::new(), ephemeral: false, reasoning_effort: None, + reasoning_summary: None, personality: None, + collaboration_mode: CollaborationMode { + mode: ModeKind::Default, + settings: Settings { + model: "gpt-5".to_string(), + reasoning_effort: None, + developer_instructions: None, + }, + }, session_source: SessionSource::Cli, thread_source: None, }; diff --git a/codex-rs/app-server/src/request_processors/turn_processor.rs b/codex-rs/app-server/src/request_processors/turn_processor.rs index d383f64d4e..71715e5079 100644 --- a/codex-rs/app-server/src/request_processors/turn_processor.rs +++ b/codex-rs/app-server/src/request_processors/turn_processor.rs @@ -479,7 +479,7 @@ impl TurnRequestProcessor { // still queued together with the input below to preserve submission order. if has_any_overrides { thread - .validate_thread_settings_overrides(CodexThreadSettingsOverrides { + .preview_thread_settings_overrides(CodexThreadSettingsOverrides { cwd: cwd.clone(), workspace_roots: runtime_workspace_roots.clone(), approval_policy, diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index aff76b8c02..1b40387c3f 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -63,7 +63,9 @@ pub struct ThreadConfigSnapshot { pub profile_workspace_roots: Vec, pub ephemeral: bool, pub reasoning_effort: Option, + pub reasoning_summary: Option, pub personality: Option, + pub collaboration_mode: CollaborationMode, pub session_source: SessionSource, pub thread_source: Option, } @@ -257,11 +259,19 @@ impl CodexThread { .await } - /// Validate persistent thread settings overrides without committing them. - pub async fn validate_thread_settings_overrides( + /// Preview persistent thread settings overrides without committing them. + pub async fn preview_thread_settings_overrides( &self, overrides: CodexThreadSettingsOverrides, - ) -> ConstraintResult<()> { + ) -> ConstraintResult { + let updates = self.thread_settings_update(overrides).await; + self.codex.session.preview_settings(&updates).await + } + + async fn thread_settings_update( + &self, + overrides: CodexThreadSettingsOverrides, + ) -> SessionSettingsUpdate { let CodexThreadSettingsOverrides { cwd, workspace_roots, @@ -289,7 +299,7 @@ impl CodexThread { .with_updates(model, effort, /*developer_instructions*/ None) }; - let updates = SessionSettingsUpdate { + SessionSettingsUpdate { cwd, workspace_roots, profile_workspace_roots, @@ -304,8 +314,7 @@ impl CodexThread { service_tier, personality, ..Default::default() - }; - self.codex.session.validate_settings(&updates).await + } } /// Use sparingly: this is intended to be removed soon. diff --git a/codex-rs/core/src/session/handlers.rs b/codex-rs/core/src/session/handlers.rs index e01ee28f99..3d57bd7074 100644 --- a/codex-rs/core/src/session/handlers.rs +++ b/codex-rs/core/src/session/handlers.rs @@ -42,7 +42,9 @@ use codex_protocol::protocol::ReviewRequest; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::ThreadMemoryMode; use codex_protocol::protocol::ThreadRolledBackEvent; +use codex_protocol::protocol::ThreadSettingsAppliedEvent; use codex_protocol::protocol::ThreadSettingsOverrides; +use codex_protocol::protocol::ThreadSettingsSnapshot; use codex_protocol::protocol::TurnAbortReason; use codex_protocol::protocol::WarningEvent; use codex_protocol::request_permissions::RequestPermissionsResponse; @@ -81,19 +83,6 @@ pub async fn realtime_conversation_list_voices(sess: &Session, sub_id: String) { .await; } -pub async fn override_turn_context(sess: &Session, sub_id: String, updates: SessionSettingsUpdate) { - if let Err(err) = sess.update_settings(updates).await { - sess.send_event_raw(Event { - id: sub_id, - msg: EventMsg::Error(ErrorEvent { - message: err.to_string(), - codex_error_info: Some(CodexErrorInfo::BadRequest), - }), - }) - .await; - } -} - pub async fn user_input_or_turn(sess: &Arc, sub_id: String, op: Op) { user_input_or_turn_inner( sess, @@ -104,36 +93,132 @@ pub async fn user_input_or_turn(sess: &Arc, sub_id: String, op: Op) { .await; } +pub async fn update_thread_settings( + sess: &Arc, + sub_id: String, + thread_settings: ThreadSettingsOverrides, +) { + let updates = thread_settings_update(sess, thread_settings).await; + let msg = match sess.update_settings(updates).await { + Ok(()) => thread_settings_applied_event(sess).await, + Err(err) => EventMsg::Error(ErrorEvent { + message: format!("invalid thread settings override: {err}"), + codex_error_info: Some(CodexErrorInfo::BadRequest), + }), + }; + sess.send_event_raw(Event { id: sub_id, msg }).await; +} + +async fn thread_settings_update( + sess: &Session, + thread_settings: ThreadSettingsOverrides, +) -> SessionSettingsUpdate { + let ThreadSettingsOverrides { + cwd, + workspace_roots, + profile_workspace_roots, + approval_policy, + approvals_reviewer, + sandbox_policy, + permission_profile, + active_permission_profile, + windows_sandbox_level, + model, + effort, + summary, + service_tier, + collaboration_mode, + personality, + } = thread_settings; + let collaboration_mode = match collaboration_mode { + Some(collaboration_mode) => collaboration_mode, + None => { + let state = sess.state.lock().await; + // Model and reasoning effort live in CollaborationMode settings today, so + // partial thread-settings updates refresh those fields on the active mode. + state + .session_configuration + .collaboration_mode + .with_updates(model, effort, /*developer_instructions*/ None) + } + }; + SessionSettingsUpdate { + cwd, + workspace_roots, + profile_workspace_roots, + approval_policy, + approvals_reviewer, + sandbox_policy, + permission_profile, + active_permission_profile, + windows_sandbox_level, + collaboration_mode: Some(collaboration_mode), + reasoning_summary: summary, + service_tier, + personality, + ..Default::default() + } +} + +async fn thread_settings_applied_event(sess: &Session) -> EventMsg { + let snapshot = { + let state = sess.state.lock().await; + state.session_configuration.thread_config_snapshot() + }; + EventMsg::ThreadSettingsApplied(ThreadSettingsAppliedEvent { + thread_settings: ThreadSettingsSnapshot { + model: snapshot.model, + model_provider_id: snapshot.model_provider_id, + service_tier: snapshot.service_tier, + approval_policy: snapshot.approval_policy, + approvals_reviewer: snapshot.approvals_reviewer, + permission_profile: snapshot.permission_profile, + active_permission_profile: snapshot.active_permission_profile, + cwd: snapshot.cwd, + reasoning_effort: snapshot.reasoning_effort, + reasoning_summary: snapshot.reasoning_summary, + personality: snapshot.personality, + collaboration_mode: snapshot.collaboration_mode, + }, + }) +} + pub(super) async fn user_input_or_turn_inner( sess: &Arc, sub_id: String, op: Op, mirror_user_text_to_realtime: Option<()>, ) { - let (items, updates, responsesapi_client_metadata) = match op { - Op::UserInput { - items, - environments, - final_output_json_schema, - responsesapi_client_metadata, - thread_settings, - } => { - let mut updates = if thread_settings == ThreadSettingsOverrides::default() { - SessionSettingsUpdate::default() - } else { - thread_settings_update(sess, thread_settings).await - }; - updates.final_output_json_schema = Some(final_output_json_schema); - updates.environments = environments; - (items, updates, responsesapi_client_metadata) - } - _ => unreachable!(), + let Op::UserInput { + items, + environments, + final_output_json_schema, + responsesapi_client_metadata, + thread_settings, + } = op + else { + unreachable!(); }; + let emit_thread_settings_applied = thread_settings != ThreadSettingsOverrides::default(); + let mut updates = if emit_thread_settings_applied { + thread_settings_update(sess, thread_settings).await + } else { + SessionSettingsUpdate::default() + }; + updates.final_output_json_schema = Some(final_output_json_schema); + updates.environments = environments; let Ok(current_context) = sess.new_turn_with_sub_id(sub_id.clone(), updates).await else { // new_turn_with_sub_id already emits the error event. return; }; + if emit_thread_settings_applied { + sess.send_event_raw(Event { + id: sub_id.clone(), + msg: thread_settings_applied_event(sess).await, + }) + .await; + } sess.maybe_emit_unknown_model_warning_for_turn(current_context.as_ref()) .await; let accepted_items = match sess @@ -183,56 +268,6 @@ pub(super) async fn user_input_or_turn_inner( } } -async fn thread_settings_update( - sess: &Session, - thread_settings: ThreadSettingsOverrides, -) -> SessionSettingsUpdate { - let ThreadSettingsOverrides { - cwd, - workspace_roots, - profile_workspace_roots, - approval_policy, - approvals_reviewer, - sandbox_policy, - permission_profile, - active_permission_profile, - windows_sandbox_level, - model, - effort, - summary, - service_tier, - collaboration_mode, - personality, - } = thread_settings; - let collaboration_mode = if let Some(collaboration_mode) = collaboration_mode { - collaboration_mode - } else { - let state = sess.state.lock().await; - // Model and reasoning effort live in CollaborationMode settings today, so - // partial thread-settings updates refresh those fields on the active mode. - state - .session_configuration - .collaboration_mode - .with_updates(model, effort, /*developer_instructions*/ None) - }; - SessionSettingsUpdate { - cwd, - workspace_roots, - profile_workspace_roots, - approval_policy, - approvals_reviewer, - sandbox_policy, - permission_profile, - active_permission_profile, - windows_sandbox_level, - collaboration_mode: Some(collaboration_mode), - reasoning_summary: summary, - service_tier, - personality, - ..Default::default() - } -} - async fn mirror_user_text_to_realtime(sess: &Arc, items: &[UserInput]) { let text = UserMessageItem::new(items).message(); if text.is_empty() { @@ -729,54 +764,14 @@ pub(super) async fn submission_loop( realtime_conversation_list_voices(&sess, sub.id.clone()).await; false } - Op::OverrideTurnContext { - cwd, - approval_policy, - approvals_reviewer, - sandbox_policy, - permission_profile, - windows_sandbox_level, - model, - effort, - summary, - service_tier, - collaboration_mode, - personality, - } => { - let collaboration_mode = if let Some(collab_mode) = collaboration_mode { - collab_mode - } else { - let state = sess.state.lock().await; - state.session_configuration.collaboration_mode.with_updates( - model.clone(), - effort, - /*developer_instructions*/ None, - ) - }; - override_turn_context( - &sess, - sub.id.clone(), - SessionSettingsUpdate { - cwd, - approval_policy, - approvals_reviewer, - sandbox_policy, - permission_profile, - windows_sandbox_level, - collaboration_mode: Some(collaboration_mode), - reasoning_summary: summary, - service_tier, - personality, - ..Default::default() - }, - ) - .await; - false - } Op::UserInput { .. } => { user_input_or_turn(&sess, sub.id.clone(), sub.op).await; false } + Op::ThreadSettings { thread_settings } => { + update_thread_settings(&sess, sub.id.clone(), thread_settings).await; + false + } Op::InterAgentCommunication { communication } => { inter_agent_communication(&sess, sub.id.clone(), communication).await; false diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 7c3b4803dd..cb848286c9 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -1384,12 +1384,15 @@ impl Session { Ok(()) } - pub(crate) async fn validate_settings( + pub(crate) async fn preview_settings( &self, updates: &SessionSettingsUpdate, - ) -> ConstraintResult<()> { + ) -> ConstraintResult { let state = self.state.lock().await; - state.session_configuration.apply(updates).map(|_| ()) + state + .session_configuration + .apply(updates) + .map(|configuration| configuration.thread_config_snapshot()) } pub(crate) async fn set_session_startup_prewarm( diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index 082308f20d..2d96cf9165 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -178,7 +178,9 @@ impl SessionConfiguration { profile_workspace_roots: self.profile_workspace_roots().to_vec(), ephemeral: self.original_config_do_not_use.ephemeral, reasoning_effort: self.collaboration_mode.reasoning_effort(), + reasoning_summary: self.model_reasoning_summary, personality: self.personality, + collaboration_mode: self.collaboration_mode.clone(), session_source: self.session_source.clone(), thread_source: self.thread_source, } diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 4f090a8a3a..2363f02cc6 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -118,6 +118,7 @@ use codex_protocol::protocol::SkillScope; use codex_protocol::protocol::Submission; use codex_protocol::protocol::ThreadGoalStatus; use codex_protocol::protocol::ThreadRolledBackEvent; +use codex_protocol::protocol::ThreadSettingsOverrides; use codex_protocol::protocol::TokenCountEvent; use codex_protocol::protocol::TokenUsage; use codex_protocol::protocol::TokenUsageInfo; @@ -2257,24 +2258,6 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result< developer_instructions: Some("Fork turn collaboration instructions.".to_string()), }, }; - forked - .thread - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: Some(AskForApproval::Never), - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: Some(collaboration_mode), - personality: None, - }) - .await?; - forked .thread .submit(Op::UserInput { @@ -2285,7 +2268,11 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result< }], final_output_json_schema: None, responsesapi_client_metadata: None, - thread_settings: Default::default(), + thread_settings: ThreadSettingsOverrides { + approval_policy: Some(AskForApproval::Never), + collaboration_mode: Some(collaboration_mode), + ..Default::default() + }, }) .await?; wait_for_event(&forked.thread, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; @@ -2338,7 +2325,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() { let turn_id = previous_context_item .turn_id .clone() - .expect("turn context should have turn_id"); + .expect("thread settings should have turn_id"); let rollout_items = vec![ RolloutItem::EventMsg(EventMsg::TurnStarted( codex_protocol::protocol::TurnStartedEvent { @@ -2521,14 +2508,14 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context let first_turn_id = first_context_item .turn_id .clone() - .expect("turn context should have turn_id"); + .expect("thread settings should have turn_id"); let mut rolled_back_context_item = first_context_item.clone(); rolled_back_context_item.turn_id = Some("rolled-back-turn".to_string()); rolled_back_context_item.model = "rolled-back-model".to_string(); let rolled_back_turn_id = rolled_back_context_item .turn_id .clone() - .expect("turn context should have turn_id"); + .expect("thread settings should have turn_id"); let turn_one_user = user_message("turn 1 user"); let turn_one_assistant = assistant_message("turn 1 assistant"); let turn_two_user = user_message("turn 2 user"); @@ -2637,7 +2624,7 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio let first_turn_id = first_context_item .turn_id .clone() - .expect("turn context should have turn_id"); + .expect("thread settings should have turn_id"); let compact_turn_id = "compact-turn".to_string(); let rolled_back_turn_id = "rolled-back-turn".to_string(); let compacted_history = vec![ @@ -4833,7 +4820,7 @@ async fn request_permissions_emits_event_when_granular_policy_allows_requests() let (session, mut turn_context, rx) = make_session_and_context_with_rx().await; *session.active_turn.lock().await = Some(ActiveTurn::default()); Arc::get_mut(&mut turn_context) - .expect("single turn context ref") + .expect("single thread settings ref") .approval_policy .set(AskForApproval::Granular(GranularApprovalConfig { sandbox_approval: true, @@ -4911,7 +4898,7 @@ async fn request_permissions_response_materializes_session_cwd_grants_before_rec let (session, mut turn_context, rx) = make_session_and_context_with_rx().await; *session.active_turn.lock().await = Some(ActiveTurn::default()); Arc::get_mut(&mut turn_context) - .expect("single turn context ref") + .expect("single thread settings ref") .approval_policy .set(AskForApproval::Granular(GranularApprovalConfig { sandbox_approval: true, @@ -5008,7 +4995,7 @@ async fn request_permissions_is_auto_denied_when_granular_policy_blocks_tool_req let (session, mut turn_context, rx) = make_session_and_context_with_rx().await; *session.active_turn.lock().await = Some(ActiveTurn::default()); Arc::get_mut(&mut turn_context) - .expect("single turn context ref") + .expect("single thread settings ref") .approval_policy .set(AskForApproval::Granular(GranularApprovalConfig { sandbox_approval: true, @@ -5198,24 +5185,6 @@ fn submission_dispatch_span_uses_debug_for_realtime_audio() { #[test] fn op_kind_for_input_and_context_ops() { - assert_eq!( - Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - } - .kind(), - "override_turn_context" - ); assert_eq!( Op::UserInput { environments: None, @@ -5227,6 +5196,13 @@ fn op_kind_for_input_and_context_ops() { .kind(), "user_input" ); + assert_eq!( + Op::ThreadSettings { + thread_settings: ThreadSettingsOverrides::default(), + } + .kind(), + "thread_settings" + ); } #[tokio::test] @@ -6798,7 +6774,7 @@ async fn build_initial_context_adds_multi_agent_v2_subagent_usage_hint_as_develo .session_configuration .session_source = session_source.clone(); Arc::get_mut(&mut turn_context) - .expect("turn context should not be shared") + .expect("thread settings should not be shared") .session_source = session_source; let initial_context = session.build_initial_context(turn_context.as_ref()).await; @@ -7064,7 +7040,7 @@ fn emit_thread_start_skill_metrics_records_description_truncated_chars_without_o #[tokio::test] async fn build_initial_context_emits_thread_start_skill_warning_on_repeated_builds() { let (session, turn_context, rx) = make_session_and_context_with_rx().await; - let mut turn_context = Arc::into_inner(turn_context).expect("sole turn context owner"); + let mut turn_context = Arc::into_inner(turn_context).expect("sole thread settings owner"); let mut outcome = SkillLoadOutcome::default(); outcome.skills = vec![ SkillMetadata { @@ -9890,7 +9866,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() { // The rejection should not poison the non-escalated path for the same // command. Force DangerFullAccess so this check stays focused on approval // policy rather than platform-specific sandbox behavior. - let turn_context_mut = Arc::get_mut(&mut turn_context).expect("unique turn context Arc"); + let turn_context_mut = Arc::get_mut(&mut turn_context).expect("unique thread settings Arc"); turn_context_mut.permission_profile = PermissionProfile::Disabled; let file_system_sandbox_policy = turn_context.file_system_sandbox_policy(); diff --git a/codex-rs/core/src/session/turn.rs b/codex-rs/core/src/session/turn.rs index 8edf2e4785..0063771480 100644 --- a/codex-rs/core/src/session/turn.rs +++ b/codex-rs/core/src/session/turn.rs @@ -1476,6 +1476,7 @@ pub(super) fn realtime_text_for_event(msg: &EventMsg) -> Option { | EventMsg::ContextCompacted(_) | EventMsg::ThreadRolledBack(_) | EventMsg::TurnStarted(_) + | EventMsg::ThreadSettingsApplied(_) | EventMsg::TurnComplete(_) | EventMsg::TokenCount(_) | EventMsg::UserMessage(_) diff --git a/codex-rs/core/tests/common/lib.rs b/codex-rs/core/tests/common/lib.rs index ad7858f804..194fb2ae0a 100644 --- a/codex-rs/core/tests/common/lib.rs +++ b/codex-rs/core/tests/common/lib.rs @@ -248,6 +248,31 @@ where wait_for_event_with_timeout(codex, predicate, Duration::from_secs(1)).await } +pub async fn submit_thread_settings( + codex: &CodexThread, + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides, +) -> anyhow::Result<()> { + use codex_protocol::protocol::EventMsg; + use codex_protocol::protocol::Op; + use tokio::time::Duration; + use tokio::time::timeout; + + let submission_id = codex.submit(Op::ThreadSettings { thread_settings }).await?; + loop { + let ev = timeout(Duration::from_secs(10), codex.next_event()) + .await + .expect("timeout waiting for thread settings update") + .expect("stream ended unexpectedly"); + if ev.id == submission_id { + match ev.msg { + EventMsg::ThreadSettingsApplied(_) => return Ok(()), + EventMsg::Error(err) => panic!("thread settings update failed: {}", err.message), + other => panic!("unexpected thread settings update event: {other:?}"), + } + } + } +} + pub async fn wait_for_event_match(codex: &CodexThread, matcher: F) -> T where F: Fn(&codex_protocol::protocol::EventMsg) -> Option, diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index bd01dc4e7d..bf8de53dd1 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -123,22 +123,14 @@ async fn user_input_includes_collaboration_instructions_after_override() -> Resu let collab_text = "collab instructions"; let collaboration_mode = collab_mode_with_instructions(Some(collab_text)); - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collaboration_mode), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -277,22 +269,14 @@ async fn override_then_next_turn_uses_updated_collaboration_instructions() -> Re let collab_text = "override instructions"; let collaboration_mode = collab_mode_with_instructions(Some(collab_text)); - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collaboration_mode), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -333,22 +317,14 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu let turn_text = "turn override"; let turn_mode = collab_mode_with_instructions(Some(turn_text)); - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(base_mode), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -405,22 +381,14 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()> let first_text = "first instructions"; let second_text = "second instructions"; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collab_mode_with_instructions(Some(first_text))), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -436,22 +404,14 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()> .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collab_mode_with_instructions(Some(second_text))), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -496,22 +456,14 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> { let test = test_codex().build(&server).await?; let collab_text = "same instructions"; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collab_mode_with_instructions(Some(collab_text))), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -527,22 +479,14 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> { .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collab_mode_with_instructions(Some(collab_text))), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -586,25 +530,17 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang let default_text = "default mode instructions"; let plan_text = "plan mode instructions"; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collab_mode_with_mode_and_instructions( ModeKind::Default, Some(default_text), )), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -620,25 +556,17 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collab_mode_with_mode_and_instructions( ModeKind::Plan, Some(plan_text), )), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -683,25 +611,17 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() let test = test_codex().build(&server).await?; let collab_text = "mode-stable instructions"; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collab_mode_with_mode_and_instructions( ModeKind::Default, Some(collab_text), )), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -717,25 +637,17 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collab_mode_with_mode_and_instructions( ModeKind::Default, Some(collab_text), )), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -785,23 +697,14 @@ async fn resume_replays_collaboration_instructions() -> Result<()> { let home = initial.home.clone(); let collab_text = "resume instructions"; - initial - .codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &initial.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collab_mode_with_instructions(Some(collab_text))), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; initial .codex @@ -856,18 +759,9 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> { let test = test_codex().build(&server).await?; let current_model = test.session_configured.model.clone(); - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(CollaborationMode { mode: ModeKind::Default, settings: Settings { @@ -876,9 +770,10 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> { developer_instructions: Some("".to_string()), }, }), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { diff --git a/codex-rs/core/tests/suite/compact.rs b/codex-rs/core/tests/suite/compact.rs index 2c789a271d..b07adfc61c 100644 --- a/codex-rs/core/tests/suite/compact.rs +++ b/codex-rs/core/tests/suite/compact.rs @@ -3279,23 +3279,15 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess .expect("submit user input"); wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; } - codex - .submit(Op::OverrideTurnContext { + core_test_support::submit_thread_settings( + &codex, + codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(PathBuf::from(PRETURN_CONTEXT_DIFF_CWD)), - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await - .expect("override turn context"); + ..Default::default() + }, + ) + .await + .expect("override thread settings"); let image_url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=" .to_string(); codex diff --git a/codex-rs/core/tests/suite/compact_remote.rs b/codex-rs/core/tests/suite/compact_remote.rs index 9db94016e6..9aac846ffe 100644 --- a/codex-rs/core/tests/suite/compact_remote.rs +++ b/codex-rs/core/tests/suite/compact_remote.rs @@ -2773,22 +2773,14 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_us for user in ["USER_ONE", "USER_TWO", "USER_THREE"] { if user == "USER_THREE" { - codex - .submit(Op::OverrideTurnContext { + core_test_support::submit_thread_settings( + &codex, + codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(PathBuf::from(PRETURN_CONTEXT_DIFF_CWD)), - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; } codex .submit(Op::UserInput { @@ -2891,22 +2883,14 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model .await?; wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, + core_test_support::submit_thread_settings( + &codex, + codex_protocol::protocol::ThreadSettingsOverrides { model: Some(next_model.to_string()), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; codex .submit(Op::UserInput { environments: None, diff --git a/codex-rs/core/tests/suite/compact_resume_fork.rs b/codex-rs/core/tests/suite/compact_resume_fork.rs index 20ae213b26..b2b02f5e66 100644 --- a/codex-rs/core/tests/suite/compact_resume_fork.rs +++ b/codex-rs/core/tests/suite/compact_resume_fork.rs @@ -508,7 +508,7 @@ async fn snapshot_rollback_past_compaction_replays_append_only_history() -> Resu } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -/// Scenario: rolling back a turn that introduced persistent thread settings +/// Scenario: rolling back a turn that introduced persistent pre-thread settings /// diffs should trim those context updates so the next request includes them /// only once. async fn snapshot_rollback_followup_turn_trims_context_updates() -> Result<()> { @@ -548,18 +548,10 @@ async fn snapshot_rollback_followup_turn_trims_context_updates() -> Result<()> { let override_cwd = config.cwd.join(PRETURN_CONTEXT_DIFF_CWD); std::fs::create_dir_all(&override_cwd)?; - conversation - .submit(Op::OverrideTurnContext { + core_test_support::submit_thread_settings( + &conversation, + codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(override_cwd.to_path_buf()), - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, collaboration_mode: Some(CollaborationMode { mode: ModeKind::Default, settings: Settings { @@ -568,9 +560,10 @@ async fn snapshot_rollback_followup_turn_trims_context_updates() -> Result<()> { developer_instructions: Some(ROLLED_BACK_DEV_INSTRUCTIONS.to_string()), }, }), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; user_turn(&conversation, TURN_TWO_USER).await; diff --git a/codex-rs/core/tests/suite/model_overrides.rs b/codex-rs/core/tests/suite/model_overrides.rs index 9f8835f19f..f70c0ae1c2 100644 --- a/codex-rs/core/tests/suite/model_overrides.rs +++ b/codex-rs/core/tests/suite/model_overrides.rs @@ -9,7 +9,7 @@ use pretty_assertions::assert_eq; const CONFIG_TOML: &str = "config.toml"; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_turn_context_does_not_persist_when_config_exists() { +async fn thread_settings_update_does_not_persist_when_config_exists() { let server = start_mock_server().await; let initial_contents = "model = \"gpt-4o\"\n"; let mut builder = test_codex() @@ -24,23 +24,16 @@ async fn override_turn_context_does_not_persist_when_config_exists() { let codex = test.codex.clone(); let config_path = test.home.path().join(CONFIG_TOML); - codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, + core_test_support::submit_thread_settings( + &codex, + codex_protocol::protocol::ThreadSettingsOverrides { model: Some("o3".to_string()), effort: Some(Some(ReasoningEffort::High)), - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await - .expect("submit override"); + ..Default::default() + }, + ) + .await + .expect("submit override"); codex.submit(Op::Shutdown).await.expect("request shutdown"); wait_for_event(&codex, |ev| matches!(ev, EventMsg::ShutdownComplete)).await; @@ -52,7 +45,7 @@ async fn override_turn_context_does_not_persist_when_config_exists() { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_turn_context_does_not_create_config_file() { +async fn thread_settings_update_does_not_create_config_file() { let server = start_mock_server().await; let mut builder = test_codex(); let test = builder.build(&server).await.expect("create conversation"); @@ -63,23 +56,16 @@ async fn override_turn_context_does_not_create_config_file() { "test setup should start without config" ); - codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, + core_test_support::submit_thread_settings( + &codex, + codex_protocol::protocol::ThreadSettingsOverrides { model: Some("o3".to_string()), effort: Some(Some(ReasoningEffort::Medium)), - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await - .expect("submit override"); + ..Default::default() + }, + ) + .await + .expect("submit override"); codex.submit(Op::Shutdown).await.expect("request shutdown"); wait_for_event(&codex, |ev| matches!(ev, EventMsg::ShutdownComplete)).await; diff --git a/codex-rs/core/tests/suite/model_switching.rs b/codex-rs/core/tests/suite/model_switching.rs index 1c9358a8a3..009dfda60f 100644 --- a/codex-rs/core/tests/suite/model_switching.rs +++ b/codex-rs/core/tests/suite/model_switching.rs @@ -161,22 +161,14 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<( .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { model: Some(next_model.to_string()), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(read_only_user_turn( @@ -241,22 +233,15 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { model: Some(next_model.to_string()), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, personality: Some(Personality::Pragmatic), - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(read_only_user_turn( @@ -988,22 +973,14 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result< ); wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { model: Some(smaller_model_slug.to_string()), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(read_only_user_turn( diff --git a/codex-rs/core/tests/suite/model_visible_layout.rs b/codex-rs/core/tests/suite/model_visible_layout.rs index 9cf83424c9..17bc0f6703 100644 --- a/codex-rs/core/tests/suite/model_visible_layout.rs +++ b/codex-rs/core/tests/suite/model_visible_layout.rs @@ -501,23 +501,15 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() - let resumed = resume_builder.resume(&server, home, rollout_path).await?; let resume_override_cwd = resumed.cwd_path().join(PRETURN_CONTEXT_DIFF_CWD); fs::create_dir_all(&resume_override_cwd)?; - resumed - .codex - .submit(Op::OverrideTurnContext { + core_test_support::submit_thread_settings( + &resumed.codex, + codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(resume_override_cwd), - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, model: Some("gpt-5.2".to_string()), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; resumed .codex .submit(Op::UserInput { diff --git a/codex-rs/core/tests/suite/override_updates.rs b/codex-rs/core/tests/suite/override_updates.rs index d0e949947a..ce7c87a93b 100644 --- a/codex-rs/core/tests/suite/override_updates.rs +++ b/codex-rs/core/tests/suite/override_updates.rs @@ -24,7 +24,7 @@ fn collab_mode_with_instructions(instructions: Option<&str>) -> CollaborationMod } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_turn_context_without_user_turn_does_not_record_permissions_update() -> Result<()> +async fn thread_settings_update_without_user_turn_does_not_record_permissions_update() -> Result<()> { skip_if_no_network!(Ok(())); @@ -34,22 +34,14 @@ async fn override_turn_context_without_user_turn_does_not_record_permissions_upd }); let test = builder.build(&server).await?; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { approval_policy: Some(AskForApproval::Never), - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex.submit(Op::Shutdown).await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::ShutdownComplete)).await; @@ -64,7 +56,7 @@ async fn override_turn_context_without_user_turn_does_not_record_permissions_upd } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_turn_context_without_user_turn_does_not_record_environment_update() -> Result<()> +async fn thread_settings_update_without_user_turn_does_not_record_environment_update() -> Result<()> { skip_if_no_network!(Ok(())); @@ -72,22 +64,14 @@ async fn override_turn_context_without_user_turn_does_not_record_environment_upd let test = test_codex().build(&server).await?; let new_cwd = TempDir::new()?; - test.codex - .submit(Op::OverrideTurnContext { + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(new_cwd.path().to_path_buf()), - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex.submit(Op::Shutdown).await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::ShutdownComplete)).await; @@ -102,8 +86,8 @@ async fn override_turn_context_without_user_turn_does_not_record_environment_upd } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn override_turn_context_without_user_turn_does_not_record_collaboration_update() -> Result<()> -{ +async fn thread_settings_update_without_user_turn_does_not_record_collaboration_update() +-> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -111,22 +95,14 @@ async fn override_turn_context_without_user_turn_does_not_record_collaboration_u let collab_text = "override collaboration instructions"; let collaboration_mode = collab_mode_with_instructions(Some(collab_text)); - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { collaboration_mode: Some(collaboration_mode), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex.submit(Op::Shutdown).await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::ShutdownComplete)).await; diff --git a/codex-rs/core/tests/suite/permissions_messages.rs b/codex-rs/core/tests/suite/permissions_messages.rs index 33d494ae45..b77e65c0f5 100644 --- a/codex-rs/core/tests/suite/permissions_messages.rs +++ b/codex-rs/core/tests/suite/permissions_messages.rs @@ -103,22 +103,14 @@ async fn permissions_message_added_on_override_change() -> Result<()> { .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { approval_policy: Some(AskForApproval::Never), - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -240,22 +232,14 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> { .await?; wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { approval_policy: Some(AskForApproval::Never), - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(Op::UserInput { @@ -330,23 +314,14 @@ async fn resume_replays_permissions_messages() -> Result<()> { .await?; wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - initial - .codex - .submit(Op::OverrideTurnContext { - cwd: None, + core_test_support::submit_thread_settings( + &initial.codex, + codex_protocol::protocol::ThreadSettingsOverrides { approval_policy: Some(AskForApproval::Never), - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; initial .codex @@ -439,23 +414,14 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { .await?; wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - initial - .codex - .submit(Op::OverrideTurnContext { - cwd: None, + core_test_support::submit_thread_settings( + &initial.codex, + codex_protocol::protocol::ThreadSettingsOverrides { approval_policy: Some(AskForApproval::Never), - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; initial .codex diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 8924ca2a25..bbe8178f8d 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -333,22 +333,14 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()> wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { personality: Some(Personality::Friendly), - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(read_only_text_turn( @@ -417,22 +409,14 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { personality: Some(Personality::Pragmatic), - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(read_only_text_turn( @@ -514,22 +498,14 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()> wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { personality: Some(Personality::Pragmatic), - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(read_only_text_turn( @@ -763,22 +739,14 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { personality: Some(Personality::Friendly), - }) - .await?; + ..Default::default() + }, + ) + .await?; test.codex .submit(read_only_text_turn( diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index accf3fb789..68b3816ae6 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -442,22 +442,18 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an let sandbox_policy = permission_profile .to_legacy_sandbox_policy(config.cwd.as_path()) .expect("workspace profile should have legacy projection"); - codex - .submit(Op::OverrideTurnContext { - cwd: None, + core_test_support::submit_thread_settings( + &codex, + codex_protocol::protocol::ThreadSettingsOverrides { approval_policy: Some(AskForApproval::Never), - approvals_reviewer: None, sandbox_policy: Some(sandbox_policy), permission_profile: Some(permission_profile), - windows_sandbox_level: None, - model: None, effort: Some(Some(ReasoningEffort::High)), summary: Some(ReasoningSummary::Detailed), - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; // Second turn after overrides codex @@ -493,7 +489,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an }); let expected_permissions_msg = body1["input"][0].clone(); let body1_input = body1["input"].as_array().expect("input array"); - // After overriding thread settings, emit one updated permissions message. + // After overriding the thread settings, emit one updated permissions message. let expected_permissions_msg_2 = body2["input"][body1_input.len()].clone(); assert_ne!( expected_permissions_msg_2, expected_permissions_msg, @@ -529,22 +525,17 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul }, }; - codex - .submit(Op::OverrideTurnContext { - cwd: None, + core_test_support::submit_thread_settings( + &codex, + codex_protocol::protocol::ThreadSettingsOverrides { approval_policy: Some(AskForApproval::Never), - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, model: Some("gpt-5.4".to_string()), effort: Some(Some(ReasoningEffort::Low)), - summary: None, - service_tier: None, collaboration_mode: Some(collaboration_mode), - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; codex .submit(Op::UserInput { diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index adc73d8a79..e143cb8e3e 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -534,22 +534,14 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> { .await; assert_eq!(model_info.shell_type, ConfigShellToolType::UnifiedExec); - codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, + core_test_support::submit_thread_settings( + &codex, + codex_protocol::protocol::ThreadSettingsOverrides { model: Some(REMOTE_MODEL_SLUG.to_string()), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; let call_id = "call"; let args = json!({ @@ -783,22 +775,14 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> { let models_manager = thread_manager.get_models_manager(); wait_for_model_available(&models_manager, model).await; - codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, + core_test_support::submit_thread_settings( + &codex, + codex_protocol::protocol::ThreadSettingsOverrides { model: Some(model.to_string()), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; let cwd_path = cwd.path().to_path_buf(); let (sandbox_policy, permission_profile) = diff --git a/codex-rs/core/tests/suite/resume.rs b/codex-rs/core/tests/suite/resume.rs index db64289354..fc7a23215f 100644 --- a/codex-rs/core/tests/suite/resume.rs +++ b/codex-rs/core/tests/suite/resume.rs @@ -423,23 +423,14 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu config.model = Some("gpt-5.3-codex".to_string()); }); let resumed = resume_builder.resume(&server, home, rollout_path).await?; - resumed - .codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, + core_test_support::submit_thread_settings( + &resumed.codex, + codex_protocol::protocol::ThreadSettingsOverrides { model: Some("gpt-5.4".to_string()), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; resumed .codex .submit(Op::UserInput { diff --git a/codex-rs/core/tests/suite/review.rs b/codex-rs/core/tests/suite/review.rs index e128a189b5..05ec967d09 100644 --- a/codex-rs/core/tests/suite/review.rs +++ b/codex-rs/core/tests/suite/review.rs @@ -789,23 +789,15 @@ async fn review_uses_overridden_cwd_for_base_branch_merge_base() { }) .await; - codex - .submit(Op::OverrideTurnContext { + core_test_support::submit_thread_settings( + &codex, + codex_protocol::protocol::ThreadSettingsOverrides { cwd: Some(repo_path.to_path_buf()), - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) - .await - .unwrap(); + ..Default::default() + }, + ) + .await + .unwrap(); codex .submit(Op::Review { diff --git a/codex-rs/docs/protocol_v1.md b/codex-rs/docs/protocol_v1.md index d18aa669ca..464e8187b4 100644 --- a/codex-rs/docs/protocol_v1.md +++ b/codex-rs/docs/protocol_v1.md @@ -70,7 +70,7 @@ For complete documentation of the `Op` and `EventMsg` variants, refer to [protoc - `Op::Interrupt` – Interrupts a running turn - `Op::ExecApproval` – Approve or deny code execution - `Op::UserInputAnswer` – Provide answers for a `request_user_input` tool call - - `Op::UserTurn` and `Op::OverrideTurnContext` accept an optional `personality` override that updates the model’s communication style + - `Op::UserInput` accepts an optional `personality` turn-context override that updates the model’s communication style Valid `personality` values are `friendly`, `pragmatic`, and `none`. When `none` is selected, the personality placeholder is replaced with an empty string. diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 1898bebeeb..167d56da5b 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -333,6 +333,7 @@ async fn run_codex_tool_session_inner( } EventMsg::AgentReasoningRawContent(_) | EventMsg::TurnStarted(_) + | EventMsg::ThreadSettingsApplied(_) | EventMsg::TokenCount(_) | EventMsg::AgentReasoning(_) | EventMsg::AgentReasoningSectionBreak(_) diff --git a/codex-rs/memories/write/src/startup_tests.rs b/codex-rs/memories/write/src/startup_tests.rs index 4fcb1d409b..30240f04ca 100644 --- a/codex-rs/memories/write/src/startup_tests.rs +++ b/codex-rs/memories/write/src/startup_tests.rs @@ -242,22 +242,14 @@ async fn memories_startup_phase1_uses_live_thread_service_tier() -> anyhow::Resu let test = build_test_codex(&server, home).await?; assert_eq!(test.config.service_tier, None); - test.codex - .submit(Op::OverrideTurnContext { - cwd: None, - approval_policy: None, - approvals_reviewer: None, - sandbox_policy: None, - permission_profile: None, - windows_sandbox_level: None, - model: None, - effort: None, - summary: None, + core_test_support::submit_thread_settings( + &test.codex, + codex_protocol::protocol::ThreadSettingsOverrides { service_tier: Some(Some(ServiceTier::Fast.request_value().to_string())), - collaboration_mode: None, - personality: None, - }) - .await?; + ..Default::default() + }, + ) + .await?; let config_snapshot = wait_for_service_tier(&test, Some(ServiceTier::Fast.request_value().to_string())).await?; diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index cec929b07f..d5bf200386 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -396,7 +396,8 @@ pub struct ConversationTextParams { pub text: String, } -/// Persistent thread-settings overrides that can be applied before user input. +/// Persistent thread-settings overrides that can be applied before user input or +/// on their own. #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, JsonSchema)] pub struct ThreadSettingsOverrides { /// Updated `cwd` for sandbox/tool calls. @@ -518,76 +519,22 @@ pub enum Op { thread_settings: ThreadSettingsOverrides, }, + /// Apply persistent thread-settings overrides without starting a turn. + /// + /// This uses the same submission queue as turn starts so app-server can + /// preserve caller order between both kinds of mutation. + ThreadSettings { + /// Persistent thread-settings overrides to apply. + #[serde(flatten)] + thread_settings: ThreadSettingsOverrides, + }, + /// Inter-agent communication that should be recorded as assistant history /// while still using the normal thread submission lifecycle. InterAgentCommunication { communication: InterAgentCommunication, }, - /// Override parts of the persistent thread settings for subsequent turns. - /// - /// All fields are optional; when omitted, the existing value is preserved. - /// This does not enqueue any input – it only updates defaults used for - /// turns that rely on persistent session-level settings (for example, - /// [`Op::UserInput`]). - OverrideTurnContext { - /// Updated `cwd` for sandbox/tool calls. - #[serde(skip_serializing_if = "Option::is_none")] - cwd: Option, - - /// Updated command approval policy. - #[serde(skip_serializing_if = "Option::is_none")] - approval_policy: Option, - - /// Updated approval reviewer for future approval prompts. - #[serde(skip_serializing_if = "Option::is_none")] - approvals_reviewer: Option, - - /// Updated sandbox policy for tool calls. - #[serde(skip_serializing_if = "Option::is_none")] - sandbox_policy: Option, - - /// Updated permissions profile for tool calls. - #[serde(skip_serializing_if = "Option::is_none")] - permission_profile: Option, - - /// Updated Windows sandbox mode for tool execution. - #[serde(skip_serializing_if = "Option::is_none")] - windows_sandbox_level: Option, - - /// Updated model slug. When set, the model info is derived - /// automatically. - #[serde(skip_serializing_if = "Option::is_none")] - model: Option, - - /// Updated reasoning effort (honored only for reasoning-capable models). - /// - /// Use `Some(Some(_))` to set a specific effort, `Some(None)` to clear - /// the effort, or `None` to leave the existing value unchanged. - #[serde(skip_serializing_if = "Option::is_none")] - effort: Option>, - - /// Updated reasoning summary preference (honored only for reasoning-capable models). - #[serde(skip_serializing_if = "Option::is_none")] - summary: Option, - - /// Updated service tier preference for future turns. - /// - /// Use `Some(Some(_))` to set a specific tier, `Some(None)` to clear the - /// preference, or `None` to leave the existing value unchanged. - #[serde(skip_serializing_if = "Option::is_none")] - service_tier: Option>, - - /// EXPERIMENTAL - set a pre-set collaboration mode. - /// Takes precedence over model, effort, and developer instructions if set. - #[serde(skip_serializing_if = "Option::is_none")] - collaboration_mode: Option, - - /// Updated personality preference. - #[serde(skip_serializing_if = "Option::is_none")] - personality: Option, - }, - /// Approve a command execution ExecApproval { /// The id of the submission we are approving @@ -775,8 +722,8 @@ impl Op { Self::RealtimeConversationClose => "realtime_conversation_close", Self::RealtimeConversationListVoices => "realtime_conversation_list_voices", Self::UserInput { .. } => "user_input", + Self::ThreadSettings { .. } => "thread_settings", Self::InterAgentCommunication { .. } => "inter_agent_communication", - Self::OverrideTurnContext { .. } => "override_turn_context", Self::ExecApproval { .. } => "exec_approval", Self::PatchApproval { .. } => "patch_approval", Self::ResolveElicitation { .. } => "resolve_elicitation", @@ -1227,6 +1174,10 @@ pub enum EventMsg { #[serde(rename = "task_started", alias = "turn_started")] TurnStarted(TurnStartedEvent), + /// Persistent thread-settings overrides from the correlated submission have + /// been applied to the session configuration. + ThreadSettingsApplied(ThreadSettingsAppliedEvent), + /// Agent has completed all actions. /// v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop. #[serde(rename = "task_complete", alias = "turn_complete")] @@ -1907,6 +1858,33 @@ pub struct TurnStartedEvent { pub collaboration_mode_kind: ModeKind, } +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] +pub struct ThreadSettingsAppliedEvent { + pub thread_settings: ThreadSettingsSnapshot, +} + +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] +pub struct ThreadSettingsSnapshot { + pub model: String, + pub model_provider_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub service_tier: Option, + pub approval_policy: AskForApproval, + pub approvals_reviewer: ApprovalsReviewer, + pub permission_profile: PermissionProfile, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub active_permission_profile: Option, + pub cwd: AbsolutePathBuf, + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_summary: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub personality: Option, + pub collaboration_mode: CollaborationMode, +} + #[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, TS)] pub struct TokenUsage { #[ts(type = "number")] diff --git a/codex-rs/rollout-trace/src/protocol_event.rs b/codex-rs/rollout-trace/src/protocol_event.rs index 1e49c82be2..a862af116c 100644 --- a/codex-rs/rollout-trace/src/protocol_event.rs +++ b/codex-rs/rollout-trace/src/protocol_event.rs @@ -228,6 +228,7 @@ pub(crate) fn tool_runtime_trace_event(event: &EventMsg) -> Option Option<&'static s | EventMsg::ModelReroute(_) | EventMsg::ModelVerification(_) | EventMsg::ContextCompacted(_) + | EventMsg::ThreadSettingsApplied(_) | EventMsg::TokenCount(_) | EventMsg::AgentMessage(_) | EventMsg::UserMessage(_) diff --git a/codex-rs/rollout/src/policy.rs b/codex-rs/rollout/src/policy.rs index ceb617763f..b6dbba98f8 100644 --- a/codex-rs/rollout/src/policy.rs +++ b/codex-rs/rollout/src/policy.rs @@ -183,6 +183,7 @@ fn event_msg_persistence_mode(ev: &EventMsg) -> Option { | EventMsg::AgentReasoningSectionBreak(_) | EventMsg::RawResponseItem(_) | EventMsg::SessionConfigured(_) + | EventMsg::ThreadSettingsApplied(_) | EventMsg::McpToolCallBegin(_) | EventMsg::ExecCommandBegin(_) | EventMsg::TerminalInteraction(_)