defmodule Pi.Plugin do
@moduledoc "Behaviour for supervised pi_bridge plugins."
alias Pi.Plugin.API
alias Pi.Plugin.Command
@type tool_call_result :: :ok | {:ok, map()} | {:block, String.t()}
@type tool_result_result :: :ok | {:ok, map()}
@type command_result :: {:ok, String.t()} | {:error, String.t()} | String.t()
@callback init(keyword()) :: {:ok, term()} | {:error, term()} | term()
@callback handle_event(map(), term()) :: {:noreply, term()} | term()
@callback apis() :: [API.t() | keyword() | map()]
@callback commands() :: [Command.t() | keyword() | map()]
@callback handle_command(atom(), String.t(), term()) ::
{command_result(), term()} | command_result()
@callback tool_call(map(), map(), term()) :: {tool_call_result(), term()} | tool_call_result()
@callback tool_result(map(), map(), term()) ::
{tool_result_result(), term()} | tool_result_result()
@callback shutdown(term()) :: :ok | term()
@optional_callbacks init: 1,
handle_event: 2,
apis: 0,
commands: 0,
handle_command: 3,
tool_call: 3,
tool_result: 3,
shutdown: 1
defmacro __using__(_opts) do
quote do
@behaviour Pi.Plugin
Module.register_attribute(__MODULE__, :pi_plugin_apis, accumulate: true)
Module.register_attribute(__MODULE__, :pi_plugin_commands, accumulate: true)
import Pi.Plugin, only: [api: 1, command: 1]
@before_compile Pi.Plugin
def init(_opts), do: {:ok, %{}}
def handle_event(_event, state), do: {:noreply, state}
def handle_command(_name, _args, state), do: {{:error, "Unknown plugin command"}, state}
def tool_call(_call, _context, state), do: {:ok, state}
def tool_result(_result, _context, state), do: {:ok, state}
def shutdown(_state), do: :ok
defoverridable init: 1,
handle_event: 2,
handle_command: 3,
tool_call: 3,
tool_result: 3,
shutdown: 1
end
end
defmacro api(attrs) do
attrs = expand_attrs(attrs, __CALLER__)
quote do
@pi_plugin_apis unquote(Macro.escape(attrs))
end
end
defmacro command(attrs) do
attrs = expand_attrs(attrs, __CALLER__)
quote do
@pi_plugin_commands unquote(Macro.escape(attrs))
end
end
defmacro __before_compile__(env) do
apis =
env.module
|> Module.get_attribute(:pi_plugin_apis)
|> Enum.reverse()
commands =
env.module
|> Module.get_attribute(:pi_plugin_commands)
|> Enum.reverse()
quote do
def apis do
unquote(Macro.escape(apis))
|> Enum.map(&API.new/1)
end
def commands do
unquote(Macro.escape(commands))
|> Enum.map(&Command.new/1)
end
defoverridable apis: 0, commands: 0
end
end
defp expand_attrs(attrs, caller) do
Enum.map(attrs, fn
{key, value} when key in [:module, :alias] -> {key, Macro.expand(value, caller)}
entry -> entry
end)
end
end