LemonGateway. Engine behaviour
(lemon_gateway v0.1.0)
View Source
Behaviour for AI engine plugins.
An engine wraps an AI backend (CLI tool, API, or native integration) and provides a uniform interface for starting runs, streaming output, cancellation, session resumption, and mid-run steering.
Implementing an Engine
defmodule MyEngine do
@behaviour LemonGateway.Engine
@impl true
def id, do: "myengine"
@impl true
def start_run(job, opts, sink_pid) do
# Start the AI run, send events to sink_pid
{:ok, make_ref(), cancel_context}
end
# ... implement remaining callbacks
endEvent Protocol
Engines send events to sink_pid as {:engine_event, run_ref, event} messages
where event is a plain tagged map built via Event.started/1, Event.action_event/1,
or Event.completed/1. Streaming text is sent as {:engine_delta, run_ref, text}.
Summary
Callbacks
Stop a run, given the cancel_ctx that start_run/3 returned.
Inject text into a run that is already in flight.
Whether this engine accepts mid-run steering.
Types
@type run_opts() :: %{ optional(:cwd) => String.t(), optional(:env) => %{required(String.t()) => String.t()}, optional(:timeout_ms) => non_neg_integer(), optional(:capabilities) => map() }
Callbacks
@callback cancel(cancel_ctx :: term()) :: :ok
Stop a run, given the cancel_ctx that start_run/3 returned.
Must return :ok for any term, including a context this engine never
produced, and must be idempotent — cancelling twice, or cancelling a run that
already completed, is still :ok.
That is stricter than it looks like it needs to be, and it is deliberate: the
gateway calls cancel/1 from supervisors and timeout paths, after crashes and
across restarts, where the context it holds may be stale or partial. An engine
that pattern-matches only its own happy-path context turns a routine
cancellation into a FunctionClauseError in the caller. End your clauses with
a catch-all:
def cancel(%{task_pid: pid}) when is_pid(pid) do
Process.exit(pid, :kill)
:ok
end
def cancel(_ctx), do: :ok
@callback extract_resume(String.t()) :: LemonCore.ResumeToken.t() | nil
@callback format_resume(LemonCore.ResumeToken.t()) :: String.t()
@callback id() :: String.t()
Inject text into a run that is already in flight.
Optional, and only called when supports_steer?/0 answers true. Receives
the same cancel_ctx as cancel/1 and, like it, should report a context it
cannot use as {:error, reason} rather than raising.
@callback supports_steer?() :: boolean()
Whether this engine accepts mid-run steering.
Must be pure and stable. Answering true obliges the engine to export
steer/2; there is no way to declare that in the behaviour itself, since
steer/2 has to stay optional for the engines that do not support it, so it
is checked by LemonPlatformTest.EngineCase instead.