-module(pig@hooks). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/pig/hooks.gleam"). -export([new/1, on_session_start/2, on_session_shutdown/2, on_before_inference/2, on_after_inference/2, on_tool_call/2, on_tool_result/2, on_error/2, on_complete/2, allow_tool/0, block_tool/1, keep_result/0, replace_result/2, keep_messages/0, replace_messages/1, decide_tool_call/2, decide_tool_result/2, decide_messages/2, notify_after_inference/2, notify_error/2, notify_complete/2, notify_session_start/2, notify_session_shutdown/2]). -export_type([before_inference_event/0, before_inference_action/0, after_inference_event/0, tool_call_event/0, tool_call_action/0, tool_result_event/0, tool_result_action/0, error_event/0, complete_event/0, session_start_event/0, session_shutdown_event/0, hooks/0, tool_call_decision/0, tool_result_decision/0, messages_decision/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Hooks system for pig agent lifecycle mediation.\n" "\n" " Hooks allow library users to intercept agent lifecycle events:\n" " - Blocking tool calls\n" " - Transforming results\n" " - Reacting to events\n" "\n" " Hooks are composed as a List(Hooks) — no wrapper type needed.\n" " Composition functions run handlers in order with per-event semantics.\n" ). -type before_inference_event() :: {before_inference_event, binary(), list(pig_protocol@message:message())}. -type before_inference_action() :: keep_messages | {replace_messages, list(pig_protocol@message:message())}. -type after_inference_event() :: {after_inference_event, binary(), pig_protocol@message:message(), integer()}. -type tool_call_event() :: {tool_call_event, binary(), binary(), binary()}. -type tool_call_action() :: allow_tool | {block_tool, binary()}. -type tool_result_event() :: {tool_result_event, binary(), binary(), binary(), boolean(), integer()}. -type tool_result_action() :: keep_result | {replace_result, binary(), boolean()}. -type error_event() :: {error_event, binary(), pig_protocol@error:ai_error()}. -type complete_event() :: {complete_event, binary(), pig_protocol@message:message(), integer()}. -type session_start_event() :: {session_start_event, list(pig_protocol@message:message())}. -type session_shutdown_event() :: {session_shutdown_event, list(pig_protocol@message:message()), integer()}. -type hooks() :: {hooks, binary(), fun((session_start_event()) -> nil), fun((session_shutdown_event()) -> nil), fun((before_inference_event()) -> before_inference_action()), fun((after_inference_event()) -> nil), fun((tool_call_event()) -> tool_call_action()), fun((tool_result_event()) -> tool_result_action()), fun((error_event()) -> nil), fun((complete_event()) -> nil)}. -type tool_call_decision() :: tool_allowed | {tool_blocked, binary(), binary()}. -type tool_result_decision() :: {result_unchanged, tool_result_event()} | {result_transformed, tool_result_event(), list(binary())}. -type messages_decision() :: {messages_unchanged, list(pig_protocol@message:message())} | {messages_replaced, list(pig_protocol@message:message()), list(binary())}. -file("src/pig/hooks.gleam", 103). ?DOC(" Creates hooks with all no-op handlers.\n"). -spec new(binary()) -> hooks(). new(Name) -> {hooks, Name, fun(_) -> nil end, fun(_) -> nil end, fun(_) -> keep_messages end, fun(_) -> nil end, fun(_) -> allow_tool end, fun(_) -> keep_result end, fun(_) -> nil end, fun(_) -> nil end}. -file("src/pig/hooks.gleam", 118). ?DOC(" Sets the session_start handler.\n"). -spec on_session_start(hooks(), fun((session_start_event()) -> nil)) -> hooks(). on_session_start(H, Handler) -> {hooks, erlang:element(2, H), Handler, erlang:element(4, H), erlang:element(5, H), erlang:element(6, H), erlang:element(7, H), erlang:element(8, H), erlang:element(9, H), erlang:element(10, H)}. -file("src/pig/hooks.gleam", 126). ?DOC(" Sets the session_shutdown handler.\n"). -spec on_session_shutdown(hooks(), fun((session_shutdown_event()) -> nil)) -> hooks(). on_session_shutdown(H, Handler) -> {hooks, erlang:element(2, H), erlang:element(3, H), Handler, erlang:element(5, H), erlang:element(6, H), erlang:element(7, H), erlang:element(8, H), erlang:element(9, H), erlang:element(10, H)}. -file("src/pig/hooks.gleam", 134). ?DOC(" Sets the before_inference handler.\n"). -spec on_before_inference( hooks(), fun((before_inference_event()) -> before_inference_action()) ) -> hooks(). on_before_inference(H, Handler) -> {hooks, erlang:element(2, H), erlang:element(3, H), erlang:element(4, H), Handler, erlang:element(6, H), erlang:element(7, H), erlang:element(8, H), erlang:element(9, H), erlang:element(10, H)}. -file("src/pig/hooks.gleam", 142). ?DOC(" Sets the after_inference handler.\n"). -spec on_after_inference(hooks(), fun((after_inference_event()) -> nil)) -> hooks(). on_after_inference(H, Handler) -> {hooks, erlang:element(2, H), erlang:element(3, H), erlang:element(4, H), erlang:element(5, H), Handler, erlang:element(7, H), erlang:element(8, H), erlang:element(9, H), erlang:element(10, H)}. -file("src/pig/hooks.gleam", 150). ?DOC(" Sets the tool_call handler.\n"). -spec on_tool_call(hooks(), fun((tool_call_event()) -> tool_call_action())) -> hooks(). on_tool_call(H, Handler) -> {hooks, erlang:element(2, H), erlang:element(3, H), erlang:element(4, H), erlang:element(5, H), erlang:element(6, H), Handler, erlang:element(8, H), erlang:element(9, H), erlang:element(10, H)}. -file("src/pig/hooks.gleam", 158). ?DOC(" Sets the tool_result handler.\n"). -spec on_tool_result( hooks(), fun((tool_result_event()) -> tool_result_action()) ) -> hooks(). on_tool_result(H, Handler) -> {hooks, erlang:element(2, H), erlang:element(3, H), erlang:element(4, H), erlang:element(5, H), erlang:element(6, H), erlang:element(7, H), Handler, erlang:element(9, H), erlang:element(10, H)}. -file("src/pig/hooks.gleam", 166). ?DOC(" Sets the error handler.\n"). -spec on_error(hooks(), fun((error_event()) -> nil)) -> hooks(). on_error(H, Handler) -> {hooks, erlang:element(2, H), erlang:element(3, H), erlang:element(4, H), erlang:element(5, H), erlang:element(6, H), erlang:element(7, H), erlang:element(8, H), Handler, erlang:element(10, H)}. -file("src/pig/hooks.gleam", 171). ?DOC(" Sets the complete handler.\n"). -spec on_complete(hooks(), fun((complete_event()) -> nil)) -> hooks(). on_complete(H, Handler) -> {hooks, erlang:element(2, H), erlang:element(3, H), erlang:element(4, H), erlang:element(5, H), erlang:element(6, H), erlang:element(7, H), erlang:element(8, H), erlang:element(9, H), Handler}. -file("src/pig/hooks.gleam", 178). ?DOC(" Returns an action that allows a tool call to proceed.\n"). -spec allow_tool() -> tool_call_action(). allow_tool() -> allow_tool. -file("src/pig/hooks.gleam", 183). ?DOC(" Returns an action that blocks a tool call with the given reason.\n"). -spec block_tool(binary()) -> tool_call_action(). block_tool(Reason) -> {block_tool, Reason}. -file("src/pig/hooks.gleam", 188). ?DOC(" Returns an action that keeps the original tool result.\n"). -spec keep_result() -> tool_result_action(). keep_result() -> keep_result. -file("src/pig/hooks.gleam", 193). ?DOC(" Returns an action that replaces the tool result.\n"). -spec replace_result(binary(), boolean()) -> tool_result_action(). replace_result(Content, Is_error) -> {replace_result, Content, Is_error}. -file("src/pig/hooks.gleam", 198). ?DOC(" Returns an action that keeps the original messages.\n"). -spec keep_messages() -> before_inference_action(). keep_messages() -> keep_messages. -file("src/pig/hooks.gleam", 203). ?DOC(" Returns an action that replaces the messages.\n"). -spec replace_messages(list(pig_protocol@message:message())) -> before_inference_action(). replace_messages(Messages) -> {replace_messages, Messages}. -file("src/pig/hooks.gleam", 239). -spec find_blocking_hook(list(hooks()), tool_call_event()) -> tool_call_decision(). find_blocking_hook(Hooks_list, Event) -> case Hooks_list of [] -> tool_allowed; [H | Rest] -> case (erlang:element(7, H))(Event) of {block_tool, Reason} -> {tool_blocked, erlang:element(2, H), Reason}; allow_tool -> find_blocking_hook(Rest, Event) end end. -file("src/pig/hooks.gleam", 232). ?DOC( " Decide whether a tool call is allowed. First BlockTool wins.\n" " Returns ToolBlocked with the blocker's name and reason for attribution.\n" ). -spec decide_tool_call(list(hooks()), tool_call_event()) -> tool_call_decision(). decide_tool_call(Hooks_list, Event) -> find_blocking_hook(Hooks_list, Event). -file("src/pig/hooks.gleam", 256). ?DOC( " Decide the final tool result. Chains ReplaceResult transformations.\n" " Returns ResultTransformed with all transformer names for observability.\n" ). -spec decide_tool_result(list(hooks()), tool_result_event()) -> tool_result_decision(). decide_tool_result(Hooks_list, Event) -> {Final_event, Transformers} = gleam@list:fold( Hooks_list, {Event, []}, fun(Acc, H) -> {Ev, Names} = Acc, Action = (erlang:element(8, H))(Ev), case Action of keep_result -> {Ev, Names}; {replace_result, Content, Is_error} -> {{tool_result_event, erlang:element(2, Ev), erlang:element(3, Ev), Content, Is_error, erlang:element(6, Ev)}, lists:append(Names, [erlang:element(2, H)])} end end ), case Transformers of [] -> {result_unchanged, Event}; _ -> {result_transformed, Final_event, Transformers} end. -file("src/pig/hooks.gleam", 286). ?DOC( " Decide the final messages for inference. Chains ReplaceMessages.\n" " Returns MessagesReplaced with all transformer names for observability.\n" ). -spec decide_messages(list(hooks()), before_inference_event()) -> messages_decision(). decide_messages(Hooks_list, Event) -> {Final_event, Transformers} = gleam@list:fold( Hooks_list, {Event, []}, fun(Acc, H) -> {Ev, Names} = Acc, Action = (erlang:element(5, H))(Ev), case Action of keep_messages -> {Ev, Names}; {replace_messages, Messages} -> {{before_inference_event, erlang:element(2, Ev), Messages}, lists:append(Names, [erlang:element(2, H)])} end end ), case Transformers of [] -> {messages_unchanged, erlang:element(3, Event)}; _ -> {messages_replaced, erlang:element(3, Final_event), Transformers} end. -file("src/pig/hooks.gleam", 311). ?DOC(" Run all after_inference handlers (fire-and-forget).\n"). -spec notify_after_inference(list(hooks()), after_inference_event()) -> nil. notify_after_inference(Hooks_list, Event) -> _ = gleam@list:map(Hooks_list, fun(H) -> (erlang:element(6, H))(Event) end), nil. -file("src/pig/hooks.gleam", 320). ?DOC(" Run all error handlers (fire-and-forget).\n"). -spec notify_error(list(hooks()), error_event()) -> nil. notify_error(Hooks_list, Event) -> _ = gleam@list:map(Hooks_list, fun(H) -> (erlang:element(9, H))(Event) end), nil. -file("src/pig/hooks.gleam", 326). ?DOC(" Run all complete handlers (fire-and-forget).\n"). -spec notify_complete(list(hooks()), complete_event()) -> nil. notify_complete(Hooks_list, Event) -> _ = gleam@list:map(Hooks_list, fun(H) -> (erlang:element(10, H))(Event) end), nil. -file("src/pig/hooks.gleam", 332). ?DOC(" Run all session_start handlers (fire-and-forget).\n"). -spec notify_session_start(list(hooks()), session_start_event()) -> nil. notify_session_start(Hooks_list, Event) -> _ = gleam@list:map(Hooks_list, fun(H) -> (erlang:element(3, H))(Event) end), nil. -file("src/pig/hooks.gleam", 341). ?DOC(" Run all session_shutdown handlers (fire-and-forget).\n"). -spec notify_session_shutdown(list(hooks()), session_shutdown_event()) -> nil. notify_session_shutdown(Hooks_list, Event) -> _ = gleam@list:map(Hooks_list, fun(H) -> (erlang:element(4, H))(Event) end), nil.