source dump of claude code
at main 290 lines 9.1 kB view raw
1// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered 2import { z } from 'zod/v4' 3import { lazySchema } from '../utils/lazySchema.js' 4import { 5 type HookEvent, 6 HOOK_EVENTS, 7 type HookInput, 8 type PermissionUpdate, 9} from 'src/entrypoints/agentSdkTypes.js' 10import type { 11 HookJSONOutput, 12 AsyncHookJSONOutput, 13 SyncHookJSONOutput, 14} from 'src/entrypoints/agentSdkTypes.js' 15import type { Message } from 'src/types/message.js' 16import type { PermissionResult } from 'src/utils/permissions/PermissionResult.js' 17import { permissionBehaviorSchema } from 'src/utils/permissions/PermissionRule.js' 18import { permissionUpdateSchema } from 'src/utils/permissions/PermissionUpdateSchema.js' 19import type { AppState } from '../state/AppState.js' 20import type { AttributionState } from '../utils/commitAttribution.js' 21 22export function isHookEvent(value: string): value is HookEvent { 23 return HOOK_EVENTS.includes(value as HookEvent) 24} 25 26// Prompt elicitation protocol types. The `prompt` key acts as discriminator 27// (mirroring the {async:true} pattern), with the id as its value. 28export const promptRequestSchema = lazySchema(() => 29 z.object({ 30 prompt: z.string(), // request id 31 message: z.string(), 32 options: z.array( 33 z.object({ 34 key: z.string(), 35 label: z.string(), 36 description: z.string().optional(), 37 }), 38 ), 39 }), 40) 41 42export type PromptRequest = z.infer<ReturnType<typeof promptRequestSchema>> 43 44export type PromptResponse = { 45 prompt_response: string // request id 46 selected: string 47} 48 49// Sync hook response schema 50export const syncHookResponseSchema = lazySchema(() => 51 z.object({ 52 continue: z 53 .boolean() 54 .describe('Whether Claude should continue after hook (default: true)') 55 .optional(), 56 suppressOutput: z 57 .boolean() 58 .describe('Hide stdout from transcript (default: false)') 59 .optional(), 60 stopReason: z 61 .string() 62 .describe('Message shown when continue is false') 63 .optional(), 64 decision: z.enum(['approve', 'block']).optional(), 65 reason: z.string().describe('Explanation for the decision').optional(), 66 systemMessage: z 67 .string() 68 .describe('Warning message shown to the user') 69 .optional(), 70 hookSpecificOutput: z 71 .union([ 72 z.object({ 73 hookEventName: z.literal('PreToolUse'), 74 permissionDecision: permissionBehaviorSchema().optional(), 75 permissionDecisionReason: z.string().optional(), 76 updatedInput: z.record(z.string(), z.unknown()).optional(), 77 additionalContext: z.string().optional(), 78 }), 79 z.object({ 80 hookEventName: z.literal('UserPromptSubmit'), 81 additionalContext: z.string().optional(), 82 }), 83 z.object({ 84 hookEventName: z.literal('SessionStart'), 85 additionalContext: z.string().optional(), 86 initialUserMessage: z.string().optional(), 87 watchPaths: z 88 .array(z.string()) 89 .describe('Absolute paths to watch for FileChanged hooks') 90 .optional(), 91 }), 92 z.object({ 93 hookEventName: z.literal('Setup'), 94 additionalContext: z.string().optional(), 95 }), 96 z.object({ 97 hookEventName: z.literal('SubagentStart'), 98 additionalContext: z.string().optional(), 99 }), 100 z.object({ 101 hookEventName: z.literal('PostToolUse'), 102 additionalContext: z.string().optional(), 103 updatedMCPToolOutput: z 104 .unknown() 105 .describe('Updates the output for MCP tools') 106 .optional(), 107 }), 108 z.object({ 109 hookEventName: z.literal('PostToolUseFailure'), 110 additionalContext: z.string().optional(), 111 }), 112 z.object({ 113 hookEventName: z.literal('PermissionDenied'), 114 retry: z.boolean().optional(), 115 }), 116 z.object({ 117 hookEventName: z.literal('Notification'), 118 additionalContext: z.string().optional(), 119 }), 120 z.object({ 121 hookEventName: z.literal('PermissionRequest'), 122 decision: z.union([ 123 z.object({ 124 behavior: z.literal('allow'), 125 updatedInput: z.record(z.string(), z.unknown()).optional(), 126 updatedPermissions: z.array(permissionUpdateSchema()).optional(), 127 }), 128 z.object({ 129 behavior: z.literal('deny'), 130 message: z.string().optional(), 131 interrupt: z.boolean().optional(), 132 }), 133 ]), 134 }), 135 z.object({ 136 hookEventName: z.literal('Elicitation'), 137 action: z.enum(['accept', 'decline', 'cancel']).optional(), 138 content: z.record(z.string(), z.unknown()).optional(), 139 }), 140 z.object({ 141 hookEventName: z.literal('ElicitationResult'), 142 action: z.enum(['accept', 'decline', 'cancel']).optional(), 143 content: z.record(z.string(), z.unknown()).optional(), 144 }), 145 z.object({ 146 hookEventName: z.literal('CwdChanged'), 147 watchPaths: z 148 .array(z.string()) 149 .describe('Absolute paths to watch for FileChanged hooks') 150 .optional(), 151 }), 152 z.object({ 153 hookEventName: z.literal('FileChanged'), 154 watchPaths: z 155 .array(z.string()) 156 .describe('Absolute paths to watch for FileChanged hooks') 157 .optional(), 158 }), 159 z.object({ 160 hookEventName: z.literal('WorktreeCreate'), 161 worktreePath: z.string(), 162 }), 163 ]) 164 .optional(), 165 }), 166) 167 168// Zod schema for hook JSON output validation 169export const hookJSONOutputSchema = lazySchema(() => { 170 // Async hook response schema 171 const asyncHookResponseSchema = z.object({ 172 async: z.literal(true), 173 asyncTimeout: z.number().optional(), 174 }) 175 return z.union([asyncHookResponseSchema, syncHookResponseSchema()]) 176}) 177 178// Infer the TypeScript type from the schema 179type SchemaHookJSONOutput = z.infer<ReturnType<typeof hookJSONOutputSchema>> 180 181// Type guard function to check if response is sync 182export function isSyncHookJSONOutput( 183 json: HookJSONOutput, 184): json is SyncHookJSONOutput { 185 return !('async' in json && json.async === true) 186} 187 188// Type guard function to check if response is async 189export function isAsyncHookJSONOutput( 190 json: HookJSONOutput, 191): json is AsyncHookJSONOutput { 192 return 'async' in json && json.async === true 193} 194 195// Compile-time assertion that SDK and Zod types match 196import type { IsEqual } from 'type-fest' 197type Assert<T extends true> = T 198type _assertSDKTypesMatch = Assert< 199 IsEqual<SchemaHookJSONOutput, HookJSONOutput> 200> 201 202/** Context passed to callback hooks for state access */ 203export type HookCallbackContext = { 204 getAppState: () => AppState 205 updateAttributionState: ( 206 updater: (prev: AttributionState) => AttributionState, 207 ) => void 208} 209 210/** Hook that is a callback. */ 211export type HookCallback = { 212 type: 'callback' 213 callback: ( 214 input: HookInput, 215 toolUseID: string | null, 216 abort: AbortSignal | undefined, 217 /** Hook index for SessionStart hooks to compute CLAUDE_ENV_FILE path */ 218 hookIndex?: number, 219 /** Optional context for accessing app state */ 220 context?: HookCallbackContext, 221 ) => Promise<HookJSONOutput> 222 /** Timeout in seconds for this hook */ 223 timeout?: number 224 /** Internal hooks (e.g. session file access analytics) are excluded from tengu_run_hook metrics */ 225 internal?: boolean 226} 227 228export type HookCallbackMatcher = { 229 matcher?: string 230 hooks: HookCallback[] 231 pluginName?: string 232} 233 234export type HookProgress = { 235 type: 'hook_progress' 236 hookEvent: HookEvent 237 hookName: string 238 command: string 239 promptText?: string 240 statusMessage?: string 241} 242 243export type HookBlockingError = { 244 blockingError: string 245 command: string 246} 247 248export type PermissionRequestResult = 249 | { 250 behavior: 'allow' 251 updatedInput?: Record<string, unknown> 252 updatedPermissions?: PermissionUpdate[] 253 } 254 | { 255 behavior: 'deny' 256 message?: string 257 interrupt?: boolean 258 } 259 260export type HookResult = { 261 message?: Message 262 systemMessage?: Message 263 blockingError?: HookBlockingError 264 outcome: 'success' | 'blocking' | 'non_blocking_error' | 'cancelled' 265 preventContinuation?: boolean 266 stopReason?: string 267 permissionBehavior?: 'ask' | 'deny' | 'allow' | 'passthrough' 268 hookPermissionDecisionReason?: string 269 additionalContext?: string 270 initialUserMessage?: string 271 updatedInput?: Record<string, unknown> 272 updatedMCPToolOutput?: unknown 273 permissionRequestResult?: PermissionRequestResult 274 retry?: boolean 275} 276 277export type AggregatedHookResult = { 278 message?: Message 279 blockingErrors?: HookBlockingError[] 280 preventContinuation?: boolean 281 stopReason?: string 282 hookPermissionDecisionReason?: string 283 permissionBehavior?: PermissionResult['behavior'] 284 additionalContexts?: string[] 285 initialUserMessage?: string 286 updatedInput?: Record<string, unknown> 287 updatedMCPToolOutput?: unknown 288 permissionRequestResult?: PermissionRequestResult 289 retry?: boolean 290}