# `CliSubprocessCore.ProviderProfile`
[🔗](https://github.com/nshkrdotcom/cli_subprocess_core/blob/v0.7.0/lib/cli_subprocess_core/provider_profile.ex#L1)

Behaviour contract for built-in and external provider CLI profiles.

# `build_result`

```elixir
@type build_result() ::
  {:ok, invocation()} | {:ok, invocation(), teardown()} | {:error, term()}
```

What `build_invocation/1` returns.

A profile that materializes a resource for the run — a Codex output-schema
file is the shipped example — returns it with a teardown the run's owner is
responsible for calling. Owner death is covered independently by
`CliSubprocessCore.EphemeralFiles`.

# `callback_spec`

```elixir
@type callback_spec() ::
  {:id, 0}
  | {:capabilities, 0}
  | {:build_invocation, 1}
  | {:init_parser_state, 1}
  | {:decode_stdout, 2}
  | {:decode_stderr, 2}
  | {:handle_exit, 2}
  | {:transport_options, 1}
```

# `decode_result`

```elixir
@type decode_result() :: {[CliSubprocessCore.Event.t()], parser_state()}
```

Event decode result returned by parser callbacks.

# `id`

```elixir
@type id() :: atom()
```

Normalized provider identifier.

# `invocation`

```elixir
@type invocation() :: CliSubprocessCore.Command.t()
```

Normalized invocation returned by a provider profile.

# `parser_state`

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

Parser state owned by an individual provider profile.

# `teardown`

```elixir
@type teardown() :: (-&gt; :ok)
```

Releases resources an invocation needed on disk or in memory for the lifetime
of the run. Idempotent, bounded, and safe to call from any process.

# `build_invocation`

```elixir
@callback build_invocation(keyword()) :: build_result()
```

# `capabilities`

```elixir
@callback capabilities() :: [atom()]
```

# `decode_stderr`

```elixir
@callback decode_stderr(binary(), parser_state()) :: decode_result()
```

# `decode_stdout`

```elixir
@callback decode_stdout(binary(), parser_state()) :: decode_result()
```

# `handle_exit`

```elixir
@callback handle_exit(term(), parser_state()) :: decode_result()
```

# `id`

```elixir
@callback id() :: id()
```

# `init_parser_state`

```elixir
@callback init_parser_state(keyword()) :: parser_state()
```

# `transport_options`

```elixir
@callback transport_options(keyword()) :: keyword()
```

# `accepts_input_after_start?`

```elixir
@spec accepts_input_after_start?(module()) :: boolean()
```

Whether a lane can be given more input after its run has started.

This is the fact a caller needs to decide *how* to say something to a session
already in flight: write to it, or interrupt the turn and resume the thread.

Two conditions, and both are required. The transport must leave stdin open,
and the profile must declare `:incremental_input` — that it invokes the CLI
in a mode which keeps *reading* stdin once the turn is running.

An open file descriptor is not the same as a reader. `claude --print` takes
its prompt on argv, consumes stdin once while assembling that prompt, and
never reads it again; the descriptor stays open for the life of the process
and writing to it mid-turn reaches nobody. Deriving this from
`close_stdin_on_start?` alone therefore reported `claude` as accepting live
input, and a caller acting on that had its message silently swallowed while
every layer above reported success. Verified against the shipping CLI: text
piped before the turn changes the answer, and the same text written eight
seconds in does not.

No shipped profile declares `:incremental_input` today, so every lane is
steered by interrupt and resume. A profile that switches to a streaming input
mode declares it and changes this with it.

It is not `:interrupt` or `:resume` from `capabilities/0` — every profile
declares those, and they do not distinguish the two mechanisms.

# `ensure_module`

```elixir
@spec ensure_module(module()) ::
  :ok
  | {:error, {:module_not_loaded, module()}}
  | {:error, {:missing_callbacks, module(), [{atom(), non_neg_integer()}]}}
  | {:error, {:behaviour_not_declared, module()}}
```

Validates that a module satisfies the provider profile contract.

# `normalize_build_result`

```elixir
@spec normalize_build_result(build_result()) ::
  {:ok, invocation(), teardown()} | {:error, term()}
```

Normalizes a `build_invocation/1` return into `{invocation, teardown}`.

A profile that materializes nothing returns the two-element form, and gets a
no-op teardown, so every caller can use one shape.

# `required_callbacks`

```elixir
@spec required_callbacks() :: [callback_spec(), ...]
```

Returns the callbacks required by the provider profile contract.

# `validate_invocation`

```elixir
@spec validate_invocation(invocation()) ::
  :ok
  | {:error, {:invalid_command, term()}}
  | {:error, {:invalid_args, term()}}
  | {:error, {:invalid_cwd, term()}}
  | {:error, {:invalid_env, term()}}
  | {:error, {:invalid_clear_env, term()}}
```

Validates a normalized invocation returned by a provider profile.

---

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