app-server: expose thread permission profiles

This commit is contained in:
Michael Bolin
2026-04-20 11:27:49 -07:00
parent ac2c7a9c94
commit 5cc2e1597e
15 changed files with 1548 additions and 229 deletions
@@ -1448,6 +1448,12 @@ mod tests {
approval_policy: v2::AskForApproval::OnFailure,
approvals_reviewer: v2::ApprovalsReviewer::User,
sandbox: v2::SandboxPolicy::DangerFullAccess,
permission_profile:
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
&codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
std::path::Path::new("/tmp"),
)
.into(),
reasoning_effort: None,
},
};
@@ -1490,6 +1496,24 @@ mod tests {
"sandbox": {
"type": "dangerFullAccess"
},
"permissionProfile": {
"network": {
"enabled": true,
},
"fileSystem": {
"entries": [
{
"path": {
"type": "special",
"value": {
"kind": "root",
},
},
"access": "write",
},
],
},
},
"reasoningEffort": null
}
}),
@@ -1377,6 +1377,70 @@ impl From<FileSystemSandboxEntry> for CoreFileSystemSandboxEntry {
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PermissionProfileFileSystemPermissions {
pub entries: Vec<FileSystemSandboxEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub glob_scan_max_depth: Option<usize>,
}
impl From<CoreFileSystemPermissions> for PermissionProfileFileSystemPermissions {
fn from(value: CoreFileSystemPermissions) -> Self {
Self {
entries: value
.entries
.into_iter()
.map(FileSystemSandboxEntry::from)
.collect(),
glob_scan_max_depth: value.glob_scan_max_depth,
}
}
}
impl From<PermissionProfileFileSystemPermissions> for CoreFileSystemPermissions {
fn from(value: PermissionProfileFileSystemPermissions) -> Self {
Self {
entries: value
.entries
.into_iter()
.map(CoreFileSystemSandboxEntry::from)
.collect(),
glob_scan_max_depth: value.glob_scan_max_depth,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PermissionProfile {
pub network: Option<AdditionalNetworkPermissions>,
pub file_system: Option<PermissionProfileFileSystemPermissions>,
}
impl From<CorePermissionProfile> for PermissionProfile {
fn from(value: CorePermissionProfile) -> Self {
Self {
network: value.network.map(AdditionalNetworkPermissions::from),
file_system: value
.file_system
.map(PermissionProfileFileSystemPermissions::from),
}
}
}
impl From<PermissionProfile> for CorePermissionProfile {
fn from(value: PermissionProfile) -> Self {
Self {
network: value.network.map(CoreNetworkPermissions::from),
file_system: value.file_system.map(CoreFileSystemPermissions::from),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -2963,6 +3027,7 @@ pub struct ThreadStartResponse {
/// Reviewer currently used for approval requests on this thread.
pub approvals_reviewer: ApprovalsReviewer,
pub sandbox: SandboxPolicy,
pub permission_profile: PermissionProfile,
pub reasoning_effort: Option<ReasoningEffort>,
}
@@ -3052,6 +3117,7 @@ pub struct ThreadResumeResponse {
/// Reviewer currently used for approval requests on this thread.
pub approvals_reviewer: ApprovalsReviewer,
pub sandbox: SandboxPolicy,
pub permission_profile: PermissionProfile,
pub reasoning_effort: Option<ReasoningEffort>,
}
@@ -3132,6 +3198,7 @@ pub struct ThreadForkResponse {
/// Reviewer currently used for approval requests on this thread.
pub approvals_reviewer: ApprovalsReviewer,
pub sandbox: SandboxPolicy,
pub permission_profile: PermissionProfile,
pub reasoning_effort: Option<ReasoningEffort>,
}
@@ -7196,6 +7263,38 @@ mod tests {
);
}
#[test]
fn permission_profile_file_system_permissions_preserves_glob_scan_depth() {
let core_permissions = CoreFileSystemPermissions {
entries: vec![CoreFileSystemSandboxEntry {
path: CoreFileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: CoreFileSystemAccessMode::None,
}],
glob_scan_max_depth: Some(2),
};
let permissions = PermissionProfileFileSystemPermissions::from(core_permissions.clone());
assert_eq!(
permissions,
PermissionProfileFileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
},
access: FileSystemAccessMode::None,
}],
glob_scan_max_depth: Some(2),
}
);
assert_eq!(
CoreFileSystemPermissions::from(permissions),
core_permissions
);
}
#[test]
fn permissions_request_approval_response_uses_granted_permission_profile_without_macos() {
let read_only_path = if cfg!(windows) {