Add turn-scoped environment selections (#18416)

## Summary
- add experimental turn/start.environments params for per-turn
environment id + cwd selections
- pass selections through core protocol ops and resolve them with
EnvironmentManager before TurnContext creation
- treat omitted selections as default behavior, empty selections as no
environment, and non-empty selections as first environment/cwd as the
turn primary

## Testing
- ran `just fmt`
- ran `just write-app-server-schema`
- not run: unit tests for this stacked PR

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
starr-openai
2026-04-21 17:48:33 -07:00
committed by GitHub
parent 6368f506b7
commit 1d4cc494c9
85 changed files with 974 additions and 35 deletions

View File

@@ -383,6 +383,7 @@ async fn resume_includes_initial_messages_and_sends_prior_items() {
// 2) Submit new input; the request body must include the prior items, then initial context, then new user input.
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -747,6 +748,7 @@ async fn includes_conversation_id_and_model_headers_in_request() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -947,6 +949,7 @@ async fn includes_base_instructions_override_in_request() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1001,6 +1004,7 @@ async fn chatgpt_auth_sends_correct_request() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1113,6 +1117,7 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1150,6 +1155,7 @@ async fn includes_user_instructions_message_in_request() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1236,6 +1242,7 @@ async fn includes_apps_guidance_as_developer_message_for_chatgpt_auth() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1297,6 +1304,7 @@ async fn omits_apps_guidance_for_api_key_auth_even_when_feature_enabled() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1354,6 +1362,7 @@ async fn omits_apps_guidance_when_configured_off() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1394,6 +1403,7 @@ async fn omits_environment_context_when_configured_off() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1449,6 +1459,7 @@ async fn skills_append_to_developer_message() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1501,6 +1512,7 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1541,6 +1553,7 @@ async fn includes_no_effort_in_request() -> anyhow::Result<()> {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1582,6 +1595,7 @@ async fn includes_default_reasoning_effort_in_request_when_defined_by_model_info
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1636,6 +1650,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re
codex
.submit(Op::UserTurn {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1692,6 +1707,7 @@ async fn configured_reasoning_summary_is_sent() -> anyhow::Result<()> {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1755,6 +1771,7 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default()
codex
.submit(Op::UserTurn {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1808,6 +1825,7 @@ async fn reasoning_summary_is_omitted_when_disabled() -> anyhow::Result<()> {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1865,6 +1883,7 @@ async fn reasoning_summary_none_overrides_model_catalog_default() -> anyhow::Res
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1902,6 +1921,7 @@ async fn includes_default_verbosity_in_request() -> anyhow::Result<()> {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1948,6 +1968,7 @@ async fn configured_verbosity_not_sent_for_models_without_support() -> anyhow::R
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -1993,6 +2014,7 @@ async fn configured_verbosity_is_sent() -> anyhow::Result<()> {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -2043,6 +2065,7 @@ async fn includes_developer_instructions_message_in_request() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -2335,6 +2358,7 @@ async fn token_count_includes_rate_limits_snapshot() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -2504,6 +2528,7 @@ async fn usage_limit_error_emits_rate_limit_event() -> anyhow::Result<()> {
let submission_id = codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -2579,6 +2604,7 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "seed turn".into(),
text_elements: Vec::new(),
@@ -2592,6 +2618,7 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "trigger context window".into(),
text_elements: Vec::new(),
@@ -2675,6 +2702,7 @@ async fn incomplete_response_emits_content_filter_error_message() -> anyhow::Res
.await?;
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "trigger incomplete".into(),
text_elements: Vec::new(),
@@ -2784,6 +2812,7 @@ async fn azure_overrides_assign_properties_used_for_responses_url() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -2871,6 +2900,7 @@ async fn env_var_overrides_loaded_auth() {
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello".into(),
text_elements: Vec::new(),
@@ -2933,6 +2963,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
// Turn 1: user sends U1; wait for completion.
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "U1".into(),
text_elements: Vec::new(),
@@ -2947,6 +2978,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
// Turn 2: user sends U2; wait for completion.
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "U2".into(),
text_elements: Vec::new(),
@@ -2961,6 +2993,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
// Turn 3: user sends U3; wait for completion.
codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "U3".into(),
text_elements: Vec::new(),