mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
fix(core): clarify request permissions results
Return model-visible status and guidance from `request_permissions` so follow-up turns know the UI decision has already resolved. This keeps approved permissions framed as active instead of pending approval. Update prompt and tool descriptions with the same contract, and cover the granted and denied output shapes plus the follow-up request payload.
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
use codex_protocol::request_permissions::RequestPermissionsArgs;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
use codex_sandboxing::policy_transforms::normalize_additional_permissions;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
@@ -11,6 +15,39 @@ use crate::tools::registry::ToolKind;
|
||||
|
||||
pub struct RequestPermissionsHandler;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum RequestPermissionsToolStatus {
|
||||
Granted,
|
||||
Denied,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Eq)]
|
||||
struct RequestPermissionsToolOutput {
|
||||
status: RequestPermissionsToolStatus,
|
||||
scope: PermissionGrantScope,
|
||||
permissions: RequestPermissionProfile,
|
||||
message: &'static str,
|
||||
}
|
||||
|
||||
fn output_for_response(response: RequestPermissionsResponse) -> RequestPermissionsToolOutput {
|
||||
if response.permissions.is_empty() {
|
||||
RequestPermissionsToolOutput {
|
||||
status: RequestPermissionsToolStatus::Denied,
|
||||
scope: response.scope,
|
||||
permissions: response.permissions,
|
||||
message: "The user has already denied or declined this permission request. Do not say that approval is still pending.",
|
||||
}
|
||||
} else {
|
||||
RequestPermissionsToolOutput {
|
||||
status: RequestPermissionsToolStatus::Granted,
|
||||
scope: response.scope,
|
||||
permissions: response.permissions,
|
||||
message: "The user has already approved this permission request. These permissions are active now; do not ask the user to approve them again.",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for RequestPermissionsHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
@@ -56,7 +93,8 @@ impl ToolHandler for RequestPermissionsHandler {
|
||||
)
|
||||
})?;
|
||||
|
||||
let content = serde_json::to_string(&response).map_err(|err| {
|
||||
let tool_output = output_for_response(response);
|
||||
let content = serde_json::to_string(&tool_output).map_err(|err| {
|
||||
FunctionCallError::Fatal(format!(
|
||||
"failed to serialize request_permissions response: {err}"
|
||||
))
|
||||
@@ -65,3 +103,66 @@ impl ToolHandler for RequestPermissionsHandler {
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
use super::output_for_response;
|
||||
|
||||
#[test]
|
||||
fn request_permissions_tool_output_marks_granted_permissions_as_active() {
|
||||
let permissions = RequestPermissionProfile {
|
||||
network: Some(NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
file_system: None,
|
||||
};
|
||||
|
||||
let output = output_for_response(RequestPermissionsResponse {
|
||||
permissions,
|
||||
scope: PermissionGrantScope::Session,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(output).expect("serialize tool output"),
|
||||
json!({
|
||||
"status": "granted",
|
||||
"scope": "session",
|
||||
"permissions": {
|
||||
"network": {
|
||||
"enabled": true,
|
||||
},
|
||||
"file_system": null,
|
||||
},
|
||||
"message": "The user has already approved this permission request. These permissions are active now; do not ask the user to approve them again.",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_permissions_tool_output_marks_empty_permissions_as_denied() {
|
||||
let output = output_for_response(RequestPermissionsResponse {
|
||||
permissions: RequestPermissionProfile::default(),
|
||||
scope: PermissionGrantScope::Turn,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(output).expect("serialize tool output"),
|
||||
json!({
|
||||
"status": "denied",
|
||||
"scope": "turn",
|
||||
"permissions": {
|
||||
"network": null,
|
||||
"file_system": null,
|
||||
},
|
||||
"message": "The user has already denied or declined this permission request. Do not say that approval is still pending.",
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user