defineHook
import { defineHook } from "@pivanov/agent-hooks-bridge";Type-checked builder for your event handler map. Identity at runtime; its only job is to give you autocomplete and per-event type narrowing.
Signature
const defineHook: (handlers: THandlerMap) => THandlerMap;
type THandlerMap = {
SessionStart?: (event: ISessionStartEvent) => THandlerResult | Promise<THandlerResult>;
PreToolUse?: (event: IPreToolUseEvent) => THandlerResult | Promise<THandlerResult>;
PostToolUse?: (event: IPostToolUseEvent) => THandlerResult | Promise<THandlerResult>;
UserPromptSubmit?: (event: IUserPromptSubmitEvent) => THandlerResult | Promise<THandlerResult>;
Stop?: (event: IStopEvent) => THandlerResult | Promise<THandlerResult>;
};
type THandlerResult = IHookResponse | void;Single event
const hooks = defineHook({
PreToolUse: (event) => {
if (event.tool === "Bash") {
return { decision: "allow" };
}
return {};
},
});event is narrowed to IPreToolUseEvent inside the handler. event.tool, event.tool_input are typed.
Multi-event
const hooks = defineHook({
SessionStart: async (event) => ({
additional_context: await readFile("AGENTS.md", "utf8"),
}),
PreToolUse: (event) => {
if (event.tool === "Bash" && isDangerous(event.tool_input.command)) {
return { decision: "deny", reason: "blocked by policy" };
}
return {};
},
Stop: () => {
sendDesktopNotification("Agent done");
},
});Handlers run only for events the host fires. Events without a handler exit cleanly with code 0.
Returning nothing
A handler that returns undefined (or has no return) is treated as { decision: "allow" }: pure observation, no decision, no exit-code change.
defineHook({
PreToolUse: (event) => {
log(`tool: ${event.tool}`);
// no return → no-op response
},
});Handlers can be async
defineHook({
PreToolUse: async (event) => {
const allowed = await checkPolicy(event);
return allowed ? { decision: "allow" } : { decision: "deny", reason: "policy" };
},
});run() awaits the handler before serializing. The bridge does not impose a per-handler timeout; the host's hook timeout governs.
Accessing host-specific data via _native
Every parsed event carries the original payload as event._native: unknown. Cast to inspect tool-specific fields:
defineHook({
PreToolUse: (event) => {
if (event.host === "cursor") {
const native = event._native as { cursor_version?: string; sandbox?: boolean };
// ...
}
},
});This is the escape hatch for fields the unified API doesn't model.