Self-Hosted: Ollama & vLLM
Ollama and vLLM are both thin configurations of the same openai-compatible adapter factory pointed at a different baseUrl, since both already speak an OpenAI-shaped /v1/chat/completions. Neither has a bespoke adapter file beyond a few lines wiring up endpointUrl.
Authorization: Bearer lattice_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxConfiguration
| Ollama | vLLM | |
|---|---|---|
| Base URL env var | OLLAMA_BASE_URL | VLLM_BASE_URL |
| API key env var | OLLAMA_API_KEY (optional) | VLLM_API_KEY (optional) |
| Endpoint called | ${OLLAMA_BASE_URL}/v1/chat/completions | ${VLLM_BASE_URL}/v1/chat/completions |
A self-hosted provider is "configured" — and therefore eligible to be referenced by a registry.models[].provider entry — the moment its baseUrl env var is set. The authorization header is only added to outgoing requests when an API key is present, since these deployments commonly run with no auth in front of them at all.
Wiring one into the registry
type ProviderAdapter = {
id: ProviderId;
createChatCompletion(
request: ChatCompletionRequest,
ctx: ProviderRequestContext,
): Promise<ChatCompletionResponse>;
streamChatCompletion(
request: ChatCompletionRequest,
ctx: ProviderRequestContext,
): AsyncGenerator<ChatCompletionChunk, void, void>;
};
type ProviderRequestContext = {
apiKey?: string;
providerModel: string;
signal: AbortSignal;
};providerModel is whatever name your Ollama or vLLM instance serves that model under locally — it doesn't need to match the registry id a client requests.
Streaming and stop reasons
Because both ride the shared openai-compatible request/response/stream mapping, they behave exactly like the OpenAI adapter: stream_options: { include_usage: true } is set on streaming requests so usage is reported on the terminal chunk, and finish_reason is passed through mostly as-is (length/content_filter are special-cased, everything else normalizes to stop).
See Adding a Provider if your self-hosted stack doesn't speak an OpenAI-compatible API and needs a bespoke adapter instead.