| |
| import { z } from 'zod/v4' |
| import { lazySchema } from '../utils/lazySchema.js' |
| import { |
| type HookEvent, |
| HOOK_EVENTS, |
| type HookInput, |
| type PermissionUpdate, |
| } from 'src/entrypoints/agentSdkTypes.js' |
| import type { |
| HookJSONOutput, |
| AsyncHookJSONOutput, |
| SyncHookJSONOutput, |
| } from 'src/entrypoints/agentSdkTypes.js' |
| import type { Message } from 'src/types/message.js' |
| import type { PermissionResult } from 'src/utils/permissions/PermissionResult.js' |
| import { permissionBehaviorSchema } from 'src/utils/permissions/PermissionRule.js' |
| import { permissionUpdateSchema } from 'src/utils/permissions/PermissionUpdateSchema.js' |
| import type { AppState } from '../state/AppState.js' |
| import type { AttributionState } from '../utils/commitAttribution.js' |
|
|
| export function isHookEvent(value: string): value is HookEvent { |
| return HOOK_EVENTS.includes(value as HookEvent) |
| } |
|
|
| |
| |
| export const promptRequestSchema = lazySchema(() => |
| z.object({ |
| prompt: z.string(), |
| message: z.string(), |
| options: z.array( |
| z.object({ |
| key: z.string(), |
| label: z.string(), |
| description: z.string().optional(), |
| }), |
| ), |
| }), |
| ) |
|
|
| export type PromptRequest = z.infer<ReturnType<typeof promptRequestSchema>> |
|
|
| export type PromptResponse = { |
| prompt_response: string |
| selected: string |
| } |
|
|
| |
| export const syncHookResponseSchema = lazySchema(() => |
| z.object({ |
| continue: z |
| .boolean() |
| .describe('Whether Claude should continue after hook (default: true)') |
| .optional(), |
| suppressOutput: z |
| .boolean() |
| .describe('Hide stdout from transcript (default: false)') |
| .optional(), |
| stopReason: z |
| .string() |
| .describe('Message shown when continue is false') |
| .optional(), |
| decision: z.enum(['approve', 'block']).optional(), |
| reason: z.string().describe('Explanation for the decision').optional(), |
| systemMessage: z |
| .string() |
| .describe('Warning message shown to the user') |
| .optional(), |
| hookSpecificOutput: z |
| .union([ |
| z.object({ |
| hookEventName: z.literal('PreToolUse'), |
| permissionDecision: permissionBehaviorSchema().optional(), |
| permissionDecisionReason: z.string().optional(), |
| updatedInput: z.record(z.string(), z.unknown()).optional(), |
| additionalContext: z.string().optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('UserPromptSubmit'), |
| additionalContext: z.string().optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('SessionStart'), |
| additionalContext: z.string().optional(), |
| initialUserMessage: z.string().optional(), |
| watchPaths: z |
| .array(z.string()) |
| .describe('Absolute paths to watch for FileChanged hooks') |
| .optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('Setup'), |
| additionalContext: z.string().optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('SubagentStart'), |
| additionalContext: z.string().optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('PostToolUse'), |
| additionalContext: z.string().optional(), |
| updatedMCPToolOutput: z |
| .unknown() |
| .describe('Updates the output for MCP tools') |
| .optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('PostToolUseFailure'), |
| additionalContext: z.string().optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('PermissionDenied'), |
| retry: z.boolean().optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('Notification'), |
| additionalContext: z.string().optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('PermissionRequest'), |
| decision: z.union([ |
| z.object({ |
| behavior: z.literal('allow'), |
| updatedInput: z.record(z.string(), z.unknown()).optional(), |
| updatedPermissions: z.array(permissionUpdateSchema()).optional(), |
| }), |
| z.object({ |
| behavior: z.literal('deny'), |
| message: z.string().optional(), |
| interrupt: z.boolean().optional(), |
| }), |
| ]), |
| }), |
| z.object({ |
| hookEventName: z.literal('Elicitation'), |
| action: z.enum(['accept', 'decline', 'cancel']).optional(), |
| content: z.record(z.string(), z.unknown()).optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('ElicitationResult'), |
| action: z.enum(['accept', 'decline', 'cancel']).optional(), |
| content: z.record(z.string(), z.unknown()).optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('CwdChanged'), |
| watchPaths: z |
| .array(z.string()) |
| .describe('Absolute paths to watch for FileChanged hooks') |
| .optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('FileChanged'), |
| watchPaths: z |
| .array(z.string()) |
| .describe('Absolute paths to watch for FileChanged hooks') |
| .optional(), |
| }), |
| z.object({ |
| hookEventName: z.literal('WorktreeCreate'), |
| worktreePath: z.string(), |
| }), |
| ]) |
| .optional(), |
| }), |
| ) |
|
|
| |
| export const hookJSONOutputSchema = lazySchema(() => { |
| |
| const asyncHookResponseSchema = z.object({ |
| async: z.literal(true), |
| asyncTimeout: z.number().optional(), |
| }) |
| return z.union([asyncHookResponseSchema, syncHookResponseSchema()]) |
| }) |
|
|
| |
| type SchemaHookJSONOutput = z.infer<ReturnType<typeof hookJSONOutputSchema>> |
|
|
| |
| export function isSyncHookJSONOutput( |
| json: HookJSONOutput, |
| ): json is SyncHookJSONOutput { |
| return !('async' in json && json.async === true) |
| } |
|
|
| |
| export function isAsyncHookJSONOutput( |
| json: HookJSONOutput, |
| ): json is AsyncHookJSONOutput { |
| return 'async' in json && json.async === true |
| } |
|
|
| |
| import type { IsEqual } from 'type-fest' |
| type Assert<T extends true> = T |
| type _assertSDKTypesMatch = Assert< |
| IsEqual<SchemaHookJSONOutput, HookJSONOutput> |
| > |
|
|
| |
| export type HookCallbackContext = { |
| getAppState: () => AppState |
| updateAttributionState: ( |
| updater: (prev: AttributionState) => AttributionState, |
| ) => void |
| } |
|
|
| |
| export type HookCallback = { |
| type: 'callback' |
| callback: ( |
| input: HookInput, |
| toolUseID: string | null, |
| abort: AbortSignal | undefined, |
| |
| hookIndex?: number, |
| |
| context?: HookCallbackContext, |
| ) => Promise<HookJSONOutput> |
| |
| timeout?: number |
| |
| internal?: boolean |
| } |
|
|
| export type HookCallbackMatcher = { |
| matcher?: string |
| hooks: HookCallback[] |
| pluginName?: string |
| } |
|
|
| export type HookProgress = { |
| type: 'hook_progress' |
| hookEvent: HookEvent |
| hookName: string |
| command: string |
| promptText?: string |
| statusMessage?: string |
| } |
|
|
| export type HookBlockingError = { |
| blockingError: string |
| command: string |
| } |
|
|
| export type PermissionRequestResult = |
| | { |
| behavior: 'allow' |
| updatedInput?: Record<string, unknown> |
| updatedPermissions?: PermissionUpdate[] |
| } |
| | { |
| behavior: 'deny' |
| message?: string |
| interrupt?: boolean |
| } |
|
|
| export type HookResult = { |
| message?: Message |
| systemMessage?: Message |
| blockingError?: HookBlockingError |
| outcome: 'success' | 'blocking' | 'non_blocking_error' | 'cancelled' |
| preventContinuation?: boolean |
| stopReason?: string |
| permissionBehavior?: 'ask' | 'deny' | 'allow' | 'passthrough' |
| hookPermissionDecisionReason?: string |
| additionalContext?: string |
| initialUserMessage?: string |
| updatedInput?: Record<string, unknown> |
| updatedMCPToolOutput?: unknown |
| permissionRequestResult?: PermissionRequestResult |
| retry?: boolean |
| } |
|
|
| export type AggregatedHookResult = { |
| message?: Message |
| blockingErrors?: HookBlockingError[] |
| preventContinuation?: boolean |
| stopReason?: string |
| hookPermissionDecisionReason?: string |
| permissionBehavior?: PermissionResult['behavior'] |
| additionalContexts?: string[] |
| initialUserMessage?: string |
| updatedInput?: Record<string, unknown> |
| updatedMCPToolOutput?: unknown |
| permissionRequestResult?: PermissionRequestResult |
| retry?: boolean |
| } |
|
|