diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 3c208054aa..784236e99b 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -3612,6 +3612,7 @@ dependencies = [ "codex-otel", "codex-prompts", "codex-protocol", + "codex-utils-path-uri", "opentelemetry", "opentelemetry_sdk", "pretty_assertions", diff --git a/codex-rs/core/src/guardian/approval_request.rs b/codex-rs/core/src/guardian/approval_request.rs index 9145b573a2..1fb4736b4d 100644 --- a/codex-rs/core/src/guardian/approval_request.rs +++ b/codex-rs/core/src/guardian/approval_request.rs @@ -1,132 +1,16 @@ use codex_analytics::GuardianReviewedAction; +pub(crate) use codex_guardian_reviewer::GuardianApprovalRequest; +pub(crate) use codex_guardian_reviewer::GuardianMcpAnnotations; +pub(crate) use codex_guardian_reviewer::GuardianNetworkAccessTrigger; use codex_protocol::approvals::GuardianAssessmentAction; use codex_protocol::approvals::GuardianCommandSource; use codex_protocol::approvals::NetworkApprovalProtocol; use codex_protocol::models::AdditionalPermissionProfile; use codex_protocol::request_permissions::RequestPermissionProfile; use codex_utils_path_uri::LegacyAppPathString; -use codex_utils_path_uri::PathUri; use serde::Serialize; use serde_json::Value; -#[derive(Clone, PartialEq)] -pub(crate) enum GuardianApprovalRequest { - NativeToolCall { - id: String, - call: Box, - }, - ExecCommand { - id: String, - environment_id: String, - command: Vec, - cwd: PathUri, - /// Executor-native rendering sent to Guardian; `cwd` remains typed for attribution. - guardian_cwd: LegacyAppPathString, - sandbox_permissions: crate::sandboxing::SandboxPermissions, - additional_permissions: Option, - justification: Option, - tty: bool, - }, - WriteStdin { - id: String, - approval_id: String, - environment_id: String, - process_id: i32, - input: String, - cwd: PathUri, - tty: bool, - sandbox_permissions: crate::sandboxing::SandboxPermissions, - additional_permissions: Option, - }, - ApplyPatch { - id: String, - environment_id: String, - cwd: PathUri, - files: Vec, - patch: String, - }, - NetworkAccess { - id: String, - turn_id: String, - environment_id: String, - target: String, - host: String, - protocol: NetworkApprovalProtocol, - port: u16, - trigger: Option, - }, - McpToolCall { - id: String, - server: String, - tool_name: String, - arguments: Option, - connector_id: Option, - connector_name: Option, - connector_description: Option, - connected_account_email: Option, - tool_title: Option, - tool_description: Option, - annotations: Option, - }, - RequestPermissions { - id: String, - environment_id: String, - turn_id: String, - reason: Option, - permissions: RequestPermissionProfile, - }, -} - -// Approval arguments may contain credentials or encrypted tool messages. -impl std::fmt::Debug for GuardianApprovalRequest { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter - .debug_struct("GuardianApprovalRequest") - .field("kind", &std::mem::discriminant(self)) - .finish_non_exhaustive() - } -} - -impl GuardianApprovalRequest { - /// The environment owning this action, independent of the reviewer's default environment. - pub(super) fn target_environment_id(&self) -> Option<&str> { - match self { - Self::ExecCommand { environment_id, .. } - | Self::WriteStdin { environment_id, .. } - | Self::ApplyPatch { environment_id, .. } - | Self::RequestPermissions { environment_id, .. } - | Self::NetworkAccess { environment_id, .. } => Some(environment_id), - Self::McpToolCall { .. } | Self::NativeToolCall { .. } => None, - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct GuardianNetworkAccessTrigger { - pub(crate) call_id: String, - pub(crate) tool_name: String, - pub(crate) command: Vec, - pub(crate) cwd: PathUri, - pub(crate) sandbox_permissions: crate::sandboxing::SandboxPermissions, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) additional_permissions: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) justification: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) tty: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -pub(crate) struct GuardianMcpAnnotations { - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) destructive_hint: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) open_world_hint: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) read_only_hint: Option, -} - #[derive(Serialize)] struct CommandApprovalAction<'a> { tool: &'a str, diff --git a/codex-rs/core/src/guardian/coverage.rs b/codex-rs/core/src/guardian/coverage.rs index 601111889a..b63b83eaae 100644 --- a/codex-rs/core/src/guardian/coverage.rs +++ b/codex-rs/core/src/guardian/coverage.rs @@ -1,22 +1,8 @@ -//! Supplies action categories to the review extension and enforces host-owned requirements. +//! Supplies Guardian categories for host-owned approval actions. -use super::GuardianApprovalRequest; use crate::tools::sandboxing::ApprovalAction; use codex_protocol::openai_models::GuardianScope; -impl GuardianApprovalRequest { - pub(crate) fn guardian_scope(&self) -> GuardianScope { - match self { - Self::ExecCommand { .. } | Self::WriteStdin { .. } => GuardianScope::Shell, - Self::ApplyPatch { .. } => GuardianScope::FileChanges, - Self::McpToolCall { server, .. } => GuardianScope::for_mcp_server(server), - Self::NetworkAccess { .. } => GuardianScope::Network, - Self::RequestPermissions { .. } => GuardianScope::Permissions, - Self::NativeToolCall { .. } => GuardianScope::NativeTool, - } - } -} - impl ApprovalAction { pub(crate) fn guardian_scope(&self) -> GuardianScope { match self { diff --git a/codex-rs/ext/guardian-reviewer/Cargo.toml b/codex-rs/ext/guardian-reviewer/Cargo.toml index 6c7b9cd55b..59ce596479 100644 --- a/codex-rs/ext/guardian-reviewer/Cargo.toml +++ b/codex-rs/ext/guardian-reviewer/Cargo.toml @@ -22,6 +22,7 @@ codex-mcp = { workspace = true } codex-otel = { workspace = true } codex-prompts = { workspace = true } codex-protocol = { workspace = true } +codex-utils-path-uri = { workspace = true } rand = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/codex-rs/ext/guardian-reviewer/src/approval_request.rs b/codex-rs/ext/guardian-reviewer/src/approval_request.rs new file mode 100644 index 0000000000..29ab475b55 --- /dev/null +++ b/codex-rs/ext/guardian-reviewer/src/approval_request.rs @@ -0,0 +1,143 @@ +//! Owns Guardian approval-request data and its existing environment and scope classification. +//! Request debug output excludes action arguments, which may contain credentials. + +use codex_protocol::approvals::NetworkApprovalProtocol; +use codex_protocol::models::AdditionalPermissionProfile; +use codex_protocol::models::SandboxPermissions; +use codex_protocol::openai_models::GuardianScope; +use codex_protocol::request_permissions::RequestPermissionProfile; +use codex_utils_path_uri::LegacyAppPathString; +use codex_utils_path_uri::PathUri; +use serde::Serialize; +use serde_json::Value; + +#[derive(Clone, PartialEq)] +pub enum GuardianApprovalRequest { + NativeToolCall { + id: String, + call: Box, + }, + ExecCommand { + id: String, + environment_id: String, + command: Vec, + cwd: PathUri, + /// Executor-native rendering sent to Guardian; `cwd` remains typed for attribution. + guardian_cwd: LegacyAppPathString, + sandbox_permissions: SandboxPermissions, + additional_permissions: Option, + justification: Option, + tty: bool, + }, + WriteStdin { + id: String, + approval_id: String, + environment_id: String, + process_id: i32, + input: String, + cwd: PathUri, + tty: bool, + sandbox_permissions: SandboxPermissions, + additional_permissions: Option, + }, + ApplyPatch { + id: String, + environment_id: String, + cwd: PathUri, + files: Vec, + patch: String, + }, + NetworkAccess { + id: String, + turn_id: String, + environment_id: String, + target: String, + host: String, + protocol: NetworkApprovalProtocol, + port: u16, + trigger: Option, + }, + McpToolCall { + id: String, + server: String, + tool_name: String, + arguments: Option, + connector_id: Option, + connector_name: Option, + connector_description: Option, + connected_account_email: Option, + tool_title: Option, + tool_description: Option, + annotations: Option, + }, + RequestPermissions { + id: String, + environment_id: String, + turn_id: String, + reason: Option, + permissions: RequestPermissionProfile, + }, +} + +// Approval arguments may contain credentials or encrypted tool messages. +impl std::fmt::Debug for GuardianApprovalRequest { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("GuardianApprovalRequest") + .field("kind", &std::mem::discriminant(self)) + .finish_non_exhaustive() + } +} + +impl GuardianApprovalRequest { + /// The environment owning this action, independent of the reviewer's default environment. + pub fn target_environment_id(&self) -> Option<&str> { + match self { + Self::ExecCommand { environment_id, .. } + | Self::WriteStdin { environment_id, .. } + | Self::ApplyPatch { environment_id, .. } + | Self::RequestPermissions { environment_id, .. } + | Self::NetworkAccess { environment_id, .. } => Some(environment_id), + Self::McpToolCall { .. } | Self::NativeToolCall { .. } => None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GuardianNetworkAccessTrigger { + pub call_id: String, + pub tool_name: String, + pub command: Vec, + pub cwd: PathUri, + pub sandbox_permissions: SandboxPermissions, + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_permissions: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub justification: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tty: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct GuardianMcpAnnotations { + #[serde(skip_serializing_if = "Option::is_none")] + pub destructive_hint: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub open_world_hint: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub read_only_hint: Option, +} + +impl GuardianApprovalRequest { + pub fn guardian_scope(&self) -> GuardianScope { + match self { + Self::ExecCommand { .. } | Self::WriteStdin { .. } => GuardianScope::Shell, + Self::ApplyPatch { .. } => GuardianScope::FileChanges, + Self::McpToolCall { server, .. } => GuardianScope::for_mcp_server(server), + Self::NetworkAccess { .. } => GuardianScope::Network, + Self::RequestPermissions { .. } => GuardianScope::Permissions, + Self::NativeToolCall { .. } => GuardianScope::NativeTool, + } + } +} diff --git a/codex-rs/ext/guardian-reviewer/src/lib.rs b/codex-rs/ext/guardian-reviewer/src/lib.rs index 10bd27c886..6a69dbcb16 100644 --- a/codex-rs/ext/guardian-reviewer/src/lib.rs +++ b/codex-rs/ext/guardian-reviewer/src/lib.rs @@ -2,6 +2,7 @@ //! of the host session runtime. //! The host supplies review attempts and enforces the resulting decision on the bound action. +mod approval_request; mod assessment; mod blocking; pub use blocking::ReviewBlocking; @@ -25,6 +26,9 @@ mod routing; mod session; mod settings; +pub use approval_request::GuardianApprovalRequest; +pub use approval_request::GuardianMcpAnnotations; +pub use approval_request::GuardianNetworkAccessTrigger; pub use assessment::GuardianAssessment; pub use assessment::guardian_output_contract_prompt; pub use assessment::guardian_output_schema;