mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
## Why Permission requests can originate in an environment whose path convention differs from the Codex host. Requiring the request working directory to be host-native prevents those requests from reaching interactive approval. ## What changed - Carry the request `cwd` as a `LegacyAppPathString` through core events and the app-server protocol so the target environment's native representation is preserved. - Convert the preserved value back to a `PathUri` when materializing permission grants. - Regenerate the JSON and TypeScript protocol schemas for the updated `cwd` type. ## Testing - Exercise the app-server permission request round trip with a target-native working directory across host path conventions. - Verify remote permission requests retain their environment working directory and granted access still unblocks later execution. GitOrigin-RevId: 97dc9707d9048883d20bab1b69c93871039f3188
808 lines
28 KiB
Rust
808 lines
28 KiB
Rust
use super::shared::v2_enum_from_core;
|
|
use crate::JsonSchema;
|
|
use crate::TS;
|
|
use codex_protocol::approvals::ExecPolicyAmendment as CoreExecPolicyAmendment;
|
|
use codex_protocol::approvals::NetworkApprovalContext as CoreNetworkApprovalContext;
|
|
use codex_protocol::approvals::NetworkApprovalProtocol as CoreNetworkApprovalProtocol;
|
|
use codex_protocol::approvals::NetworkPolicyAmendment as CoreNetworkPolicyAmendment;
|
|
use codex_protocol::approvals::NetworkPolicyRuleAction as CoreNetworkPolicyRuleAction;
|
|
use codex_protocol::models::ActivePermissionProfile as CoreActivePermissionProfile;
|
|
use codex_protocol::models::AdditionalPermissionProfile as CoreAdditionalPermissionProfile;
|
|
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
|
|
use codex_protocol::models::LegacyReadWriteRoots;
|
|
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
|
|
use codex_protocol::permissions::FileSystemAccessMode as CoreFileSystemAccessMode;
|
|
use codex_protocol::permissions::FileSystemPath as CoreFileSystemPath;
|
|
use codex_protocol::permissions::FileSystemSandboxEntry as CoreFileSystemSandboxEntry;
|
|
use codex_protocol::permissions::FileSystemSpecialPath as CoreFileSystemSpecialPath;
|
|
use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess;
|
|
use codex_protocol::request_permissions::PermissionGrantScope as CorePermissionGrantScope;
|
|
use codex_protocol::request_permissions::RequestPermissionProfile as CoreRequestPermissionProfile;
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
|
use codex_utils_path_uri::LegacyAppPathString;
|
|
use codex_utils_path_uri::PathUri;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use std::io;
|
|
use std::num::NonZeroUsize;
|
|
use std::path::Path;
|
|
|
|
v2_enum_from_core! {
|
|
pub enum NetworkApprovalProtocol from CoreNetworkApprovalProtocol {
|
|
Http,
|
|
Https,
|
|
Socks5Tcp,
|
|
Socks5Udp,
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct NetworkApprovalContext {
|
|
pub host: String,
|
|
pub protocol: NetworkApprovalProtocol,
|
|
}
|
|
|
|
impl From<CoreNetworkApprovalContext> for NetworkApprovalContext {
|
|
fn from(value: CoreNetworkApprovalContext) -> Self {
|
|
Self {
|
|
host: value.host,
|
|
protocol: value.protocol.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct AdditionalFileSystemPermissions {
|
|
/// This will be removed in favor of `entries`.
|
|
pub read: Option<Vec<LegacyAppPathString>>,
|
|
/// This will be removed in favor of `entries`.
|
|
pub write: Option<Vec<LegacyAppPathString>>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
#[ts(optional)]
|
|
pub glob_scan_max_depth: Option<NonZeroUsize>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
#[ts(optional)]
|
|
pub entries: Option<Vec<FileSystemSandboxEntry>>,
|
|
}
|
|
|
|
fn permission_path_uri(path: LegacyAppPathString) -> io::Result<PathUri> {
|
|
if let Ok(path) = AbsolutePathBuf::try_from(path.clone()) {
|
|
return Ok(path.into());
|
|
}
|
|
PathUri::try_from(path).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))
|
|
}
|
|
|
|
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
|
impl From<CoreFileSystemPermissions> for AdditionalFileSystemPermissions {
|
|
fn from(value: CoreFileSystemPermissions) -> Self {
|
|
if let Some(LegacyReadWriteRoots { read, write }) = value.legacy_read_write_roots() {
|
|
let mut entries = Vec::with_capacity(
|
|
read.as_ref().map_or(0, Vec::len) + write.as_ref().map_or(0, Vec::len),
|
|
);
|
|
if let Some(paths) = read.as_ref() {
|
|
entries.extend(paths.iter().map(|path| FileSystemSandboxEntry {
|
|
path: FileSystemPath::Path {
|
|
path: LegacyAppPathString::from_abs_path(path),
|
|
},
|
|
access: FileSystemAccessMode::Read,
|
|
}));
|
|
}
|
|
if let Some(paths) = write.as_ref() {
|
|
entries.extend(paths.iter().map(|path| FileSystemSandboxEntry {
|
|
path: FileSystemPath::Path {
|
|
path: LegacyAppPathString::from_abs_path(path),
|
|
},
|
|
access: FileSystemAccessMode::Write,
|
|
}));
|
|
}
|
|
Self {
|
|
read: read.map(|paths| {
|
|
paths
|
|
.iter()
|
|
.map(LegacyAppPathString::from_abs_path)
|
|
.collect()
|
|
}),
|
|
write: write.map(|paths| {
|
|
paths
|
|
.iter()
|
|
.map(LegacyAppPathString::from_abs_path)
|
|
.collect()
|
|
}),
|
|
glob_scan_max_depth: None,
|
|
entries: Some(entries),
|
|
}
|
|
} else {
|
|
let mut read = Vec::new();
|
|
let mut write = Vec::new();
|
|
let legacy_compatible = value.glob_scan_max_depth.is_none()
|
|
&& value.entries.iter().all(|entry| {
|
|
let CoreFileSystemPath::Path { path } = &entry.path else {
|
|
return false;
|
|
};
|
|
let legacy_path = LegacyAppPathString::from(path.clone());
|
|
if legacy_path.to_inferred_path_uri().as_ref() != Some(path) {
|
|
return false;
|
|
}
|
|
match entry.access {
|
|
CoreFileSystemAccessMode::Read => read.push(legacy_path),
|
|
CoreFileSystemAccessMode::Write => write.push(legacy_path),
|
|
CoreFileSystemAccessMode::Deny => return false,
|
|
}
|
|
true
|
|
});
|
|
Self {
|
|
read: (legacy_compatible && !read.is_empty()).then_some(read),
|
|
write: (legacy_compatible && !write.is_empty()).then_some(write),
|
|
glob_scan_max_depth: value.glob_scan_max_depth,
|
|
entries: Some(
|
|
value
|
|
.entries
|
|
.into_iter()
|
|
.map(FileSystemSandboxEntry::from)
|
|
.collect(),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
|
impl TryFrom<AdditionalFileSystemPermissions> for CoreFileSystemPermissions {
|
|
type Error = io::Error;
|
|
|
|
fn try_from(value: AdditionalFileSystemPermissions) -> Result<Self, Self::Error> {
|
|
let mut permissions = if let Some(entries) = value.entries {
|
|
Self {
|
|
entries: entries
|
|
.into_iter()
|
|
.map(CoreFileSystemSandboxEntry::try_from)
|
|
.collect::<io::Result<_>>()?,
|
|
glob_scan_max_depth: None,
|
|
}
|
|
} else {
|
|
let read = value
|
|
.read
|
|
.map(|paths| {
|
|
paths
|
|
.into_iter()
|
|
.map(permission_path_uri)
|
|
.collect::<io::Result<Vec<_>>>()
|
|
})
|
|
.transpose()?;
|
|
let write = value
|
|
.write
|
|
.map(|paths| {
|
|
paths
|
|
.into_iter()
|
|
.map(permission_path_uri)
|
|
.collect::<io::Result<Vec<_>>>()
|
|
})
|
|
.transpose()?;
|
|
CoreFileSystemPermissions::from_read_write_path_uris(read, write)
|
|
};
|
|
permissions.glob_scan_max_depth = value.glob_scan_max_depth;
|
|
Ok(permissions)
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct AdditionalNetworkPermissions {
|
|
pub enabled: Option<bool>,
|
|
}
|
|
|
|
impl From<CoreNetworkPermissions> for AdditionalNetworkPermissions {
|
|
fn from(value: CoreNetworkPermissions) -> Self {
|
|
Self {
|
|
enabled: value.enabled,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<AdditionalNetworkPermissions> for CoreNetworkPermissions {
|
|
fn from(value: AdditionalNetworkPermissions) -> Self {
|
|
Self {
|
|
enabled: value.enabled,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[serde(deny_unknown_fields)]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct RequestPermissionProfile {
|
|
pub network: Option<AdditionalNetworkPermissions>,
|
|
pub file_system: Option<AdditionalFileSystemPermissions>,
|
|
}
|
|
|
|
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
|
impl From<CoreRequestPermissionProfile> for RequestPermissionProfile {
|
|
fn from(value: CoreRequestPermissionProfile) -> Self {
|
|
Self {
|
|
network: value.network.map(AdditionalNetworkPermissions::from),
|
|
file_system: value.file_system.map(AdditionalFileSystemPermissions::from),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<RequestPermissionProfile> for CoreRequestPermissionProfile {
|
|
type Error = io::Error;
|
|
|
|
fn try_from(value: RequestPermissionProfile) -> Result<Self, Self::Error> {
|
|
Ok(Self {
|
|
network: value.network.map(CoreNetworkPermissions::from),
|
|
file_system: value
|
|
.file_system
|
|
.map(CoreFileSystemPermissions::try_from)
|
|
.transpose()?,
|
|
})
|
|
}
|
|
}
|
|
|
|
v2_enum_from_core!(
|
|
pub enum FileSystemAccessMode from CoreFileSystemAccessMode {
|
|
Read,
|
|
Write,
|
|
Deny
|
|
}
|
|
);
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
#[ts(tag = "kind")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum FileSystemSpecialPath {
|
|
Root,
|
|
Minimal,
|
|
#[serde(alias = "current_working_directory")]
|
|
ProjectRoots {
|
|
subpath: Option<LegacyAppPathString>,
|
|
},
|
|
Tmpdir,
|
|
SlashTmp,
|
|
Unknown {
|
|
path: String,
|
|
subpath: Option<LegacyAppPathString>,
|
|
},
|
|
}
|
|
|
|
impl From<CoreFileSystemSpecialPath> for FileSystemSpecialPath {
|
|
fn from(value: CoreFileSystemSpecialPath) -> Self {
|
|
match value {
|
|
CoreFileSystemSpecialPath::Root => Self::Root,
|
|
CoreFileSystemSpecialPath::Minimal => Self::Minimal,
|
|
CoreFileSystemSpecialPath::ProjectRoots { subpath } => Self::ProjectRoots {
|
|
subpath: subpath
|
|
.as_deref()
|
|
.map(Path::new)
|
|
.map(LegacyAppPathString::from_path),
|
|
},
|
|
CoreFileSystemSpecialPath::Tmpdir => Self::Tmpdir,
|
|
CoreFileSystemSpecialPath::SlashTmp => Self::SlashTmp,
|
|
CoreFileSystemSpecialPath::Unknown { path, subpath } => Self::Unknown {
|
|
path,
|
|
subpath: subpath
|
|
.as_deref()
|
|
.map(Path::new)
|
|
.map(LegacyAppPathString::from_path),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<FileSystemSpecialPath> for CoreFileSystemSpecialPath {
|
|
fn from(value: FileSystemSpecialPath) -> Self {
|
|
match value {
|
|
FileSystemSpecialPath::Root => Self::Root,
|
|
FileSystemSpecialPath::Minimal => Self::Minimal,
|
|
FileSystemSpecialPath::ProjectRoots { subpath } => Self::ProjectRoots {
|
|
subpath: subpath.map(LegacyAppPathString::into_string),
|
|
},
|
|
FileSystemSpecialPath::Tmpdir => Self::Tmpdir,
|
|
FileSystemSpecialPath::SlashTmp => Self::SlashTmp,
|
|
FileSystemSpecialPath::Unknown { path, subpath } => Self::Unknown {
|
|
path,
|
|
subpath: subpath.map(LegacyAppPathString::into_string),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(tag = "type", rename_all = "snake_case")]
|
|
#[ts(tag = "type")]
|
|
#[ts(export_to = "v2/")]
|
|
// TODO(anp): Rename this type to distinguish it from the protocol FileSystemPath.
|
|
pub enum FileSystemPath {
|
|
Path { path: LegacyAppPathString },
|
|
GlobPattern { pattern: String },
|
|
Special { value: FileSystemSpecialPath },
|
|
}
|
|
|
|
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
|
impl From<CoreFileSystemPath> for FileSystemPath {
|
|
fn from(value: CoreFileSystemPath) -> Self {
|
|
match value {
|
|
CoreFileSystemPath::Path { path } => Self::Path { path: path.into() },
|
|
CoreFileSystemPath::GlobPattern { pattern } => Self::GlobPattern { pattern },
|
|
CoreFileSystemPath::Special { value } => Self::Special {
|
|
value: value.into(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
|
impl TryFrom<FileSystemPath> for CoreFileSystemPath {
|
|
type Error = io::Error;
|
|
|
|
fn try_from(value: FileSystemPath) -> Result<Self, Self::Error> {
|
|
Ok(match value {
|
|
FileSystemPath::Path { path } => Self::Path {
|
|
path: permission_path_uri(path)?,
|
|
},
|
|
FileSystemPath::GlobPattern { pattern } => Self::GlobPattern { pattern },
|
|
FileSystemPath::Special { value } => Self::Special {
|
|
value: value.into(),
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct FileSystemSandboxEntry {
|
|
pub path: FileSystemPath,
|
|
pub access: FileSystemAccessMode,
|
|
}
|
|
|
|
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
|
impl From<CoreFileSystemSandboxEntry> for FileSystemSandboxEntry {
|
|
fn from(value: CoreFileSystemSandboxEntry) -> Self {
|
|
Self {
|
|
path: value.path.into(),
|
|
access: value.access.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<FileSystemSandboxEntry> for CoreFileSystemSandboxEntry {
|
|
type Error = io::Error;
|
|
|
|
fn try_from(value: FileSystemSandboxEntry) -> Result<Self, Self::Error> {
|
|
Ok(Self {
|
|
path: value.path.try_into()?,
|
|
access: value.access.to_core(),
|
|
missing_path_behavior: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct PermissionProfileListParams {
|
|
/// Opaque pagination cursor returned by a previous call.
|
|
#[ts(optional = nullable)]
|
|
pub cursor: Option<String>,
|
|
/// Optional page size; defaults to the full result set.
|
|
#[ts(optional = nullable)]
|
|
pub limit: Option<u32>,
|
|
/// Optional working directory to resolve project config layers.
|
|
#[ts(optional = nullable)]
|
|
pub cwd: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct PermissionProfileSummary {
|
|
/// Available permission profile identifier.
|
|
pub id: String,
|
|
/// Optional user-facing description for display in clients.
|
|
pub description: Option<String>,
|
|
/// Whether the effective requirements allow selecting this profile.
|
|
pub allowed: bool,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct PermissionProfileListResponse {
|
|
pub data: Vec<PermissionProfileSummary>,
|
|
/// Opaque cursor to pass to the next call to continue after the last item.
|
|
/// If None, there are no more items to return.
|
|
pub next_cursor: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct ActivePermissionProfile {
|
|
/// Identifier from `default_permissions` or the implicit built-in default,
|
|
/// such as `:workspace` or a user-defined `[permissions.<id>]` profile.
|
|
pub id: String,
|
|
/// Parent profile identifier from the selected permissions profile's
|
|
/// `extends` setting, when present.
|
|
#[serde(default)]
|
|
pub extends: Option<String>,
|
|
}
|
|
|
|
impl ActivePermissionProfile {
|
|
pub fn new(id: impl Into<String>) -> Self {
|
|
Self {
|
|
id: id.into(),
|
|
extends: None,
|
|
}
|
|
}
|
|
|
|
pub fn read_only() -> Self {
|
|
CoreActivePermissionProfile::read_only().into()
|
|
}
|
|
}
|
|
|
|
impl From<CoreActivePermissionProfile> for ActivePermissionProfile {
|
|
fn from(value: CoreActivePermissionProfile) -> Self {
|
|
Self {
|
|
id: value.id,
|
|
extends: value.extends,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ActivePermissionProfile> for CoreActivePermissionProfile {
|
|
fn from(value: ActivePermissionProfile) -> Self {
|
|
Self {
|
|
id: value.id,
|
|
extends: value.extends,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct AdditionalPermissionProfile {
|
|
/// Partial overlay used for per-command permission requests.
|
|
pub network: Option<AdditionalNetworkPermissions>,
|
|
pub file_system: Option<AdditionalFileSystemPermissions>,
|
|
}
|
|
|
|
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
|
impl From<CoreAdditionalPermissionProfile> for AdditionalPermissionProfile {
|
|
fn from(value: CoreAdditionalPermissionProfile) -> Self {
|
|
Self {
|
|
network: value.network.map(AdditionalNetworkPermissions::from),
|
|
file_system: value.file_system.map(AdditionalFileSystemPermissions::from),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<AdditionalPermissionProfile> for CoreAdditionalPermissionProfile {
|
|
type Error = io::Error;
|
|
|
|
fn try_from(value: AdditionalPermissionProfile) -> Result<Self, Self::Error> {
|
|
Ok(Self {
|
|
network: value.network.map(CoreNetworkPermissions::from),
|
|
file_system: value
|
|
.file_system
|
|
.map(CoreFileSystemPermissions::try_from)
|
|
.transpose()?,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct GrantedPermissionProfile {
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
#[ts(optional)]
|
|
pub network: Option<AdditionalNetworkPermissions>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
#[ts(optional)]
|
|
pub file_system: Option<AdditionalFileSystemPermissions>,
|
|
}
|
|
|
|
impl TryFrom<GrantedPermissionProfile> for CoreAdditionalPermissionProfile {
|
|
type Error = io::Error;
|
|
|
|
fn try_from(value: GrantedPermissionProfile) -> Result<Self, Self::Error> {
|
|
Ok(Self {
|
|
network: value.network.map(CoreNetworkPermissions::from),
|
|
file_system: value
|
|
.file_system
|
|
.map(CoreFileSystemPermissions::try_from)
|
|
.transpose()?,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum NetworkAccess {
|
|
#[default]
|
|
Restricted,
|
|
Enabled,
|
|
}
|
|
|
|
#[derive(Serialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
#[ts(tag = "type")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum SandboxPolicy {
|
|
DangerFullAccess,
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(rename_all = "camelCase")]
|
|
ReadOnly {
|
|
#[serde(default)]
|
|
network_access: bool,
|
|
},
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(rename_all = "camelCase")]
|
|
ExternalSandbox {
|
|
#[serde(default)]
|
|
network_access: NetworkAccess,
|
|
},
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(rename_all = "camelCase")]
|
|
WorkspaceWrite {
|
|
#[serde(default)]
|
|
writable_roots: Vec<AbsolutePathBuf>,
|
|
#[serde(default)]
|
|
network_access: bool,
|
|
#[serde(default)]
|
|
exclude_tmpdir_env_var: bool,
|
|
#[serde(default)]
|
|
exclude_slash_tmp: bool,
|
|
},
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
enum SandboxPolicyDeserialize {
|
|
DangerFullAccess,
|
|
#[serde(rename_all = "camelCase")]
|
|
ReadOnly {
|
|
#[serde(default)]
|
|
network_access: bool,
|
|
#[serde(default)]
|
|
access: Option<LegacyReadOnlyAccess>,
|
|
},
|
|
#[serde(rename_all = "camelCase")]
|
|
ExternalSandbox {
|
|
#[serde(default)]
|
|
network_access: NetworkAccess,
|
|
},
|
|
#[serde(rename_all = "camelCase")]
|
|
WorkspaceWrite {
|
|
#[serde(default)]
|
|
writable_roots: Vec<AbsolutePathBuf>,
|
|
#[serde(default)]
|
|
read_only_access: Option<LegacyReadOnlyAccess>,
|
|
#[serde(default)]
|
|
network_access: bool,
|
|
#[serde(default)]
|
|
exclude_tmpdir_env_var: bool,
|
|
#[serde(default)]
|
|
exclude_slash_tmp: bool,
|
|
},
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
enum LegacyReadOnlyAccess {
|
|
FullAccess,
|
|
Restricted,
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for SandboxPolicy {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
match SandboxPolicyDeserialize::deserialize(deserializer)? {
|
|
SandboxPolicyDeserialize::DangerFullAccess => Ok(SandboxPolicy::DangerFullAccess),
|
|
SandboxPolicyDeserialize::ReadOnly {
|
|
network_access,
|
|
access,
|
|
} => {
|
|
if matches!(access, Some(LegacyReadOnlyAccess::Restricted)) {
|
|
return Err(serde::de::Error::custom(
|
|
"readOnly.access is no longer supported; use permissionProfile for restricted reads",
|
|
));
|
|
}
|
|
Ok(SandboxPolicy::ReadOnly { network_access })
|
|
}
|
|
SandboxPolicyDeserialize::ExternalSandbox { network_access } => {
|
|
Ok(SandboxPolicy::ExternalSandbox { network_access })
|
|
}
|
|
SandboxPolicyDeserialize::WorkspaceWrite {
|
|
writable_roots,
|
|
read_only_access,
|
|
network_access,
|
|
exclude_tmpdir_env_var,
|
|
exclude_slash_tmp,
|
|
} => {
|
|
if matches!(read_only_access, Some(LegacyReadOnlyAccess::Restricted)) {
|
|
return Err(serde::de::Error::custom(
|
|
"workspaceWrite.readOnlyAccess is no longer supported; use permissionProfile for restricted reads",
|
|
));
|
|
}
|
|
Ok(SandboxPolicy::WorkspaceWrite {
|
|
writable_roots,
|
|
network_access,
|
|
exclude_tmpdir_env_var,
|
|
exclude_slash_tmp,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SandboxPolicy {
|
|
pub fn to_core(&self) -> codex_protocol::protocol::SandboxPolicy {
|
|
match self {
|
|
SandboxPolicy::DangerFullAccess => {
|
|
codex_protocol::protocol::SandboxPolicy::DangerFullAccess
|
|
}
|
|
SandboxPolicy::ReadOnly { network_access } => {
|
|
codex_protocol::protocol::SandboxPolicy::ReadOnly {
|
|
network_access: *network_access,
|
|
}
|
|
}
|
|
SandboxPolicy::ExternalSandbox { network_access } => {
|
|
codex_protocol::protocol::SandboxPolicy::ExternalSandbox {
|
|
network_access: match network_access {
|
|
NetworkAccess::Restricted => CoreNetworkAccess::Restricted,
|
|
NetworkAccess::Enabled => CoreNetworkAccess::Enabled,
|
|
},
|
|
}
|
|
}
|
|
SandboxPolicy::WorkspaceWrite {
|
|
writable_roots,
|
|
network_access,
|
|
exclude_tmpdir_env_var,
|
|
exclude_slash_tmp,
|
|
} => codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
|
|
writable_roots: writable_roots.clone(),
|
|
network_access: *network_access,
|
|
exclude_tmpdir_env_var: *exclude_tmpdir_env_var,
|
|
exclude_slash_tmp: *exclude_slash_tmp,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
|
|
fn from(value: codex_protocol::protocol::SandboxPolicy) -> Self {
|
|
match value {
|
|
codex_protocol::protocol::SandboxPolicy::DangerFullAccess => {
|
|
SandboxPolicy::DangerFullAccess
|
|
}
|
|
codex_protocol::protocol::SandboxPolicy::ReadOnly { network_access } => {
|
|
SandboxPolicy::ReadOnly { network_access }
|
|
}
|
|
codex_protocol::protocol::SandboxPolicy::ExternalSandbox { network_access } => {
|
|
SandboxPolicy::ExternalSandbox {
|
|
network_access: match network_access {
|
|
CoreNetworkAccess::Restricted => NetworkAccess::Restricted,
|
|
CoreNetworkAccess::Enabled => NetworkAccess::Enabled,
|
|
},
|
|
}
|
|
}
|
|
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
|
|
writable_roots,
|
|
network_access,
|
|
exclude_tmpdir_env_var,
|
|
exclude_slash_tmp,
|
|
} => SandboxPolicy::WorkspaceWrite {
|
|
writable_roots,
|
|
network_access,
|
|
exclude_tmpdir_env_var,
|
|
exclude_slash_tmp,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(transparent)]
|
|
#[ts(type = "Array<string>", export_to = "v2/")]
|
|
pub struct ExecPolicyAmendment {
|
|
pub command: Vec<String>,
|
|
}
|
|
|
|
impl ExecPolicyAmendment {
|
|
pub fn into_core(self) -> CoreExecPolicyAmendment {
|
|
CoreExecPolicyAmendment::new(self.command)
|
|
}
|
|
}
|
|
|
|
impl From<CoreExecPolicyAmendment> for ExecPolicyAmendment {
|
|
fn from(value: CoreExecPolicyAmendment) -> Self {
|
|
Self {
|
|
command: value.command().to_vec(),
|
|
}
|
|
}
|
|
}
|
|
|
|
v2_enum_from_core!(
|
|
pub enum NetworkPolicyRuleAction from CoreNetworkPolicyRuleAction {
|
|
Allow, Deny
|
|
}
|
|
);
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct NetworkPolicyAmendment {
|
|
pub host: String,
|
|
pub action: NetworkPolicyRuleAction,
|
|
}
|
|
|
|
impl NetworkPolicyAmendment {
|
|
pub fn into_core(self) -> CoreNetworkPolicyAmendment {
|
|
CoreNetworkPolicyAmendment {
|
|
host: self.host,
|
|
action: self.action.to_core(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<CoreNetworkPolicyAmendment> for NetworkPolicyAmendment {
|
|
fn from(value: CoreNetworkPolicyAmendment) -> Self {
|
|
Self {
|
|
host: value.host,
|
|
action: NetworkPolicyRuleAction::from(value.action),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct PermissionsRequestApprovalParams {
|
|
pub thread_id: String,
|
|
pub turn_id: String,
|
|
pub item_id: String,
|
|
#[serde(default)]
|
|
pub environment_id: Option<String>,
|
|
/// Unix timestamp (in milliseconds) when this approval request started.
|
|
#[ts(type = "number")]
|
|
pub started_at_ms: i64,
|
|
pub cwd: LegacyAppPathString,
|
|
pub reason: Option<String>,
|
|
pub permissions: RequestPermissionProfile,
|
|
}
|
|
|
|
v2_enum_from_core!(
|
|
#[derive(Default)]
|
|
pub enum PermissionGrantScope from CorePermissionGrantScope {
|
|
#[default]
|
|
Turn,
|
|
Session
|
|
}
|
|
);
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct PermissionsRequestApprovalResponse {
|
|
pub permissions: GrantedPermissionProfile,
|
|
#[serde(default)]
|
|
pub scope: PermissionGrantScope,
|
|
/// Review every subsequent command in this turn before normal sandboxed execution.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
#[ts(optional)]
|
|
pub strict_auto_review: Option<bool>,
|
|
}
|