Skip to main content

ai_agent/types/
permissions.rs

1// Source: ~/claudecode/openclaudecode/src/types/permissions.ts
2
3//! Pure permission type definitions extracted to break import cycles.
4//! This file contains only type definitions and constants with no runtime dependencies.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9// ============================================================================
10// Permission Modes
11// ============================================================================
12
13/// External permission modes (user-addressable)
14pub const EXTERNAL_PERMISSION_MODES: &[&str] = &[
15    "acceptEdits",
16    "bypassPermissions",
17    "default",
18    "dontAsk",
19    "plan",
20];
21
22/// External permission mode type.
23pub type ExternalPermissionMode = String;
24
25/// Internal permission mode includes external modes plus 'auto' and 'bubble'.
26pub type InternalPermissionMode = String;
27
28/// Union of all permission modes.
29pub type PermissionMode = String;
30
31/// Runtime validation set: modes that are user-addressable.
32pub const INTERNAL_PERMISSION_MODES: &[&str] = &[
33    "acceptEdits",
34    "bypassPermissions",
35    "default",
36    "dontAsk",
37    "plan",
38    "auto",
39];
40
41/// All permission modes.
42pub const PERMISSION_MODES: &[&str] = INTERNAL_PERMISSION_MODES;
43
44// ============================================================================
45// Permission Behaviors
46// ============================================================================
47
48/// Permission behavior enum.
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
50#[serde(rename_all = "lowercase")]
51pub enum PermissionBehavior {
52    Allow,
53    Deny,
54    Ask,
55}
56
57impl PermissionBehavior {
58    pub fn as_str(&self) -> &'static str {
59        match self {
60            PermissionBehavior::Allow => "allow",
61            PermissionBehavior::Deny => "deny",
62            PermissionBehavior::Ask => "ask",
63        }
64    }
65}
66
67// ============================================================================
68// Permission Rules
69// ============================================================================
70
71/// Where a permission rule originated from.
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
73#[serde(rename_all = "camelCase")]
74pub enum PermissionRuleSource {
75    UserSettings,
76    ProjectSettings,
77    LocalSettings,
78    FlagSettings,
79    PolicySettings,
80    CliArg,
81    Command,
82    Session,
83}
84
85impl PermissionRuleSource {
86    pub fn as_str(&self) -> &'static str {
87        match self {
88            PermissionRuleSource::UserSettings => "userSettings",
89            PermissionRuleSource::ProjectSettings => "projectSettings",
90            PermissionRuleSource::LocalSettings => "localSettings",
91            PermissionRuleSource::FlagSettings => "flagSettings",
92            PermissionRuleSource::PolicySettings => "policySettings",
93            PermissionRuleSource::CliArg => "cliArg",
94            PermissionRuleSource::Command => "command",
95            PermissionRuleSource::Session => "session",
96        }
97    }
98}
99
100/// The value of a permission rule - specifies which tool and optional content.
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct PermissionRuleValue {
103    #[serde(rename = "toolName")]
104    pub tool_name: String,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    #[serde(rename = "ruleContent")]
107    pub rule_content: Option<String>,
108}
109
110/// A permission rule with its source and behavior.
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct PermissionRule {
113    pub source: PermissionRuleSource,
114    #[serde(rename = "ruleBehavior")]
115    pub rule_behavior: PermissionBehavior,
116    #[serde(rename = "ruleValue")]
117    pub rule_value: PermissionRuleValue,
118}
119
120// ============================================================================
121// Permission Updates
122// ============================================================================
123
124/// Where a permission update should be persisted.
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
126#[serde(rename_all = "camelCase")]
127pub enum PermissionUpdateDestination {
128    UserSettings,
129    ProjectSettings,
130    LocalSettings,
131    Session,
132    CliArg,
133}
134
135/// Update operations for permission configuration.
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(tag = "type")]
138pub enum PermissionUpdate {
139    #[serde(rename = "addRules")]
140    AddRules {
141        destination: PermissionUpdateDestination,
142        rules: Vec<PermissionRuleValue>,
143        behavior: PermissionBehavior,
144    },
145    #[serde(rename = "replaceRules")]
146    ReplaceRules {
147        destination: PermissionUpdateDestination,
148        rules: Vec<PermissionRuleValue>,
149        behavior: PermissionBehavior,
150    },
151    #[serde(rename = "removeRules")]
152    RemoveRules {
153        destination: PermissionUpdateDestination,
154        rules: Vec<PermissionRuleValue>,
155        behavior: PermissionBehavior,
156    },
157    #[serde(rename = "setMode")]
158    SetMode {
159        destination: PermissionUpdateDestination,
160        mode: ExternalPermissionMode,
161    },
162    #[serde(rename = "addDirectories")]
163    AddDirectories {
164        destination: PermissionUpdateDestination,
165        directories: Vec<String>,
166    },
167    #[serde(rename = "removeDirectories")]
168    RemoveDirectories {
169        destination: PermissionUpdateDestination,
170        directories: Vec<String>,
171    },
172}
173
174impl PermissionUpdate {
175    /// Return the update type name (e.g. "addRules", "setMode")
176    pub fn type_name(&self) -> &'static str {
177        match self {
178            PermissionUpdate::AddRules { .. } => "addRules",
179            PermissionUpdate::ReplaceRules { .. } => "replaceRules",
180            PermissionUpdate::RemoveRules { .. } => "removeRules",
181            PermissionUpdate::SetMode { .. } => "setMode",
182            PermissionUpdate::AddDirectories { .. } => "addDirectories",
183            PermissionUpdate::RemoveDirectories { .. } => "removeDirectories",
184        }
185    }
186}
187
188/// Source of an additional working directory permission.
189pub type WorkingDirectorySource = PermissionRuleSource;
190
191/// An additional directory included in permission scope.
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct AdditionalWorkingDirectory {
194    pub path: String,
195    pub source: WorkingDirectorySource,
196}
197
198// ============================================================================
199// Permission Decisions & Results
200// ============================================================================
201
202/// Minimal command shape for permission metadata.
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct PermissionCommandMetadata {
205    pub name: String,
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub description: Option<String>,
208    /// Allow additional properties for forward compatibility
209    #[serde(flatten)]
210    pub extra: HashMap<String, serde_json::Value>,
211}
212
213/// Metadata attached to permission decisions.
214pub type PermissionMetadata = Option<PermissionCommandMetadata>;
215
216/// Result when permission is granted.
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct PermissionAllowDecision {
219    pub behavior: String, // "allow"
220    #[serde(skip_serializing_if = "Option::is_none")]
221    #[serde(rename = "updatedInput")]
222    pub updated_input: Option<HashMap<String, serde_json::Value>>,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    #[serde(rename = "userModified")]
225    pub user_modified: Option<bool>,
226    #[serde(skip_serializing_if = "Option::is_none")]
227    #[serde(rename = "decisionReason")]
228    pub decision_reason: Option<PermissionDecisionReason>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    #[serde(rename = "toolUseID")]
231    pub tool_use_id: Option<String>,
232    #[serde(skip_serializing_if = "Option::is_none")]
233    #[serde(rename = "acceptFeedback")]
234    pub accept_feedback: Option<String>,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    #[serde(rename = "contentBlocks")]
237    pub content_blocks: Option<Vec<serde_json::Value>>,
238}
239
240/// Metadata for a pending classifier check that will run asynchronously.
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct PendingClassifierCheck {
243    pub command: String,
244    pub cwd: String,
245    pub descriptions: Vec<String>,
246}
247
248/// Result when user should be prompted.
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct PermissionAskDecision {
251    pub behavior: String, // "ask"
252    pub message: String,
253    #[serde(skip_serializing_if = "Option::is_none")]
254    #[serde(rename = "updatedInput")]
255    pub updated_input: Option<HashMap<String, serde_json::Value>>,
256    #[serde(skip_serializing_if = "Option::is_none")]
257    #[serde(rename = "decisionReason")]
258    pub decision_reason: Option<PermissionDecisionReason>,
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub suggestions: Option<Vec<PermissionUpdate>>,
261    #[serde(skip_serializing_if = "Option::is_none")]
262    #[serde(rename = "blockedPath")]
263    pub blocked_path: Option<String>,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub metadata: Option<PermissionMetadata>,
266    /// If true, triggered by a bashCommandIsSafe security check
267    #[serde(skip_serializing_if = "Option::is_none")]
268    #[serde(rename = "isBashSecurityCheckForMisparsing")]
269    pub is_bash_security_check_for_misparsing: Option<bool>,
270    /// If set, an allow classifier check should be run asynchronously
271    #[serde(skip_serializing_if = "Option::is_none")]
272    #[serde(rename = "pendingClassifierCheck")]
273    pub pending_classifier_check: Option<PendingClassifierCheck>,
274    /// Optional content blocks (e.g., images) to include
275    #[serde(skip_serializing_if = "Option::is_none")]
276    #[serde(rename = "contentBlocks")]
277    pub content_blocks: Option<Vec<serde_json::Value>>,
278}
279
280/// Result when permission is denied.
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct PermissionDenyDecision {
283    pub behavior: String, // "deny"
284    pub message: String,
285    #[serde(rename = "decisionReason")]
286    pub decision_reason: PermissionDecisionReason,
287    #[serde(skip_serializing_if = "Option::is_none")]
288    #[serde(rename = "toolUseID")]
289    pub tool_use_id: Option<String>,
290}
291
292/// A permission decision - allow, ask, or deny.
293#[derive(Debug, Clone, Serialize, Deserialize)]
294#[serde(tag = "behavior")]
295pub enum PermissionDecision {
296    #[serde(rename = "allow")]
297    Allow(PermissionAllowDecision),
298    #[serde(rename = "ask")]
299    Ask(PermissionAskDecision),
300    #[serde(rename = "deny")]
301    Deny(PermissionDenyDecision),
302}
303
304/// Permission result with additional passthrough option.
305#[derive(Debug, Clone, Serialize, Deserialize)]
306#[serde(tag = "behavior")]
307pub enum PermissionResult {
308    #[serde(rename = "allow")]
309    Allow(PermissionAllowDecision),
310    #[serde(rename = "ask")]
311    Ask(PermissionAskDecision),
312    #[serde(rename = "deny")]
313    Deny(PermissionDenyDecision),
314    #[serde(rename = "passthrough")]
315    Passthrough {
316        message: String,
317        #[serde(skip_serializing_if = "Option::is_none")]
318        #[serde(rename = "decisionReason")]
319        decision_reason: Option<serde_json::Value>,
320        #[serde(skip_serializing_if = "Option::is_none")]
321        suggestions: Option<Vec<PermissionUpdate>>,
322        #[serde(skip_serializing_if = "Option::is_none")]
323        #[serde(rename = "blockedPath")]
324        blocked_path: Option<String>,
325        #[serde(skip_serializing_if = "Option::is_none")]
326        #[serde(rename = "pendingClassifierCheck")]
327        pending_classifier_check: Option<PendingClassifierCheck>,
328    },
329}
330
331/// Explanation of why a permission decision was made.
332#[derive(Debug, Clone, Serialize, Deserialize)]
333#[serde(tag = "type")]
334pub enum PermissionDecisionReason {
335    #[serde(rename = "rule")]
336    Rule { rule: PermissionRule },
337    #[serde(rename = "mode")]
338    Mode { mode: PermissionMode },
339    #[serde(rename = "subcommandResults")]
340    SubcommandResults {
341        reasons: HashMap<String, PermissionResult>,
342    },
343    #[serde(rename = "permissionPromptTool")]
344    PermissionPromptTool {
345        #[serde(rename = "permissionPromptToolName")]
346        permission_prompt_tool_name: String,
347        #[serde(rename = "toolResult")]
348        tool_result: serde_json::Value,
349    },
350    #[serde(rename = "hook")]
351    Hook {
352        #[serde(rename = "hookName")]
353        hook_name: String,
354        #[serde(skip_serializing_if = "Option::is_none")]
355        #[serde(rename = "hookSource")]
356        hook_source: Option<String>,
357        #[serde(skip_serializing_if = "Option::is_none")]
358        reason: Option<String>,
359    },
360    #[serde(rename = "asyncAgent")]
361    AsyncAgent { reason: String },
362    #[serde(rename = "sandboxOverride")]
363    SandboxOverride { reason: SandboxOverrideReason },
364    #[serde(rename = "classifier")]
365    Classifier { classifier: String, reason: String },
366    #[serde(rename = "workingDir")]
367    WorkingDir { reason: String },
368    #[serde(rename = "safetyCheck")]
369    SafetyCheck {
370        reason: String,
371        /// When true, auto mode lets the classifier evaluate this instead of forcing a prompt
372        #[serde(rename = "classifierApprovable")]
373        classifier_approvable: bool,
374    },
375    #[serde(rename = "other")]
376    Other { reason: String },
377}
378
379/// Sandbox override reason.
380#[derive(Debug, Clone, Serialize, Deserialize)]
381#[serde(rename_all = "camelCase")]
382pub enum SandboxOverrideReason {
383    ExcludedCommand,
384    DangerouslyDisableSandbox,
385}
386
387// ============================================================================
388// Bash Classifier Types
389// ============================================================================
390
391/// Classifier result.
392#[derive(Debug, Clone, Serialize, Deserialize)]
393pub struct ClassifierResult {
394    pub matches: bool,
395    #[serde(skip_serializing_if = "Option::is_none")]
396    #[serde(rename = "matchedDescription")]
397    pub matched_description: Option<String>,
398    pub confidence: ClassifierConfidence,
399    pub reason: String,
400}
401
402/// Classifier confidence level.
403#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
404#[serde(rename_all = "lowercase")]
405pub enum ClassifierConfidence {
406    High,
407    Medium,
408    Low,
409}
410
411/// Classifier behavior.
412#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
413#[serde(rename_all = "lowercase")]
414pub enum ClassifierBehavior {
415    Deny,
416    Ask,
417    Allow,
418}
419
420/// Classifier token usage.
421#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct ClassifierUsage {
423    #[serde(rename = "inputTokens")]
424    pub input_tokens: i64,
425    #[serde(rename = "outputTokens")]
426    pub output_tokens: i64,
427    #[serde(rename = "cacheReadInputTokens")]
428    pub cache_read_input_tokens: i64,
429    #[serde(rename = "cacheCreationInputTokens")]
430    pub cache_creation_input_tokens: i64,
431}
432
433/// YOLO classifier result.
434#[derive(Debug, Clone, Serialize, Deserialize)]
435pub struct YoloClassifierResult {
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub thinking: Option<String>,
438    #[serde(rename = "shouldBlock")]
439    pub should_block: bool,
440    pub reason: String,
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub unavailable: Option<bool>,
443    /// API returned "prompt is too long"
444    #[serde(skip_serializing_if = "Option::is_none")]
445    #[serde(rename = "transcriptTooLong")]
446    pub transcript_too_long: Option<bool>,
447    /// The model used for this classifier call
448    pub model: String,
449    /// Token usage from the classifier API call
450    #[serde(skip_serializing_if = "Option::is_none")]
451    pub usage: Option<ClassifierUsage>,
452    /// Duration of the classifier API call in ms
453    #[serde(skip_serializing_if = "Option::is_none")]
454    #[serde(rename = "durationMs")]
455    pub duration_ms: Option<u64>,
456    /// Character lengths of the prompt components sent to the classifier
457    #[serde(skip_serializing_if = "Option::is_none")]
458    #[serde(rename = "promptLengths")]
459    pub prompt_lengths: Option<ClassifierPromptLengths>,
460    /// Path where error prompts were dumped
461    #[serde(skip_serializing_if = "Option::is_none")]
462    #[serde(rename = "errorDumpPath")]
463    pub error_dump_path: Option<String>,
464    /// Which classifier stage produced the final decision
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub stage: Option<ClassifierStage>,
467    /// Token usage from stage 1 when stage 2 was also run
468    #[serde(skip_serializing_if = "Option::is_none")]
469    #[serde(rename = "stage1Usage")]
470    pub stage1_usage: Option<ClassifierUsage>,
471    /// Duration of stage 1 in ms when stage 2 was also run
472    #[serde(skip_serializing_if = "Option::is_none")]
473    #[serde(rename = "stage1DurationMs")]
474    pub stage1_duration_ms: Option<u64>,
475    /// API request_id for stage 1
476    #[serde(skip_serializing_if = "Option::is_none")]
477    #[serde(rename = "stage1RequestId")]
478    pub stage1_request_id: Option<String>,
479    /// API message id for stage 1
480    #[serde(skip_serializing_if = "Option::is_none")]
481    #[serde(rename = "stage1MsgId")]
482    pub stage1_msg_id: Option<String>,
483    /// Token usage from stage 2 when stage 2 was run
484    #[serde(skip_serializing_if = "Option::is_none")]
485    #[serde(rename = "stage2Usage")]
486    pub stage2_usage: Option<ClassifierUsage>,
487    /// Duration of stage 2 in ms when stage 2 was run
488    #[serde(skip_serializing_if = "Option::is_none")]
489    #[serde(rename = "stage2DurationMs")]
490    pub stage2_duration_ms: Option<u64>,
491    /// API request_id for stage 2
492    #[serde(skip_serializing_if = "Option::is_none")]
493    #[serde(rename = "stage2RequestId")]
494    pub stage2_request_id: Option<String>,
495    /// API message id for stage 2
496    #[serde(skip_serializing_if = "Option::is_none")]
497    #[serde(rename = "stage2MsgId")]
498    pub stage2_msg_id: Option<String>,
499}
500
501/// Classifier stage enum.
502#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
503#[serde(rename_all = "lowercase")]
504pub enum ClassifierStage {
505    Fast,
506    Thinking,
507}
508
509/// Character lengths of prompt components.
510#[derive(Debug, Clone, Serialize, Deserialize)]
511pub struct ClassifierPromptLengths {
512    #[serde(rename = "systemPrompt")]
513    pub system_prompt: usize,
514    #[serde(rename = "toolCalls")]
515    pub tool_calls: usize,
516    #[serde(rename = "userPrompts")]
517    pub user_prompts: usize,
518}
519
520// ============================================================================
521// Permission Explainer Types
522// ============================================================================
523
524/// Risk level enum.
525#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
526#[serde(rename_all = "UPPERCASE")]
527pub enum RiskLevel {
528    Low,
529    Medium,
530    High,
531}
532
533/// Permission explanation.
534#[derive(Debug, Clone, Serialize, Deserialize)]
535pub struct PermissionExplanation {
536    #[serde(rename = "riskLevel")]
537    pub risk_level: RiskLevel,
538    pub explanation: String,
539    pub reasoning: String,
540    pub risk: String,
541}
542
543// ============================================================================
544// Tool Permission Context
545// ============================================================================
546
547/// Mapping of permission rules by their source.
548#[derive(Debug, Clone, Serialize, Deserialize)]
549pub struct ToolPermissionRulesBySource {
550    #[serde(skip_serializing_if = "Option::is_none")]
551    #[serde(rename = "userSettings")]
552    pub user_settings: Option<Vec<String>>,
553    #[serde(skip_serializing_if = "Option::is_none")]
554    #[serde(rename = "projectSettings")]
555    pub project_settings: Option<Vec<String>>,
556    #[serde(skip_serializing_if = "Option::is_none")]
557    #[serde(rename = "localSettings")]
558    pub local_settings: Option<Vec<String>>,
559    #[serde(skip_serializing_if = "Option::is_none")]
560    #[serde(rename = "flagSettings")]
561    pub flag_settings: Option<Vec<String>>,
562    #[serde(skip_serializing_if = "Option::is_none")]
563    #[serde(rename = "policySettings")]
564    pub policy_settings: Option<Vec<String>>,
565    #[serde(skip_serializing_if = "Option::is_none")]
566    #[serde(rename = "cliArg")]
567    pub cli_arg: Option<Vec<String>>,
568    #[serde(skip_serializing_if = "Option::is_none")]
569    pub command: Option<Vec<String>>,
570    #[serde(skip_serializing_if = "Option::is_none")]
571    pub session: Option<Vec<String>>,
572}
573
574/// Context needed for permission checking in tools.
575#[derive(Debug, Clone, Serialize, Deserialize)]
576pub struct ToolPermissionContext {
577    pub mode: PermissionMode,
578    #[serde(rename = "additionalWorkingDirectories")]
579    pub additional_working_directories: HashMap<String, AdditionalWorkingDirectory>,
580    #[serde(rename = "alwaysAllowRules")]
581    pub always_allow_rules: ToolPermissionRulesBySource,
582    #[serde(rename = "alwaysDenyRules")]
583    pub always_deny_rules: ToolPermissionRulesBySource,
584    #[serde(rename = "alwaysAskRules")]
585    pub always_ask_rules: ToolPermissionRulesBySource,
586    #[serde(rename = "isBypassPermissionsModeAvailable")]
587    pub is_bypass_permissions_mode_available: bool,
588    #[serde(skip_serializing_if = "Option::is_none")]
589    #[serde(rename = "strippedDangerousRules")]
590    pub stripped_dangerous_rules: Option<ToolPermissionRulesBySource>,
591    #[serde(skip_serializing_if = "Option::is_none")]
592    #[serde(rename = "shouldAvoidPermissionPrompts")]
593    pub should_avoid_permission_prompts: Option<bool>,
594    #[serde(skip_serializing_if = "Option::is_none")]
595    #[serde(rename = "awaitAutomatedChecksBeforeDialog")]
596    pub await_automated_checks_before_dialog: Option<bool>,
597    #[serde(skip_serializing_if = "Option::is_none")]
598    #[serde(rename = "prePlanMode")]
599    pub pre_plan_mode: Option<PermissionMode>,
600}