PranitInitial commit439f83c3 months agoHistory
251 lines · 7.3 KB · Zig
RawDownload
const std = @import("std");const types = @import("../shared/types.zig"); pub const HookKind = enum {    pre_tool_use,    stop,    post_turn_end,    attention_required,     pub fn definition(self: HookKind) HookDefinition {        return switch (self) {            .pre_tool_use => pre_tool_use,            .stop => stop,            .post_turn_end => post_turn_end,            .attention_required => attention_required,        };    }}; pub const HookDefinition = struct {    kind: HookKind,    lifecycle_event: []const u8,    agent_loop_point: []const u8,    purpose: []const u8,}; pub const pre_tool_use = HookDefinition{    .kind = .pre_tool_use,    .lifecycle_event = "PreToolUse",    .agent_loop_point = "before local tool validation, permissions, and execution",    .purpose = "lets handlers keep a call unchanged, rewrite its arguments, or block it",}; pub const stop = HookDefinition{    .kind = .stop,    .lifecycle_event = "Stop",    .agent_loop_point = "after a terminal assistant candidate before turn finalization",    .purpose = "lets handlers allow completion or request one synthetic continuation",}; pub const post_turn_end = HookDefinition{    .kind = .post_turn_end,    .lifecycle_event = "PostTurnEnd",    .agent_loop_point = "after a terminal turn outcome is accepted",    .purpose = "lets handlers observe terminal turns and run side effects without changing the turn outcome",}; pub const attention_required = HookDefinition{    .kind = .attention_required,    .lifecycle_event = "AttentionRequired",    .agent_loop_point = "after a user decision prompt becomes active",    .purpose = "lets handlers observe when a foreground turn is waiting for user attention",}; pub const all_hooks = [_]HookDefinition{    pre_tool_use,    stop,    post_turn_end,    attention_required,}; pub const ScopeKind = enum {    interactive,    ask,    acp,    subagent,}; pub const Scope = struct {    kind: ScopeKind,    workspace_root: []const u8,    session_id: ?[]const u8 = null,    subagent_id: ?u64 = null,}; pub const Invocation = struct {    scope: Scope,    turn_id: ?u64 = null,}; pub const Limits = struct {    pub const handler_name_bytes = 128;    pub const reason_bytes = 4 * 1024;    pub const context_bytes = 64 * 1024;    pub const arguments_json_bytes = 1024 * 1024;}; pub const RegistrationError = error{    EmptyHandlerName,    HandlerNameTooLong,    InvalidHandlerName,    DuplicateHandlerName,    RuntimeFrozen,}; pub const ViewError = error{    RuntimeNotFrozen,}; pub const HandlerError = error{    Failed,    Cancelled,}; pub const MutationDispatchError = error{    OutOfMemory,    HandlerFailed,    Cancelled,    InvalidHandlerOutput,    HandlerOutputTooLarge,}; pub const PreToolUseInput = struct {    invocation: Invocation,    step_index: usize,    call_id: []const u8,    tool_name: []const u8,    arguments_json: []const u8,}; pub const PreToolUseAction = union(enum) {    continue_,    rewrite_arguments: []const u8,    block: []const u8,}; pub const PreToolUseOutcome = union(enum) {    unchanged,    rewritten: []u8,    blocked: []u8,     pub fn deinit(self: *PreToolUseOutcome, alloc: std.mem.Allocator) void {        switch (self.*) {            .rewritten => |arguments_json| alloc.free(arguments_json),            .blocked => |reason| alloc.free(reason),            .unchanged => {},        }        self.* = .unchanged;    }}; pub const PreToolUseHandler = struct {    name: []const u8,    ctx: *anyopaque,    run: *const fn (        ctx: *anyopaque,        input: PreToolUseInput,    ) HandlerError!PreToolUseAction,}; pub const StopInput = struct {    invocation: Invocation,    step_index: usize,    assistant_text: []const u8,    provider_disposition: types.ProviderCompletionDisposition,    can_continue: bool,}; pub const StopAction = union(enum) {    allow,    continue_once: []const u8,}; pub const StopOutcome = union(enum) {    allow,    continue_once: []u8,     pub fn deinit(self: *StopOutcome, alloc: std.mem.Allocator) void {        switch (self.*) {            .continue_once => |context| alloc.free(context),            .allow => {},        }        self.* = .allow;    }}; pub const StopHandler = struct {    name: []const u8,    ctx: *anyopaque,    run: *const fn (        ctx: *anyopaque,        input: StopInput,    ) HandlerError!StopAction,}; pub const PostTurnEndInput = struct {    invocation: Invocation,    outcome: types.TurnPresentationOutcome,    provider_disposition: ?types.ProviderCompletionDisposition = null,}; pub const PostTurnEndHandler = struct {    name: []const u8,    ctx: *anyopaque,    run: *const fn (        ctx: *anyopaque,        input: PostTurnEndInput,    ) HandlerError!void,}; pub const AttentionKind = enum {    permission,    question,    route_recovery,}; pub const AttentionRequiredInput = struct {    invocation: Invocation,    kind: AttentionKind,}; pub const AttentionRequiredHandler = struct {    name: []const u8,    ctx: *anyopaque,    run: *const fn (        ctx: *anyopaque,        input: AttentionRequiredInput,    ) HandlerError!void,}; test "hook definitions enumerate current hook surfaces" {    try std.testing.expectEqual(@as(usize, 4), all_hooks.len);    try std.testing.expectEqual(HookKind.pre_tool_use, all_hooks[0].kind);    try std.testing.expectEqual(HookKind.stop, all_hooks[1].kind);    try std.testing.expectEqual(HookKind.post_turn_end, all_hooks[2].kind);    try std.testing.expectEqual(HookKind.attention_required, all_hooks[3].kind);    try std.testing.expectEqualStrings("PreToolUse", pre_tool_use.lifecycle_event);    try std.testing.expectEqualStrings("Stop", stop.lifecycle_event);    try std.testing.expectEqualStrings("PostTurnEnd", post_turn_end.lifecycle_event);    try std.testing.expectEqualStrings("AttentionRequired", attention_required.lifecycle_event);    try std.testing.expectEqual(HookKind.pre_tool_use, HookKind.pre_tool_use.definition().kind);    try std.testing.expectEqual(HookKind.stop, HookKind.stop.definition().kind);    try std.testing.expectEqual(HookKind.post_turn_end, HookKind.post_turn_end.definition().kind);    try std.testing.expectEqual(HookKind.attention_required, HookKind.attention_required.definition().kind);    try std.testing.expectEqualStrings("PreToolUse", HookKind.pre_tool_use.definition().lifecycle_event);    try std.testing.expectEqualStrings("Stop", HookKind.stop.definition().lifecycle_event);    try std.testing.expectEqualStrings("PostTurnEnd", HookKind.post_turn_end.definition().lifecycle_event);    try std.testing.expectEqualStrings("AttentionRequired", HookKind.attention_required.definition().lifecycle_event);    try std.testing.expect(pre_tool_use.agent_loop_point.len > 0);    try std.testing.expect(pre_tool_use.purpose.len > 0);    try std.testing.expect(stop.agent_loop_point.len > 0);    try std.testing.expect(stop.purpose.len > 0);    try std.testing.expect(post_turn_end.agent_loop_point.len > 0);    try std.testing.expect(post_turn_end.purpose.len > 0);    try std.testing.expect(attention_required.agent_loop_point.len > 0);    try std.testing.expect(attention_required.purpose.len > 0);} test "Hooks v1.0 scope kinds remain limited to the current surfaces" {    try std.testing.expectEqual(@as(usize, 4), @typeInfo(ScopeKind).@"enum".fields.len);}