Navigation
Copy page

Models, Providers, and Transports

On this page

kli does not bind a model to its wire protocol or its credentials. Three parts stay separate: a registry that lists what you can select, the providers that put entries in that list, and the transports that turn a selection into an HTTP call. That split is why you can run two providers side by side, switch between them mid-conversation, and add an OpenAI-compatible endpoint without touching the others.

The registry: what you can select

There is one model registry in a running kli. It holds two kinds of entries. A provider entry records how to reach a vendor: an API style, a base URL, header and metadata config, and which credentials it needs. A model definition entry records a single model you can pick, keyed by its provider and model id, with its context window and whether it reasons. The model definition also carries the API style, so picking a model is enough to know how its call will be shaped.

The registry does not hold secrets and does not open sockets. It answers one question: given the credentials present right now, which models can you select? When you list models, the registry walks its definitions and keeps each one whose provider has a usable credential. A model whose key is not set never appears, so the list reflects what will actually run rather than the full catalogue.

Selecting a model records a current selection on the registry and, if a session is active, appends a model-change entry to the session log. Selection is plain state, so switching providers mid-conversation is the same operation as picking a model at the start.

Providers: extensions that fill the registry

A provider is not built into the registry. It is an extension that, when it loads, registers its catalogue: an auth-provider, a credential reference, one provider entry, the model definitions, and the transport adapter for its API. The shipped providers:

  • anthropic — Claude models on the Messages API. Reads the key from ANTHROPIC_API_KEY.
  • openai — GPT models on the Responses API. Reads the key from OPENAI_API_KEY.
  • openai-codex — GPT models through a ChatGPT account on the Responses API. Authenticates by OAuth rather than an API key.
  • compatible — model entries you define for any OpenAI-compatible endpoint, declared in ~/.config/kli/providers.json.

Because every provider registers through the same path, the registry treats them uniformly: a Claude entry and a self-hosted entry are the same kind of object, differing only in their recorded API and credentials. Adding a provider adds rows to the list and one transport adapter; it changes nothing about the providers already there.

The same uniformity runs in reverse. A provider is retractable: removing it drains exactly what it registered — its models, its provider entry, its credential reference, its auth-provider, and a reference to its transport adapter — and leaves the rest of the registry intact. Two providers that share a transport (openai and openai-codex both use the Responses API) share one adapter through a reference count, so retracting one keeps the other's transport in place.

The compatible provider reads its entries from a JSON file keyed by provider id. Each entry names a base URL, an API (openai-completions by default, or openai-responses), the environment variable that holds its key, and its models. Secrets stay out of the file; only the variable name lives there. A compatible entry registers through the same installer as the built-in providers, so a model you define behaves like a built-in one once it is in the registry.

Transports: how a selection becomes a call

A transport is the adapter that streams one API shape. There are three, keyed by API style, not by vendor:

  • anthropic-messages — the Claude Messages format. Sends the key in an x-api-key header.
  • openai-responses — the OpenAI Responses format. Sends a bearer token, and for a ChatGPT account adds the account header.
  • openai-completions — the OpenAI chat-completions format, for compatible endpoints that speak it. Sends a bearer token.

When you select a model and the agent needs a turn, the runtime reads the API from the provider, finds the registered adapter for that API, resolves the credential, and hands the request to the adapter to stream. The adapter converts kli's messages and tool descriptors into that API's JSON, opens the stream, and emits a normalized sequence of deltas — assistant text, reasoning, tool calls, usage, stop reason — that the rest of kli consumes without caring which vendor produced them.

Keying transports by API rather than by vendor is why one adapter serves more than one provider. openai and openai-codex are distinct providers with distinct base URLs and distinct credentials, but both register the openai-responses API, so both stream through the same Responses adapter. A new OpenAI-compatible endpoint reuses an existing transport by declaring an API the registry already knows, so it registers no adapter of its own.

Why auth modes differ by provider

The credential mechanism is a property of the provider, not the model and not the transport. A provider declares its credential when it registers, and the auth layer holds a reference of the matching kind:

  • Environment key (anthropic, openai, and most compatible entries) — the key is read from a named environment variable at call time. The reference stores the variable name, never the secret.
  • OAuth (openai-codex) — kli runs a login flow and persists the tokens; the reference refreshes an expired access token before the call. There is no API key because a ChatGPT account does not issue one.
  • Static, persisted key — a key you set once and kli stores, for providers without an environment variable.

The registry uses this only to decide availability — an environment provider is available when its variable is set, an OAuth provider when a usable token is on file — so the model list stays honest without reading any secret. The transport uses it to build the right header: the resolved credential becomes x-api-key for Messages and a bearer token for the OpenAI APIs. Different auth modes coexist in one session because each provider carries its own; an OAuth ChatGPT model and an environment-keyed Claude model are both selectable at the same time, and switching between them changes nothing about how the other is authenticated.

Putting a provider to work

To add credentials and pick a model, see Connect a Provider. To define an OpenAI-compatible endpoint, see Add a Compatible Provider. For the full list of shipped models, providers, and config keys, see the model reference. For how a selection drives a turn, see The Agent Loop.