π― Use case
This file lives under βtypes/β, which covers shared TypeScript types and generated typings. On the API surface it exposes isHookEvent, promptRequestSchema, PromptRequest, PromptResponse, and syncHookResponseSchema (and more) β mainly types, interfaces, or factory objects. Dependencies touch schema validation, src, and type-fest. It composes internal code from utils and state (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered import { z } from 'zod/v4' import { lazySchema } from '../utils/lazySchema.js' import { type HookEvent,
π€ Exports (heuristic)
isHookEventpromptRequestSchemaPromptRequestPromptResponsesyncHookResponseSchemahookJSONOutputSchemaisSyncHookJSONOutputisAsyncHookJSONOutputHookCallbackContextHookCallbackHookCallbackMatcherHookProgressHookBlockingErrorPermissionRequestResultHookResultAggregatedHookResult
π External import roots
Package roots from from "β¦" (relative paths omitted).
zodsrctype-fest
π₯οΈ Source preview
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
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)
}
// Prompt elicitation protocol types. The `prompt` key acts as discriminator
// (mirroring the {async:true} pattern), with the id as its value.
export const promptRequestSchema = lazySchema(() =>
z.object({
prompt: z.string(), // request id
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 // request id
selected: string
}
// Sync hook response schema
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(),
}),
)
// Zod schema for hook JSON output validation
export const hookJSONOutputSchema = lazySchema(() => {
// Async hook response schema
const asyncHookResponseSchema = z.object({
async: z.literal(true),
asyncTimeout: z.number().optional(),
})
return z.union([asyncHookResponseSchema, syncHookResponseSchema()])
})
// Infer the TypeScript type from the schema
type SchemaHookJSONOutput = z.infer<ReturnType<typeof hookJSONOutputSchema>>
// Type guard function to check if response is sync
export function isSyncHookJSONOutput(
json: HookJSONOutput,
): json is SyncHookJSONOutput {
return !('async' in json && json.async === true)
}
// Type guard function to check if response is async
export function isAsyncHookJSONOutput(
json: HookJSONOutput,
): json is AsyncHookJSONOutput {
return 'async' in json && json.async === true
}
// Compile-time assertion that SDK and Zod types match
import type { IsEqual } from 'type-fest'
type Assert<T extends true> = T
type _assertSDKTypesMatch = Assert<
IsEqual<SchemaHookJSONOutput, HookJSONOutput>
>
/** Context passed to callback hooks for state access */
export type HookCallbackContext = {
getAppState: () => AppState
updateAttributionState: (
updater: (prev: AttributionState) => AttributionState,
) => void
}
/** Hook that is a callback. */
export type HookCallback = {
type: 'callback'
callback: (
input: HookInput,
toolUseID: string | null,
abort: AbortSignal | undefined,
/** Hook index for SessionStart hooks to compute CLAUDE_ENV_FILE path */
hookIndex?: number,
/** Optional context for accessing app state */
context?: HookCallbackContext,
) => Promise<HookJSONOutput>
/** Timeout in seconds for this hook */
timeout?: number
/** Internal hooks (e.g. session file access analytics) are excluded from tengu_run_hook metrics */
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
}