tirea_extension_permission/
state.rs1use crate::model::{
2 PermissionRule, PermissionRuleScope, PermissionRuleSource, PermissionRuleset,
3 ToolPermissionBehavior,
4};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use tirea_state::State;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11#[serde(tag = "type", rename_all = "snake_case")]
12pub enum PermissionAction {
13 SetDefault {
14 behavior: ToolPermissionBehavior,
15 },
16 SetTool {
17 tool_id: String,
18 behavior: ToolPermissionBehavior,
19 },
20 RemoveTool {
21 tool_id: String,
22 },
23 ClearTools,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
28#[serde(tag = "type", rename_all = "snake_case")]
29pub enum PermissionPolicyAction {
30 SetDefault {
31 behavior: ToolPermissionBehavior,
32 },
33 SetTool {
34 tool_id: String,
35 behavior: ToolPermissionBehavior,
36 #[serde(default)]
37 scope: PermissionRuleScope,
38 #[serde(default)]
39 source: PermissionRuleSource,
40 },
41 RemoveTool {
42 tool_id: String,
43 },
44 ClearTools,
45 AllowTool {
46 tool_id: String,
47 },
48 DenyTool {
49 tool_id: String,
50 },
51}
52
53#[derive(Debug, Clone, Default, Serialize, Deserialize, State)]
55#[serde(default)]
56#[tirea(
57 path = "permission_policy",
58 action = "PermissionPolicyAction",
59 scope = "thread"
60)]
61pub struct PermissionPolicy {
62 pub default_behavior: ToolPermissionBehavior,
63 pub rules: HashMap<String, PermissionRule>,
64}
65
66#[derive(Debug, Clone, Default, Serialize, Deserialize, State)]
71#[serde(default)]
72#[tirea(
73 path = "permission_overrides",
74 action = "PermissionOverridesAction",
75 scope = "run"
76)]
77pub struct PermissionOverrides {
78 pub rules: HashMap<String, PermissionRule>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
83#[serde(tag = "type", rename_all = "snake_case")]
84pub enum PermissionOverridesAction {
85 SetTool {
86 tool_id: String,
87 behavior: ToolPermissionBehavior,
88 #[serde(default)]
89 scope: PermissionRuleScope,
90 #[serde(default)]
91 source: PermissionRuleSource,
92 },
93 RemoveTool {
94 tool_id: String,
95 },
96 Clear,
97}
98
99impl PermissionOverrides {
100 fn upsert_tool_rule(
101 &mut self,
102 tool_id: String,
103 behavior: ToolPermissionBehavior,
104 scope: PermissionRuleScope,
105 source: PermissionRuleSource,
106 ) {
107 let rule = PermissionRule::new_tool(tool_id, behavior)
108 .with_scope(scope)
109 .with_source(source);
110 self.rules.insert(rule.subject.key(), rule);
111 }
112
113 pub(super) fn reduce(&mut self, action: PermissionOverridesAction) {
114 match action {
115 PermissionOverridesAction::SetTool {
116 tool_id,
117 behavior,
118 scope,
119 source,
120 } => self.upsert_tool_rule(tool_id, behavior, scope, source),
121 PermissionOverridesAction::RemoveTool { tool_id } => {
122 self.rules.remove(
123 &PermissionRule::new_tool(tool_id, ToolPermissionBehavior::Ask)
124 .subject
125 .key(),
126 );
127 }
128 PermissionOverridesAction::Clear => self.rules.clear(),
129 }
130 }
131}
132
133impl PermissionPolicy {
134 fn upsert_tool_rule(
135 &mut self,
136 tool_id: String,
137 behavior: ToolPermissionBehavior,
138 scope: PermissionRuleScope,
139 source: PermissionRuleSource,
140 ) {
141 let rule = PermissionRule::new_tool(tool_id, behavior)
142 .with_scope(scope)
143 .with_source(source);
144 self.rules.insert(rule.subject.key(), rule);
145 }
146
147 pub(super) fn reduce(&mut self, action: PermissionPolicyAction) {
148 match action {
149 PermissionPolicyAction::SetDefault { behavior } => self.default_behavior = behavior,
150 PermissionPolicyAction::SetTool {
151 tool_id,
152 behavior,
153 scope,
154 source,
155 } => self.upsert_tool_rule(tool_id, behavior, scope, source),
156 PermissionPolicyAction::RemoveTool { tool_id } => {
157 self.rules.remove(
158 &PermissionRule::new_tool(tool_id, ToolPermissionBehavior::Ask)
159 .subject
160 .key(),
161 );
162 }
163 PermissionPolicyAction::ClearTools => self.rules.clear(),
164 PermissionPolicyAction::AllowTool { tool_id } => self.upsert_tool_rule(
165 tool_id,
166 ToolPermissionBehavior::Allow,
167 PermissionRuleScope::Thread,
168 PermissionRuleSource::Runtime,
169 ),
170 PermissionPolicyAction::DenyTool { tool_id } => self.upsert_tool_rule(
171 tool_id,
172 ToolPermissionBehavior::Deny,
173 PermissionRuleScope::Thread,
174 PermissionRuleSource::Runtime,
175 ),
176 }
177 }
178}
179
180#[derive(Debug, Clone, Default, Serialize, Deserialize)]
181#[serde(default)]
182struct LegacyPermissionOverrides {
183 pub default_behavior: ToolPermissionBehavior,
184 pub tools: HashMap<String, ToolPermissionBehavior>,
185}
186
187#[derive(Debug, Clone, Default, Serialize, Deserialize)]
188#[serde(default)]
189struct LegacyPermissionPolicy {
190 pub default_behavior: ToolPermissionBehavior,
191 pub allowed_tools: Vec<String>,
192 pub denied_tools: Vec<String>,
193}
194
195pub fn permission_state_action(
197 action: PermissionAction,
198) -> tirea_contract::runtime::state::AnyStateAction {
199 use tirea_contract::runtime::state::AnyStateAction;
200 let policy_action = match action {
201 PermissionAction::SetDefault { behavior } => {
202 PermissionPolicyAction::SetDefault { behavior }
203 }
204 PermissionAction::SetTool { tool_id, behavior } => PermissionPolicyAction::SetTool {
205 tool_id,
206 behavior,
207 scope: PermissionRuleScope::Thread,
208 source: PermissionRuleSource::Runtime,
209 },
210 PermissionAction::RemoveTool { tool_id } => PermissionPolicyAction::RemoveTool { tool_id },
211 PermissionAction::ClearTools => PermissionPolicyAction::ClearTools,
212 };
213 AnyStateAction::new::<PermissionPolicy>(policy_action)
214}
215
216pub fn permission_override_action(
222 action: PermissionAction,
223) -> tirea_contract::runtime::state::AnyStateAction {
224 use tirea_contract::runtime::state::AnyStateAction;
225 let overrides_action = match action {
226 PermissionAction::SetTool { tool_id, behavior } => PermissionOverridesAction::SetTool {
227 tool_id,
228 behavior,
229 scope: PermissionRuleScope::Thread,
230 source: PermissionRuleSource::Skill,
231 },
232 PermissionAction::RemoveTool { tool_id } => {
233 PermissionOverridesAction::RemoveTool { tool_id }
234 }
235 PermissionAction::ClearTools => PermissionOverridesAction::Clear,
236 PermissionAction::SetDefault { behavior } => {
238 return permission_state_action(PermissionAction::SetDefault { behavior });
239 }
240 };
241 AnyStateAction::new::<PermissionOverrides>(overrides_action)
242}
243
244#[must_use]
251pub fn permission_rules_from_snapshot(snapshot: &serde_json::Value) -> PermissionRuleset {
252 let mut ruleset = PermissionRuleset::default();
253 let mut default_from_new_state = false;
254
255 if let Some(policy_value) = snapshot.get(PermissionPolicy::PATH) {
256 let prefers_legacy_shape = policy_value.get("allowed_tools").is_some()
257 || policy_value.get("denied_tools").is_some();
258 if prefers_legacy_shape {
259 if let Ok(legacy_policy) =
260 serde_json::from_value::<LegacyPermissionPolicy>(policy_value.clone())
261 {
262 default_from_new_state = true;
263 ruleset.default_behavior = legacy_policy.default_behavior;
264 for tool_id in legacy_policy.allowed_tools {
265 let rule = PermissionRule::new_tool(tool_id, ToolPermissionBehavior::Allow)
266 .with_source(PermissionRuleSource::Runtime);
267 ruleset.rules.entry(rule.subject.key()).or_insert(rule);
268 }
269 for tool_id in legacy_policy.denied_tools {
270 let rule = PermissionRule::new_tool(tool_id, ToolPermissionBehavior::Deny)
271 .with_source(PermissionRuleSource::Runtime);
272 ruleset.rules.insert(rule.subject.key(), rule);
273 }
274 }
275 } else if let Ok(policy) = PermissionPolicy::from_value(policy_value) {
276 default_from_new_state = true;
277 ruleset.default_behavior = policy.default_behavior;
278 ruleset.rules.extend(policy.rules);
279 }
280 }
281
282 if let Some(legacy_value) = snapshot.get("permissions") {
283 if let Ok(legacy) =
284 serde_json::from_value::<LegacyPermissionOverrides>(legacy_value.clone())
285 {
286 if !default_from_new_state {
287 ruleset.default_behavior = legacy.default_behavior;
288 }
289 for (tool_id, behavior) in legacy.tools {
290 let rule = PermissionRule::new_tool(tool_id, behavior)
291 .with_source(PermissionRuleSource::Runtime);
292 ruleset.rules.entry(rule.subject.key()).or_insert(rule);
293 }
294 }
295 }
296
297 if let Some(overrides_value) = snapshot.get(PermissionOverrides::PATH) {
299 if let Ok(overrides) = PermissionOverrides::from_value(overrides_value) {
300 for (key, rule) in overrides.rules {
301 ruleset.rules.insert(key, rule);
302 }
303 }
304 }
305
306 ruleset
307}