From 2a612613f203357ce78e7a16b4d4920252236343 Mon Sep 17 00:00:00 2001 From: Roy Han Date: Mon, 16 Mar 2026 14:33:47 -0700 Subject: [PATCH] sandbox --- .../schema/json/ClientRequest.json | 28 ++++++++++++++++ .../codex_app_server_protocol.schemas.json | 28 ++++++++++++++++ .../codex_app_server_protocol.v2.schemas.json | 28 ++++++++++++++++ .../RawResponseItemCompletedNotification.json | 28 ++++++++++++++++ .../schema/json/v2/ThreadResumeParams.json | 28 ++++++++++++++++ .../typescript/ResponseItemMessageMetadata.ts | 3 +- .../schema/typescript/ResponseItemMetadata.ts | 8 +++++ .../typescript/SandboxPolicyMetadata.ts | 5 +++ .../schema/typescript/index.ts | 1 + codex-rs/core/src/codex.rs | 32 ++++++++++++++++++- codex-rs/protocol/src/item_metadata.rs | 18 +++++++++++ codex-rs/protocol/src/models.rs | 16 ++++++++++ 12 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/typescript/ResponseItemMetadata.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/SandboxPolicyMetadata.ts diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 52c740db08..1572143f07 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1996,6 +1996,16 @@ "description": "Client-visible metadata ID generated by Codex for this item.", "type": "string" }, + "sandbox_policy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicyMetadata" + }, + { + "type": "null" + } + ] + }, "user_message_type": { "anyOf": [ { @@ -2030,6 +2040,16 @@ } ] }, + "sandbox_policy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicyMetadata" + }, + { + "type": "null" + } + ] + }, "user_message_type": { "anyOf": [ { @@ -2408,6 +2428,14 @@ } ] }, + "SandboxPolicyMetadata": { + "enum": [ + "read_only", + "sandbox", + "full_access" + ], + "type": "string" + }, "ServiceTier": { "enum": [ "fast", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index e39e719c02..3fe047a577 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -10719,6 +10719,16 @@ "description": "Client-visible metadata ID generated by Codex for this item.", "type": "string" }, + "sandbox_policy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicyMetadata" + }, + { + "type": "null" + } + ] + }, "user_message_type": { "anyOf": [ { @@ -10753,6 +10763,16 @@ } ] }, + "sandbox_policy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicyMetadata" + }, + { + "type": "null" + } + ] + }, "user_message_type": { "anyOf": [ { @@ -11151,6 +11171,14 @@ } ] }, + "SandboxPolicyMetadata": { + "enum": [ + "read_only", + "sandbox", + "full_access" + ], + "type": "string" + }, "SandboxWorkspaceWrite": { "properties": { "exclude_slash_tmp": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 09b5e6b67c..654b1e6049 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -7487,6 +7487,16 @@ "description": "Client-visible metadata ID generated by Codex for this item.", "type": "string" }, + "sandbox_policy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicyMetadata" + }, + { + "type": "null" + } + ] + }, "user_message_type": { "anyOf": [ { @@ -7521,6 +7531,16 @@ } ] }, + "sandbox_policy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicyMetadata" + }, + { + "type": "null" + } + ] + }, "user_message_type": { "anyOf": [ { @@ -7919,6 +7939,14 @@ } ] }, + "SandboxPolicyMetadata": { + "enum": [ + "read_only", + "sandbox", + "full_access" + ], + "type": "string" + }, "SandboxWorkspaceWrite": { "properties": { "exclude_slash_tmp": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json index 2033980db3..21abd9b0c6 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json @@ -844,6 +844,16 @@ "description": "Client-visible metadata ID generated by Codex for this item.", "type": "string" }, + "sandbox_policy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicyMetadata" + }, + { + "type": "null" + } + ] + }, "user_message_type": { "anyOf": [ { @@ -878,6 +888,16 @@ } ] }, + "sandbox_policy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicyMetadata" + }, + { + "type": "null" + } + ] + }, "user_message_type": { "anyOf": [ { @@ -1004,6 +1024,14 @@ ], "type": "string" }, + "SandboxPolicyMetadata": { + "enum": [ + "read_only", + "sandbox", + "full_access" + ], + "type": "string" + }, "UserMessageType": { "enum": [ "prompt", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json index 9073ac0960..9ea4cf95a7 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json @@ -910,6 +910,16 @@ "description": "Client-visible metadata ID generated by Codex for this item.", "type": "string" }, + "sandbox_policy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicyMetadata" + }, + { + "type": "null" + } + ] + }, "user_message_type": { "anyOf": [ { @@ -944,6 +954,16 @@ } ] }, + "sandbox_policy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicyMetadata" + }, + { + "type": "null" + } + ] + }, "user_message_type": { "anyOf": [ { @@ -1078,6 +1098,14 @@ ], "type": "string" }, + "SandboxPolicyMetadata": { + "enum": [ + "read_only", + "sandbox", + "full_access" + ], + "type": "string" + }, "ServiceTier": { "enum": [ "fast", diff --git a/codex-rs/app-server-protocol/schema/typescript/ResponseItemMessageMetadata.ts b/codex-rs/app-server-protocol/schema/typescript/ResponseItemMessageMetadata.ts index a4e0c36c96..eb06ec079b 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ResponseItemMessageMetadata.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ResponseItemMessageMetadata.ts @@ -1,9 +1,10 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SandboxPolicyMetadata } from "./SandboxPolicyMetadata"; import type { UserMessageType } from "./UserMessageType"; -export type ResponseItemMessageMetadata = { user_message_type?: UserMessageType, +export type ResponseItemMessageMetadata = { user_message_type?: UserMessageType, sandbox_policy?: SandboxPolicyMetadata, /** * Client-visible metadata ID generated by Codex for this item. */ diff --git a/codex-rs/app-server-protocol/schema/typescript/ResponseItemMetadata.ts b/codex-rs/app-server-protocol/schema/typescript/ResponseItemMetadata.ts new file mode 100644 index 0000000000..4f3ffa0405 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/ResponseItemMetadata.ts @@ -0,0 +1,8 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ReviewDecisionMetadata } from "./ReviewDecisionMetadata"; +import type { SandboxPolicyMetadata } from "./SandboxPolicyMetadata"; +import type { UserMessageType } from "./UserMessageType"; + +export type ResponseItemMetadata = { user_message_type?: UserMessageType, sandbox_policy?: SandboxPolicyMetadata, is_tool_call_escalated?: boolean, review_decision?: ReviewDecisionMetadata, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/SandboxPolicyMetadata.ts b/codex-rs/app-server-protocol/schema/typescript/SandboxPolicyMetadata.ts new file mode 100644 index 0000000000..f11e2b4931 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/SandboxPolicyMetadata.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SandboxPolicyMetadata = "read_only" | "sandbox" | "full_access"; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index 5237198966..a963406a5c 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -63,6 +63,7 @@ export type { ResponseItemMessageMetadata } from "./ResponseItemMessageMetadata" export type { ResponseItemMetadata } from "./ResponseItemMetadata"; export type { ReviewDecision } from "./ReviewDecision"; export type { ReviewDecisionMetadata } from "./ReviewDecisionMetadata"; +export type { SandboxPolicyMetadata } from "./SandboxPolicyMetadata"; export type { ServerNotification } from "./ServerNotification"; export type { ServerRequest } from "./ServerRequest"; export type { ServiceTier } from "./ServiceTier"; diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 44759eb7d0..14e40526bf 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -83,6 +83,7 @@ use codex_protocol::config_types::Settings; use codex_protocol::config_types::WebSearchMode; use codex_protocol::dynamic_tools::DynamicToolResponse; use codex_protocol::dynamic_tools::DynamicToolSpec; +use codex_protocol::item_metadata::stamp_sandbox_policy_on_input_item; use codex_protocol::item_metadata::stamp_user_message_type_on_input_item; use codex_protocol::items::PlanItem; use codex_protocol::items::TurnItem; @@ -335,6 +336,7 @@ use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; use codex_protocol::models::ResponseItemMetadata; use codex_protocol::models::ReviewDecisionMetadata; +use codex_protocol::models::SandboxPolicyMetadata; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::InitialHistory; @@ -1027,6 +1029,16 @@ fn review_decision_to_metadata(decision: &ReviewDecision) -> ReviewDecisionMetad } } +fn sandbox_policy_to_metadata(policy: &SandboxPolicy) -> SandboxPolicyMetadata { + match policy { + SandboxPolicy::ReadOnly { .. } => SandboxPolicyMetadata::ReadOnly, + SandboxPolicy::WorkspaceWrite { .. } | SandboxPolicy::ExternalSandbox { .. } => { + SandboxPolicyMetadata::Sandbox + } + SandboxPolicy::DangerFullAccess => SandboxPolicyMetadata::FullAccess, + } +} + fn response_item_tool_call_id(item: &ResponseItem) -> Option<&str> { match item { ResponseItem::LocalShellCall { @@ -3374,6 +3386,9 @@ impl Session { | ResponseItem::CustomToolCall { metadata, .. } => metadata.clone().unwrap_or_default(), _ => return response_item, }; + metadata.sandbox_policy = Some(sandbox_policy_to_metadata( + turn_context.sandbox_policy.get(), + )); match outcome { Some(review_decision) => { @@ -4050,7 +4065,7 @@ impl Session { return Err(SteerInputError::NoActiveTurn(input)); }; - let Some((active_turn_id, _)) = active_turn.tasks.first() else { + let Some((active_turn_id, active_turn_context)) = active_turn.tasks.first() else { return Err(SteerInputError::NoActiveTurn(input)); }; @@ -4066,6 +4081,10 @@ impl Session { let mut input_item: ResponseInputItem = input.into(); if self.enabled(Feature::ItemMetadata) { stamp_user_message_type_on_input_item(&mut input_item, UserMessageType::PromptSteering); + stamp_sandbox_policy_on_input_item( + &mut input_item, + sandbox_policy_to_metadata(active_turn_context.sandbox_policy.get()), + ); } let mut turn_state = active_turn.turn_state.lock().await; @@ -4081,6 +4100,10 @@ impl Session { let mut active = self.active_turn.lock().await; match active.as_mut() { Some(at) => { + let sandbox_policy = at + .tasks + .first() + .map(|(_, task)| sandbox_policy_to_metadata(task.sandbox_policy.get())); let mut ts = at.turn_state.lock().await; for mut item in input { if self.enabled(Feature::ItemMetadata) @@ -4090,6 +4113,9 @@ impl Session { &mut item, UserMessageType::PromptQueued, ); + if let Some(sandbox_policy) = sandbox_policy.clone() { + stamp_sandbox_policy_on_input_item(&mut item, sandbox_policy); + } } ts.push_pending_input(item); } @@ -5790,6 +5816,10 @@ pub(crate) async fn run_turn( let mut initial_input_for_turn: ResponseInputItem = ResponseInputItem::from(input.clone()); if sess.enabled(Feature::ItemMetadata) { stamp_user_message_type_on_input_item(&mut initial_input_for_turn, UserMessageType::Prompt); + stamp_sandbox_policy_on_input_item( + &mut initial_input_for_turn, + sandbox_policy_to_metadata(turn_context.sandbox_policy.get()), + ); } let response_item: ResponseItem = initial_input_for_turn.clone().into(); sess.record_user_prompt_and_emit_turn_item(turn_context.as_ref(), &input, response_item) diff --git a/codex-rs/protocol/src/item_metadata.rs b/codex-rs/protocol/src/item_metadata.rs index 0860187fcc..f502307cf0 100644 --- a/codex-rs/protocol/src/item_metadata.rs +++ b/codex-rs/protocol/src/item_metadata.rs @@ -1,5 +1,6 @@ use crate::models::ResponseInputItem; use crate::models::ResponseItemMessageMetadata; +use crate::models::SandboxPolicyMetadata; use crate::models::UserMessageType; pub fn stamp_user_message_type_on_input_item(item: &mut ResponseInputItem, kind: UserMessageType) { @@ -15,3 +16,20 @@ pub fn stamp_user_message_type_on_input_item(item: &mut ResponseInputItem, kind: metadata_value.user_message_type = Some(kind); *metadata = Some(metadata_value); } + +pub fn stamp_sandbox_policy_on_input_item( + item: &mut ResponseInputItem, + sandbox_policy: SandboxPolicyMetadata, +) { + let ResponseInputItem::Message { role, metadata, .. } = item else { + return; + }; + if role != "user" { + return; + } + let mut metadata_value = metadata + .take() + .unwrap_or_else(|| ResponseItemMessageMetadata::new(/*user_message_type*/ None)); + metadata_value.sandbox_policy = Some(sandbox_policy); + *metadata = Some(metadata_value); +} diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 031a2517f5..f8251be209 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -274,6 +274,9 @@ pub struct ResponseItemMessageMetadata { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub user_message_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub sandbox_policy: Option, /// Client-visible metadata ID generated by Codex for this item. pub metadata_id: String, } @@ -282,6 +285,7 @@ impl ResponseItemMessageMetadata { pub fn new(user_message_type: Option) -> Self { Self { user_message_type, + sandbox_policy: None, metadata_id: uuid::Uuid::new_v4().to_string(), } } @@ -300,6 +304,15 @@ pub enum ReviewDecisionMetadata { DeniedWithNetworkPolicyDeny, } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +#[ts(rename_all = "snake_case")] +pub enum SandboxPolicyMetadata { + ReadOnly, + Sandbox, + FullAccess, +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, TS)] pub struct ResponseItemMetadata { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -307,6 +320,9 @@ pub struct ResponseItemMetadata { pub user_message_type: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] + pub sandbox_policy: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] pub is_tool_call_escalated: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)]