forge_primitives/
permission_interaction.rs1use serde::{Deserialize, Serialize};
13
14use crate::permissions::PermissionUpdate;
15use crate::session_update::ToolCall;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(tag = "kind", rename_all = "snake_case")]
23pub enum PermissionAction {
24 Allow,
26 AllowWithUpdates { updates: Vec<PermissionUpdate> },
28 AllowWithInput,
32 Deny,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "snake_case")]
41pub enum PermissionOptionKind {
42 Allow,
44 Deny,
46 Edit,
48 Notes,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct PermissionOption {
54 pub option_id: String,
55 pub name: String,
56 pub kind: PermissionOptionKind,
57 pub action: PermissionAction,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct PermissionRequest {
62 pub tool_call: ToolCall,
63 pub options: Vec<PermissionOption>,
64 pub display: Option<PermissionDisplay>,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
68pub struct PermissionDisplay {
69 pub title: Option<String>,
70 pub display_name: Option<String>,
71 pub description: Option<String>,
72 pub decision_reason: Option<String>,
75}
76
77impl PermissionDisplay {
78 pub fn is_empty(&self) -> bool {
80 self.title.as_ref().is_none_or(|value| value.trim().is_empty())
81 && self.display_name.as_ref().is_none_or(|value| value.trim().is_empty())
82 && self.description.as_ref().is_none_or(|value| value.trim().is_empty())
83 && self.decision_reason.as_ref().is_none_or(|value| value.trim().is_empty())
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88#[serde(tag = "outcome", rename_all = "snake_case")]
89pub enum PermissionOutcome {
90 Selected {
91 option_id: String,
92 action: PermissionAction,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
101 notes_text: Option<String>,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
105 edited_input: Option<serde_json::Value>,
106 },
107 Cancelled,
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::permission::PermissionMode;
114 use crate::permissions::PermissionUpdate;
115
116 #[test]
117 fn permission_action_round_trips_through_serde() {
118 let allow_with_updates = PermissionAction::AllowWithUpdates {
119 updates: vec![PermissionUpdate::SetMode {
120 mode: PermissionMode::Auto,
121 destination: None,
122 }],
123 };
124 let json = serde_json::to_string(&allow_with_updates).expect("serialize");
125 let back: PermissionAction = serde_json::from_str(&json).expect("deserialize");
126 assert_eq!(allow_with_updates, back);
127 }
128
129 #[test]
130 fn permission_outcome_selected_round_trips_with_notes_and_edited_input() {
131 let outcome = PermissionOutcome::Selected {
132 option_id: "allow_with_edits".into(),
133 action: PermissionAction::AllowWithInput,
134 notes_text: Some("trimmed args".to_owned()),
135 edited_input: Some(serde_json::json!({"command": "echo trimmed"})),
136 };
137 let json = serde_json::to_string(&outcome).expect("serialize");
138 let back: PermissionOutcome = serde_json::from_str(&json).expect("deserialize");
139 assert_eq!(outcome, back);
140 }
141}