Skip to main content

guides/provider-profile-contract.md

# Provider Profile Contract

`CliSubprocessCore.ProviderProfile` defines the contract every first-party
built-in or external provider CLI profile must implement.

## Behaviour Surface

```elixir
@callback id() :: atom()
@callback capabilities() :: [atom()]
@callback build_invocation(keyword()) ::
            {:ok, CliSubprocessCore.Command.t()} | {:error, term()}
@callback init_parser_state(keyword()) :: term()
@callback decode_stdout(binary(), term()) ::
            {[CliSubprocessCore.Event.t()], term()}
@callback decode_stderr(binary(), term()) ::
            {[CliSubprocessCore.Event.t()], term()}
@callback handle_exit(term(), term()) ::
            {[CliSubprocessCore.Event.t()], term()}
@callback transport_options(keyword()) :: keyword()
```

## Responsibilities

Each profile owns:

- the normalized provider id
- the capability list exposed to downstream consumers
- CLI command construction for that provider across both
  `CliSubprocessCore.Command.run/1` and `CliSubprocessCore.Session`
- stdout parsing into normalized core events
- stderr parsing into normalized core events
- exit handling into normalized terminal events
- transport option overrides needed by the provider CLI

The foundation explicitly keeps those responsibilities out of downstream repos.

## Validation Helpers

The behaviour module provides two helper functions:

- `CliSubprocessCore.ProviderProfile.ensure_module/1`
- `CliSubprocessCore.ProviderProfile.validate_invocation/1`

`ensure_module/1` verifies that a module is loaded, declares the behaviour, and
exports the required callbacks.

`validate_invocation/1` verifies that the profile returned a valid
`CliSubprocessCore.Command` struct.

That same invocation contract is reused by both:

- `CliSubprocessCore.Command.run/1` for one-shot non-PTY execution
- `CliSubprocessCore.Session.start_session/1` for long-lived normalized sessions

## Registry Integration

`CliSubprocessCore.ProviderRegistry` stores provider profile modules by id.

External preloads are supported at application boot:

```elixir
config :cli_subprocess_core,
  built_in_profile_modules: [
    MyApp.ProviderProfiles.Example
  ]
```

That preload hook only affects the default registry boot list. It does not make
the external profile a first-party built-in shipped by
`cli_subprocess_core`.

Ad hoc registrations can also be added at runtime:

```elixir
:ok =
  CliSubprocessCore.ProviderRegistry.register(
    MyApp.ProviderProfiles.Example
  )
```

## Design Constraints

Provider profiles should emit only `CliSubprocessCore.Event` values containing
`CliSubprocessCore.Payload.*` structs. They should not invent competing
normalized payload families in downstream repos.

For a step-by-step implementation guide, see
`guides/custom-provider-profiles.md`.