forked from
oppi.li/claude-code
source dump of claude code
1/**
2 * Pure permission type definitions extracted to break import cycles.
3 *
4 * This file contains only type definitions and constants with no runtime dependencies.
5 * Implementation files remain in src/utils/permissions/ but can now import from here
6 * to avoid circular dependencies.
7 */
8
9import { feature } from 'bun:bundle'
10import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs'
11
12// ============================================================================
13// Permission Modes
14// ============================================================================
15
16export const EXTERNAL_PERMISSION_MODES = [
17 'acceptEdits',
18 'bypassPermissions',
19 'default',
20 'dontAsk',
21 'plan',
22] as const
23
24export type ExternalPermissionMode = (typeof EXTERNAL_PERMISSION_MODES)[number]
25
26// Exhaustive mode union for typechecking. The user-addressable runtime set
27// is INTERNAL_PERMISSION_MODES below.
28export type InternalPermissionMode = ExternalPermissionMode | 'auto' | 'bubble'
29export type PermissionMode = InternalPermissionMode
30
31// Runtime validation set: modes that are user-addressable (settings.json
32// defaultMode, --permission-mode CLI flag, conversation recovery).
33export const INTERNAL_PERMISSION_MODES = [
34 ...EXTERNAL_PERMISSION_MODES,
35 ...(feature('TRANSCRIPT_CLASSIFIER') ? (['auto'] as const) : ([] as const)),
36] as const satisfies readonly PermissionMode[]
37
38export const PERMISSION_MODES = INTERNAL_PERMISSION_MODES
39
40// ============================================================================
41// Permission Behaviors
42// ============================================================================
43
44export type PermissionBehavior = 'allow' | 'deny' | 'ask'
45
46// ============================================================================
47// Permission Rules
48// ============================================================================
49
50/**
51 * Where a permission rule originated from.
52 * Includes all SettingSource values plus additional rule-specific sources.
53 */
54export type PermissionRuleSource =
55 | 'userSettings'
56 | 'projectSettings'
57 | 'localSettings'
58 | 'flagSettings'
59 | 'policySettings'
60 | 'cliArg'
61 | 'command'
62 | 'session'
63
64/**
65 * The value of a permission rule - specifies which tool and optional content
66 */
67export type PermissionRuleValue = {
68 toolName: string
69 ruleContent?: string
70}
71
72/**
73 * A permission rule with its source and behavior
74 */
75export type PermissionRule = {
76 source: PermissionRuleSource
77 ruleBehavior: PermissionBehavior
78 ruleValue: PermissionRuleValue
79}
80
81// ============================================================================
82// Permission Updates
83// ============================================================================
84
85/**
86 * Where a permission update should be persisted
87 */
88export type PermissionUpdateDestination =
89 | 'userSettings'
90 | 'projectSettings'
91 | 'localSettings'
92 | 'session'
93 | 'cliArg'
94
95/**
96 * Update operations for permission configuration
97 */
98export type PermissionUpdate =
99 | {
100 type: 'addRules'
101 destination: PermissionUpdateDestination
102 rules: PermissionRuleValue[]
103 behavior: PermissionBehavior
104 }
105 | {
106 type: 'replaceRules'
107 destination: PermissionUpdateDestination
108 rules: PermissionRuleValue[]
109 behavior: PermissionBehavior
110 }
111 | {
112 type: 'removeRules'
113 destination: PermissionUpdateDestination
114 rules: PermissionRuleValue[]
115 behavior: PermissionBehavior
116 }
117 | {
118 type: 'setMode'
119 destination: PermissionUpdateDestination
120 mode: ExternalPermissionMode
121 }
122 | {
123 type: 'addDirectories'
124 destination: PermissionUpdateDestination
125 directories: string[]
126 }
127 | {
128 type: 'removeDirectories'
129 destination: PermissionUpdateDestination
130 directories: string[]
131 }
132
133/**
134 * Source of an additional working directory permission.
135 * Note: This is currently the same as PermissionRuleSource but kept as a
136 * separate type for semantic clarity and potential future divergence.
137 */
138export type WorkingDirectorySource = PermissionRuleSource
139
140/**
141 * An additional directory included in permission scope
142 */
143export type AdditionalWorkingDirectory = {
144 path: string
145 source: WorkingDirectorySource
146}
147
148// ============================================================================
149// Permission Decisions & Results
150// ============================================================================
151
152/**
153 * Minimal command shape for permission metadata.
154 * This is intentionally a subset of the full Command type to avoid import cycles.
155 * Only includes properties needed by permission-related components.
156 */
157export type PermissionCommandMetadata = {
158 name: string
159 description?: string
160 // Allow additional properties for forward compatibility
161 [key: string]: unknown
162}
163
164/**
165 * Metadata attached to permission decisions
166 */
167export type PermissionMetadata =
168 | { command: PermissionCommandMetadata }
169 | undefined
170
171/**
172 * Result when permission is granted
173 */
174export type PermissionAllowDecision<
175 Input extends { [key: string]: unknown } = { [key: string]: unknown },
176> = {
177 behavior: 'allow'
178 updatedInput?: Input
179 userModified?: boolean
180 decisionReason?: PermissionDecisionReason
181 toolUseID?: string
182 acceptFeedback?: string
183 contentBlocks?: ContentBlockParam[]
184}
185
186/**
187 * Metadata for a pending classifier check that will run asynchronously.
188 * Used to enable non-blocking allow classifier evaluation.
189 */
190export type PendingClassifierCheck = {
191 command: string
192 cwd: string
193 descriptions: string[]
194}
195
196/**
197 * Result when user should be prompted
198 */
199export type PermissionAskDecision<
200 Input extends { [key: string]: unknown } = { [key: string]: unknown },
201> = {
202 behavior: 'ask'
203 message: string
204 updatedInput?: Input
205 decisionReason?: PermissionDecisionReason
206 suggestions?: PermissionUpdate[]
207 blockedPath?: string
208 metadata?: PermissionMetadata
209 /**
210 * If true, this ask decision was triggered by a bashCommandIsSafe_DEPRECATED security check
211 * for patterns that splitCommand_DEPRECATED could misparse (e.g. line continuations, shell-quote
212 * transformations). Used by bashToolHasPermission to block early before splitCommand_DEPRECATED
213 * transforms the command. Not set for simple newline compound commands.
214 */
215 isBashSecurityCheckForMisparsing?: boolean
216 /**
217 * If set, an allow classifier check should be run asynchronously.
218 * The classifier may auto-approve the permission before the user responds.
219 */
220 pendingClassifierCheck?: PendingClassifierCheck
221 /**
222 * Optional content blocks (e.g., images) to include alongside the rejection
223 * message in the tool result. Used when users paste images as feedback.
224 */
225 contentBlocks?: ContentBlockParam[]
226}
227
228/**
229 * Result when permission is denied
230 */
231export type PermissionDenyDecision = {
232 behavior: 'deny'
233 message: string
234 decisionReason: PermissionDecisionReason
235 toolUseID?: string
236}
237
238/**
239 * A permission decision - allow, ask, or deny
240 */
241export type PermissionDecision<
242 Input extends { [key: string]: unknown } = { [key: string]: unknown },
243> =
244 | PermissionAllowDecision<Input>
245 | PermissionAskDecision<Input>
246 | PermissionDenyDecision
247
248/**
249 * Permission result with additional passthrough option
250 */
251export type PermissionResult<
252 Input extends { [key: string]: unknown } = { [key: string]: unknown },
253> =
254 | PermissionDecision<Input>
255 | {
256 behavior: 'passthrough'
257 message: string
258 decisionReason?: PermissionDecision<Input>['decisionReason']
259 suggestions?: PermissionUpdate[]
260 blockedPath?: string
261 /**
262 * If set, an allow classifier check should be run asynchronously.
263 * The classifier may auto-approve the permission before the user responds.
264 */
265 pendingClassifierCheck?: PendingClassifierCheck
266 }
267
268/**
269 * Explanation of why a permission decision was made
270 */
271export type PermissionDecisionReason =
272 | {
273 type: 'rule'
274 rule: PermissionRule
275 }
276 | {
277 type: 'mode'
278 mode: PermissionMode
279 }
280 | {
281 type: 'subcommandResults'
282 reasons: Map<string, PermissionResult>
283 }
284 | {
285 type: 'permissionPromptTool'
286 permissionPromptToolName: string
287 toolResult: unknown
288 }
289 | {
290 type: 'hook'
291 hookName: string
292 hookSource?: string
293 reason?: string
294 }
295 | {
296 type: 'asyncAgent'
297 reason: string
298 }
299 | {
300 type: 'sandboxOverride'
301 reason: 'excludedCommand' | 'dangerouslyDisableSandbox'
302 }
303 | {
304 type: 'classifier'
305 classifier: string
306 reason: string
307 }
308 | {
309 type: 'workingDir'
310 reason: string
311 }
312 | {
313 type: 'safetyCheck'
314 reason: string
315 // When true, auto mode lets the classifier evaluate this instead of
316 // forcing a prompt. True for sensitive-file paths (.claude/, .git/,
317 // shell configs) — the classifier can see context and decide. False
318 // for Windows path bypass attempts and cross-machine bridge messages.
319 classifierApprovable: boolean
320 }
321 | {
322 type: 'other'
323 reason: string
324 }
325
326// ============================================================================
327// Bash Classifier Types
328// ============================================================================
329
330export type ClassifierResult = {
331 matches: boolean
332 matchedDescription?: string
333 confidence: 'high' | 'medium' | 'low'
334 reason: string
335}
336
337export type ClassifierBehavior = 'deny' | 'ask' | 'allow'
338
339export type ClassifierUsage = {
340 inputTokens: number
341 outputTokens: number
342 cacheReadInputTokens: number
343 cacheCreationInputTokens: number
344}
345
346export type YoloClassifierResult = {
347 thinking?: string
348 shouldBlock: boolean
349 reason: string
350 unavailable?: boolean
351 /**
352 * API returned "prompt is too long" — the classifier transcript exceeded
353 * the context window. Deterministic (same transcript → same error), so
354 * callers should fall back to normal prompting rather than retry/fail-closed.
355 */
356 transcriptTooLong?: boolean
357 /** The model used for this classifier call */
358 model: string
359 /** Token usage from the classifier API call (for overhead telemetry) */
360 usage?: ClassifierUsage
361 /** Duration of the classifier API call in ms */
362 durationMs?: number
363 /** Character lengths of the prompt components sent to the classifier */
364 promptLengths?: {
365 systemPrompt: number
366 toolCalls: number
367 userPrompts: number
368 }
369 /** Path where error prompts were dumped (only set when unavailable due to API error) */
370 errorDumpPath?: string
371 /** Which classifier stage produced the final decision (2-stage XML only) */
372 stage?: 'fast' | 'thinking'
373 /** Token usage from stage 1 (fast) when stage 2 was also run */
374 stage1Usage?: ClassifierUsage
375 /** Duration of stage 1 in ms when stage 2 was also run */
376 stage1DurationMs?: number
377 /**
378 * API request_id (req_xxx) for stage 1. Enables joining to server-side
379 * api_usage logs for cache-miss / routing attribution. Also used for the
380 * legacy 1-stage (tool_use) classifier — the single request goes here.
381 */
382 stage1RequestId?: string
383 /**
384 * API message id (msg_xxx) for stage 1. Enables joining the
385 * tengu_auto_mode_decision analytics event to the classifier's actual
386 * prompt/completion in post-analysis.
387 */
388 stage1MsgId?: string
389 /** Token usage from stage 2 (thinking) when stage 2 was run */
390 stage2Usage?: ClassifierUsage
391 /** Duration of stage 2 in ms when stage 2 was run */
392 stage2DurationMs?: number
393 /** API request_id for stage 2 (set whenever stage 2 ran) */
394 stage2RequestId?: string
395 /** API message id (msg_xxx) for stage 2 (set whenever stage 2 ran) */
396 stage2MsgId?: string
397}
398
399// ============================================================================
400// Permission Explainer Types
401// ============================================================================
402
403export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH'
404
405export type PermissionExplanation = {
406 riskLevel: RiskLevel
407 explanation: string
408 reasoning: string
409 risk: string
410}
411
412// ============================================================================
413// Tool Permission Context
414// ============================================================================
415
416/**
417 * Mapping of permission rules by their source
418 */
419export type ToolPermissionRulesBySource = {
420 [T in PermissionRuleSource]?: string[]
421}
422
423/**
424 * Context needed for permission checking in tools
425 * Note: Uses a simplified DeepImmutable approximation for this types-only file
426 */
427export type ToolPermissionContext = {
428 readonly mode: PermissionMode
429 readonly additionalWorkingDirectories: ReadonlyMap<
430 string,
431 AdditionalWorkingDirectory
432 >
433 readonly alwaysAllowRules: ToolPermissionRulesBySource
434 readonly alwaysDenyRules: ToolPermissionRulesBySource
435 readonly alwaysAskRules: ToolPermissionRulesBySource
436 readonly isBypassPermissionsModeAvailable: boolean
437 readonly strippedDangerousRules?: ToolPermissionRulesBySource
438 readonly shouldAvoidPermissionPrompts?: boolean
439 readonly awaitAutomatedChecksBeforeDialog?: boolean
440 readonly prePlanMode?: PermissionMode
441}