# `ALLM.StreamRunner`
[🔗](https://github.com/cykod/ALLM/blob/v0.5.0/lib/allm/stream_runner.ex#L1)

> #### Internal {: .warning}
>
> This module is internal — it's documented for transparency, but
> call sites should use `ALLM.stream_generate/3` instead.

Validates the request, resolves model/tools/params via `ALLM.Engine`,
dispatches to the engine adapter's `stream/2`, and applies the
documented post-filters plus the `:on_event` observer.

## Orchestration opts are stripped

Orchestration opts — `:mode`, `:max_turns`, `:halt_when` — are
deny-listed here and NOT forwarded to the adapter.
`stream_generate/3` is single-request, so they would be no-ops or (in
the case of `:halt_when`) a `Protocol.UndefinedError` trap at the
Jason-encode boundary of real providers. `Logger.debug/1` fires for
each stripped key so power users can see the drop during
development.

## No double-wrapped `Stream.resource/3`

The adapter owns the streaming resource and its cleanup hook; this
module only composes `Stream.each/2` (for `:on_event`) and
`Stream.filter/2` (for the three emit/include filters) on top. Both
operators propagate `{:halt, _}` upstream, which preserves the
halt-safety contract.

## `:on_event` failure mode

`:on_event` is invoked lazily by `Stream.each/2` inside the consumer's
reducing process — an exception raised by the callback surfaces there,
not at the `stream_generate/3` call site. This module does not wrap the
callback in `try/rescue`; callers who need resilience wrap their own
callback.

# `run`

```elixir
@spec run(ALLM.Engine.t(), ALLM.Request.t(), keyword()) ::
  {:ok, Enumerable.t()}
  | {:error,
     ALLM.Error.EngineError.t()
     | ALLM.Error.AdapterError.t()
     | ALLM.Error.ValidationError.t()}
```

Dispatch a streaming request. Validates, resolves params, forwards to
`engine.adapter.stream/2`, and wires the post-processing pipeline.

Returns `{:ok, stream}` on a successfully-opened stream (lazy — no
event fires until the caller reduces) or `{:error, struct}` on a
synchronous pre-flight failure.

## Examples

    iex> engine = ALLM.Engine.new(
    ...> adapter: ALLM.Providers.Fake,
    ...> adapter_opts: [script: [{:text, "hi"}, {:finish, :stop}]]
    ...>)
    iex> req = ALLM.request([ALLM.user("say hi")])
    iex> {:ok, stream} = ALLM.StreamRunner.run(engine, req)
    iex> events = Enum.to_list(stream)
    iex> Enum.any?(events, &match?({:message_completed, _}, &1))
    true

---

*Consult [api-reference.md](api-reference.md) for complete listing*
