# `ReqManagedAgents.Provider`
[🔗](https://github.com/cash-mckeeman/req_managed_agents/blob/v0.14.0/req_managed_agents/lib/req_managed_agents/provider.ex#L1)

Contract a streaming agent backend implements so one `ReqManagedAgents.Session` loop can
drive ANY provider. A provider owns its transport **mode** and **invocation** end-to-end:

  * `:streaming` (push) — the server holds a connection open and pushes events; the client
    posts inputs out-of-band; a turn ends on a boundary event (`turn_boundary?/1`).
  * `:request_response` (pull) — the client calls (`poll_turn/2`) and the server answers the
    whole turn; resume re-sends the conversation delta.

The canonical vocabulary uses Anthropic's `custom_tool_use` / `custom_tool_result` terms and
names ONLY the client-side / return-of-control species; server-side tools are observe-only.

**Raw preservation.** Normalization is additive, never lossy: `turn_outcome` carries the raw,
JSON-decoded provider `events` it was derived from alongside the normalized fields, so a
consumer can always cross-reference the provider's own wire documentation.

# `conn`

```elixir
@type conn() :: term()
```

Provider-private connection / session handle.

# `event`

```elixir
@type event() :: %{required(String.t()) =&gt; term()}
```

A raw, decoded provider event (string-keyed wire map).

# `handle`

```elixir
@type handle() :: term()
```

Provider-private handle to a provisioned, reusable server-side resource.

# `input`

```elixir
@type input() :: term()
```

Provider-private input that drives the next turn.

# `spec`

```elixir
@type spec() :: ReqManagedAgents.Agent.Spec.t()
```

A provider-agnostic agent definition — the input to provisioning and the cache key.
`provision/2` coerces its input via `ReqManagedAgents.Agent.Spec.new/1` at the boundary,
so any Spec-shaped map is accepted; the callback itself is typed against the validated
struct (#70, generalizes #68).

# `terminal`

```elixir
@type terminal() :: :end_turn | :requires_action | :terminated
```

# `consumer`

```elixir
@callback consumer(conn()) :: pid() | nil
```

The linked process consuming the stream on `conn`'s behalf, if any. `nil` when there is no
live consumer (e.g. :request_response, or a not-yet-consolidated resume).

# `data_plane`
*optional* 

```elixir
@callback data_plane() :: :none | :chat_completions | :managed_agents_api
```

Optional — the wire used when `ReqManagedAgents.ModelConfig` supplies a credential
or destination. `:none` means those settings cannot apply; it is also the default
when the callback is absent.

# `kickoff_input`

```elixir
@callback kickoff_input(opts :: keyword()) :: input()
```

Input that kicks off the conversation (the initial user message).

# `mode`

```elixir
@callback mode() :: :streaming | :request_response
```

# `native_model`
*optional* 

```elixir
@callback native_model(String.t()) :: {:ok, term()} | :error
```

Optional — translate a catalog model id (`"provider:model_id"`) into the wire model
value. Return `:error` for an unsupported id. Without this callback, the catalog
id is sent as given.

# `normalize`

```elixir
@callback normalize([event()]) :: ReqManagedAgents.TurnResult.t()
```

Fold a turn's accumulated events into the canonical turn outcome (carries raw `events`).

# `open`

```elixir
@callback open(opts :: keyword(), subscriber :: pid()) :: {:ok, conn()} | {:error, term()}
```

Establish the connection/session; for :streaming, open the event stream to `subscriber`.

# `pending_tool_uses`
*optional* 

```elixir
@callback pending_tool_uses([event()]) :: [ReqManagedAgents.ToolUse.t()]
```

Optional — recover unanswered custom tool uses from `events` (the session's full
accumulated raw event history, oldest first).

A `requires_action` turn's per-batch `normalize/1` can resolve to zero
`custom_tool_uses` when the ids its own stop condition references live in an
EARLIER, already-processed batch (the referencing idle and the tool uses it
names don't always land in the same batch). When that happens, `Session` calls
this callback instead of driving an empty resume — implement it the same way
`reconnect/3` recovers unanswered tool calls across a stream drop (e.g. via
`ReqManagedAgents.Providers.ClaudeManagedAgents.Consolidate.unanswered_tool_uses/1`). Return `[]` when nothing
is recoverable; `Session` then surfaces a loud protocol-state error rather than
ever POSTing an empty events list.

Every use returned here is re-run at-least-once: `Session` re-invokes
`c:ReqManagedAgents.Handler.handle_tool_call/3` (or `/4`) for it regardless of
whether that `tool_use` id was already dispatched earlier in the session —
see `ReqManagedAgents.Handler`'s moduledoc.

# `poll_turn`
*optional* 

```elixir
@callback poll_turn(conn(), input()) :: {:ok, [event()], conn()} | {:error, term()}
```

:request_response only — run one turn synchronously: send `input`, return the turn's events.

# `provision`

```elixir
@callback provision(spec(), opts :: keyword()) :: {:ok, handle()} | {:error, term()}
```

Create (or look up) the provider-side agent resource for `spec`; return a durable handle.

# `push_input`
*optional* 

```elixir
@callback push_input(conn(), input()) :: :ok | {:error, term()}
```

:streaming only — post `input`; events arrive asynchronously at the subscriber.

# `reconnect`
*optional* 

```elixir
@callback reconnect(conn(), subscriber :: pid(), seen :: MapSet.t()) ::
  {:ok, conn(), [ReqManagedAgents.ToolUse.t()], MapSet.t()} | {:error, term()}
```

:streaming only — after a stream drop, re-open the stream (delivering to `subscriber`) and
return any unanswered tool calls to re-drive locally, plus the grown `seen` set.
`Session` prefers `reconnect_with_events/3` when both callbacks are implemented.

Optional: required only for providers that must recover events or unanswered tool
calls across a reconnect (streaming drops, or list-events surfaces). A
request_response provider that only reattaches may omit it — `Session` defaults to
`{:ok, conn, [], seen}`.

# `reconnect_with_events`
*optional* 

```elixir
@callback reconnect_with_events(conn(), subscriber :: pid(), seen :: MapSet.t()) ::
  {:ok, conn(), [ReqManagedAgents.ToolUse.t()], MapSet.t(), [map()]}
  | {:error, term()}
```

Optional alternative to `reconnect/3`, also returning fresh historical events in
chronological order, already deduplicated and included in the returned seen set.
Session prefers this callback and processes history before queued stream events,
without recording it as live evidence. Initial reattachment ignores historical
turns; pending tool calls take precedence over historical event processing.

# `ref`

```elixir
@callback ref(conn()) :: reference() | nil
```

The live stream's tag `conn` carries, if any — `Session` matches inbound
`{:managed_agents, ref, _}` messages against it. `nil` for a :request_response conn or a
:streaming conn with no stream open yet (e.g. a not-yet-consolidated resume).

# `resume_input`

```elixir
@callback resume_input(
  tool_uses :: [ReqManagedAgents.ToolUse.t()],
  results :: [ReqManagedAgents.ToolResult.t()]
) :: input()
```

Input that resumes the loop after local tools ran (the mode's resume contract).

# `resumed?`

```elixir
@callback resumed?(conn()) :: boolean()
```

Whether `open/2` consolidated an EXISTING session into `conn` (a resume) rather than
creating a fresh one. Gates `Session`'s reattach behavior (#66).

Returning `true` routes `Session` through the `:resume` → `:reconnect` continuation.
If neither reconnect callback is implemented, resume proceeds with no event
recovery (`{:ok, conn, [], seen}`) — correct for request_response reattach. A
synchronous `Session.run/2` resume with no `:prompt` has nothing to deliver and
parks until the run timeout — reattach callers should always pass `prompt:`.

# `session_id`

```elixir
@callback session_id(conn()) :: String.t() | nil
```

The provider-side session id `conn` carries, if any (`Session` surfaces it in
`SessionResult`/`SessionInfo`; `nil` when the concept doesn't apply).

# `supports_budget?`
*optional* 

```elixir
@callback supports_budget?() :: boolean()
```

Optional — true when the provider can enforce the `:budget` spend cap on a fresh session.

# `supports_outcomes?`
*optional* 

```elixir
@callback supports_outcomes?() :: boolean()
```

Optional — true when the provider natively honors the `:outcome` kickoff (`user.define_outcome`).

# `supports_runtime_model?`
*optional* 

```elixir
@callback supports_runtime_model?() :: boolean()
```

Optional — true when `ReqManagedAgents.ModelConfig.model` selects this run's model.

# `teardown`
*optional* 

```elixir
@callback teardown(handle(), opts :: keyword()) :: :ok | {:error, term()}
```

Delete the provider-side resource named by `handle`. `opts` carries the client / test seam.

# `text_delta`
*optional* 

```elixir
@callback text_delta(event()) :: String.t() | nil
```

Optional — map ONE raw event to a normalized text chunk, or `nil`.

When implemented, the `Session` emits `%{"type" => "rma.text_delta", "text" => chunk}`
through `handle_event` immediately after forwarding the raw event (additive
normalization: alongside, never instead of, the raw event; never stored in
`SessionResult.events`). Chunk granularity is whatever the provider's wire exposes —
true streaming deltas on AgentCore (`contentBlockDelta`), whole message blocks on
Claude Managed Agents (`agent.message`).

# `transcript`
*optional* 

```elixir
@callback transcript(conn()) :: [map()] | nil
```

Optional — the provider's client-held conversation history in its native wire shape
(string-keyed message maps), or `nil`. Implemented by providers whose history lives
in the conn (e.g. `Local`); server-held providers (CMA, AgentCore) omit it. `Session`
embeds the value into `SessionResult.transcript` at terminal. Entries are
provider-verbatim — never interpreted by this library.

# `turn_boundary?`
*optional* 

```elixir
@callback turn_boundary?(event()) :: boolean()
```

:streaming only — does this event close a turn (so accumulated events form one)?

# `user_input`

```elixir
@callback user_input(text :: String.t()) :: input()
```

Input for a follow-up user message into a running session.

# `data_plane`

```elixir
@spec data_plane(module()) :: :none | :chat_completions | :managed_agents_api
```

The wire declared by `c:data_plane/0`, or `:none` when the callback is absent.
Loads the provider before checking for the callback.

# `native_model`

```elixir
@spec native_model(module(), String.t()) ::
  {:ok, term()} | {:error, {:unsupported_model, String.t()}}
```

Translate a catalog model id using `c:native_model/1`, loading the provider first.
Without the callback, returns the id as given. Unsupported ids return
`{:error, {:unsupported_model, model}}`.

# `result_of`

```elixir
@spec result_of(String.t(), event()) :: ReqManagedAgents.ToolResult.t()
```

Legacy wire adapter: extract a canonical `%ToolResult{}` from a Claude Managed Agents tool-result event
(`user.custom_tool_result` shape), given the tool-use id it answers.

# `supports?`

```elixir
@spec supports?(module(), :outcomes | :budget | :runtime_model | :data_plane) ::
  boolean()
```

Whether the provider declares a capability. `:data_plane` means a wire other than
`:none`; the remaining capabilities read their optional `supports_*?/0` callbacks.
Absent callbacks mean `false`. The provider is loaded before checking callbacks.

---

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