# `Pixir.Provider`
[🔗](https://github.com/Ranvier-Technologies/pixir/blob/main/lib/pixir/provider.ex#L1)

The Provider (ADR 0002/0003/0019): the OpenAI **Responses API** dialect, reached with
either Credential kind. Pixir always sends `store: false`; the local Log remains the
source of truth. The HTTP/SSE path sends folded **History** as full input. The
WebSocket path may use connection-local `previous_response_id` and a late delta as an
optimization, but it never replaces Log replay.

`stream/2` performs one streamed model call:

  * ephemeral `text` / `reasoning` deltas are delivered to the `:on_delta` callback
    as `{:text_delta, chunk}` / `{:reasoning_delta, chunk}` (the Turn loop turns
    these into ephemeral Events — keeping this module free of bus/Session deps);
  * the assembled result `%{text, reasoning, function_calls, finish_reason}` is
    returned for the Turn loop to persist (the final `assistant_message`) and to
    decide whether to run tools.

Errors are structured (ADR 0005). Tests may still inject `:transport` directly; the
production path uses `Pixir.Provider.TransportPolicy` (`:auto | :websocket |
:http_sse`) so WebSocket can be preferred while HTTP/SSE remains fallback. Routing is
frozen once per stream invocation; authentication is resolved once per logical
attempt and reused by any transport fallback inside that attempt. An explicit
`open_responses` backend uses the portable HTTP/SSE body/header policy, strict WHATWG
framing, generated pinned-schema validation before capability policy, and a
conservative no-reasoning/nonportable-output boundary; the default ChatGPT/Codex
wire and compatibility reducer remain separate.
Known strict-open events alone reach that reducer; unknown matched event names are
counted as bounded `other` evidence and their payloads remain opaque.

# `request`

```elixir
@type request() :: %{
  optional(:model) =&gt; String.t(),
  optional(:system_prompt) =&gt; String.t(),
  optional(:developer_context) =&gt; String.t(),
  optional(:workspace) =&gt; String.t(),
  optional(:history) =&gt; [map()],
  optional(:tools) =&gt; [map()],
  optional(:hosted_tools) =&gt; [map()],
  optional(:web_search) =&gt; map() | keyword() | boolean(),
  optional(:prompt_cache_key) =&gt; String.t(),
  optional(:prompt_cache_retention) =&gt; String.t(),
  optional(:output_schema) =&gt; map()
}
```

# `result`

```elixir
@type result() :: %{
  text: String.t(),
  reasoning: String.t(),
  reasoning_items: [map()],
  function_calls: [%{call_id: String.t(), name: String.t(), args: map()}],
  output_items: [
    reasoning: map(),
    function_call: map(),
    provider_hosted_tool: map()
  ],
  provider_hosted_tools: map(),
  web_search: map(),
  finish_reason: :stop | :tool_calls,
  usage: map() | nil,
  usage_summary: map(),
  provider_metadata: map(),
  output_truncation: Pixir.Provider.OutputTruncation.t()
}
```

# `default_model`

Resolve the model id (open knob) through `Pixir.Config.load/1`'s effective snapshot.
That snapshot applies this precedence:

  1. `config :pixir, :model` (programmatic override)
  2. `PIXIR_MODEL` env var
  3. `~/.pixir/config.json` → `"model"`
  4. the built-in default (`gpt-5.5`) when earlier values are absent or blank

# `model_supported?`

```elixir
@spec model_supported?(String.t()) :: boolean()
```

Whether `model_id` is in the advertised catalog (`models/0`). Used to reject an
unknown per-turn `_meta.model` before it reaches the backend (epic A.5,
decision #8 — fail early with `-32602` rather than passing through to a backend
`model_not_supported`).

# `models`

```elixir
@spec models(keyword()) :: [%{required(String.t()) =&gt; String.t() | boolean()}]
```

The model catalog Pixir advertises to a client (epic A.5). A list of
`%{"id" => slug, "name" => label, "default" => bool}` (string-keyed so it
rides ACP `_meta` verbatim). Source: the built-in list (`gpt-5.5` &
family), extended/overridden by a `~/.pixir/config.json` `"models"` array of
slugs if present. The active `default_model/0` is always included and flagged
`default: true` (so the client's picker has a default even if config narrows
the list).

# `probe`

```elixir
@spec probe(keyword()) :: {:ok, %{model: String.t()}} | {:error, map()}
```

Cheaply validate connectivity and the model id: a minimal streamed call (tiny
prompt, no tools). Returns `{:ok, %{model: id}}` if the backend accepts it, or the
structured error otherwise (e.g. a `provider_http_error` for a bad model id). Useful
before committing to a full Turn.

# `request_body_preview`

```elixir
@spec request_body_preview(
  request(),
  keyword()
) :: {:ok, map()} | {:error, map()}
```

Build a Responses request body preview without auth or network.

This uses the same request-shaping path as `stream/2`, including Provider-hosted
tools, prompt-cache fields, and the px2 developer-context item. Smoke tasks should
still bound/redact the returned body before printing it.

# `responses_backend_compatible?`

Whether this Provider accepts an explicit Responses backend selection.

# `stream`

```elixir
@spec stream(
  request(),
  keyword()
) :: {:ok, result()} | {:error, map()}
```

Stream one Responses call, retrying transient failures (network, `:rate_limited`,
5xx) with capped exponential backoff. Terminal errors (`:usage_limit_reached`,
`:model_not_supported`, auth) are not retried. See the module doc for the result
shape. Options: `:max_retries` (default 2), `:sleep` (injectable for tests).

# `tool_spec`

```elixir
@spec tool_spec(map()) :: map()
```

Map a tool definition from a `__tool__/0` callback to a Responses function tool.

# `usage_summary`

```elixir
@spec usage_summary(map() | nil) :: map()
```

Normalize OpenAI usage payloads into the fields Pixir records as Provider evidence.

Supports the Responses shape (`input_tokens_details.cached_tokens`) and the legacy
prompt/completion naming used by older examples.

---

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