From 8b6f131cea89d95e2b726f989e093d3b3ce42c75 Mon Sep 17 00:00:00 2001 From: starr-openai Date: Mon, 20 Apr 2026 14:21:35 -0700 Subject: [PATCH] codex: document turn environments API Co-authored-by: Codex --- .../app-server-protocol/src/protocol/v2.rs | 98 ++++++++++++++++--- codex-rs/app-server/README.md | 6 ++ 2 files changed, 90 insertions(+), 14 deletions(-) diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 94f5eae308..2ecaa733ed 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -9753,27 +9753,13 @@ mod tests { #[test] fn turn_start_params_preserve_explicit_null_service_tier() { - let cwd = test_absolute_path(); let params: TurnStartParams = serde_json::from_value(json!({ "threadId": "thread_123", "input": [], - "environments": [ - { - "environmentId": "local", - "cwd": cwd - } - ], "serviceTier": null })) .expect("params should deserialize"); assert_eq!(params.service_tier, Some(None)); - assert_eq!( - params.environments, - Some(vec![TurnEnvironmentParams { - environment_id: "local".to_string(), - cwd, - }]) - ); let serialized = serde_json::to_value(¶ms).expect("params should serialize"); assert_eq!( @@ -9803,6 +9789,90 @@ mod tests { assert_eq!(serialized_without_override.get("serviceTier"), None); } + #[test] + fn turn_start_params_round_trip_environments() { + let cwd = test_absolute_path(); + let params: TurnStartParams = serde_json::from_value(json!({ + "threadId": "thread_123", + "input": [], + "environments": [ + { + "environmentId": "local", + "cwd": cwd + } + ], + })) + .expect("params should deserialize"); + + assert_eq!( + params.environments, + Some(vec![TurnEnvironmentParams { + environment_id: "local".to_string(), + cwd: cwd.clone(), + }]) + ); + assert_eq!( + crate::experimental_api::ExperimentalApi::experimental_reason(¶ms), + Some("turn/start.environments") + ); + + let serialized = serde_json::to_value(¶ms).expect("params should serialize"); + assert_eq!( + serialized.get("environments"), + Some(&json!([ + { + "environmentId": "local", + "cwd": cwd + } + ])) + ); + } + + #[test] + fn turn_start_params_preserve_empty_environments() { + let params: TurnStartParams = serde_json::from_value(json!({ + "threadId": "thread_123", + "input": [], + "environments": [], + })) + .expect("params should deserialize"); + + assert_eq!(params.environments, Some(Vec::new())); + assert_eq!( + crate::experimental_api::ExperimentalApi::experimental_reason(¶ms), + Some("turn/start.environments") + ); + + let serialized = serde_json::to_value(¶ms).expect("params should serialize"); + assert_eq!(serialized.get("environments"), Some(&json!([]))); + } + + #[test] + fn turn_start_params_treat_null_or_omitted_environments_as_default() { + let null_environments: TurnStartParams = serde_json::from_value(json!({ + "threadId": "thread_123", + "input": [], + "environments": null, + })) + .expect("params should deserialize"); + let omitted_environments: TurnStartParams = serde_json::from_value(json!({ + "threadId": "thread_123", + "input": [], + })) + .expect("params should deserialize"); + + assert_eq!(null_environments.environments, None); + assert_eq!(omitted_environments.environments, None); + assert_eq!( + crate::experimental_api::ExperimentalApi::experimental_reason(&null_environments), + None + ); + assert_eq!( + crate::experimental_api::ExperimentalApi::experimental_reason(&omitted_environments), + None + ); + } + #[test] fn turn_start_params_reject_relative_environment_cwd() { let err = serde_json::from_value::(json!({ diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 86221ec801..d817015984 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -519,6 +519,8 @@ Turns attach user input (text or images) to a thread and trigger Codex generatio You can optionally specify config overrides on the new turn. If specified, these settings become the default for subsequent turns on the same thread. `outputSchema` applies only to the current turn. +`environments` is experimental and requires `initialize.params.capabilities.experimentalApi = true`. When omitted or `null`, Codex uses the thread's default environment behavior. When set to `[]`, the turn runs without an agent-accessible environment. When set to one or more `{ "environmentId", "cwd" }` entries, Codex resolves each id against the configured environments and uses the first entry as the turn's primary environment and cwd. + `approvalsReviewer` accepts: - `"user"` — default. Review approval requests directly in the client. @@ -530,6 +532,10 @@ You can optionally specify config overrides on the new turn. If specified, these "input": [ { "type": "text", "text": "Run tests" } ], // Below are optional config overrides "cwd": "/Users/me/project", + // Experimental: turn-scoped environment selection. + "environments": [ + { "environmentId": "local", "cwd": "/Users/me/project" } + ], "approvalPolicy": "unlessTrusted", "sandboxPolicy": { "type": "workspaceWrite",