Features Apograph CMS on GitHub

OpenAI-compatible

Copilot plugin @apograph/copilot-server@apograph/copilot-admin

Ollama, vLLM, llama.cpp, LM Studio, LiteLLM, Azure, OpenAI — one wire format.

Documents 0.5.2 Updated Edit this page Report a problem

On this page

A ModelProvider speaking the OpenAI chat-completions wire format against a configurable base URL. Ollama, vLLM, llama.cpp, LM Studio, LiteLLM, OpenRouter, Azure and OpenAI itself all speak it.

That is what makes a local, air-gapped install a configuration choice rather than a fork.

No SDK

It depends only on the copilot’s domain package — for the port’s type, erased at runtime — and Node built-ins. The surface is one POST /chat/completions and a few lines of event-stream parsing, and a dependency for that would not earn its place.

Install

npm install @apograph/copilot-provider-openai
import { createOpenAiProvider } from '@apograph/copilot-provider-openai';

createOpenAiProvider(config.plugins.copilot.providers.openai);

Configuration

{ baseUrl, models, apiKey?, headers?, capabilities?, timeoutMs?, maxRetries?, maxTokensField? }

VariableDefaultWhat it does
COPILOT_OPENAI_BASE_URLunsetSetting it registers this backend
COPILOT_OPENAI_MODELSllama3.1Comma-separated. The first is the default
COPILOT_OPENAI_API_KEYemptyLeave empty for a local runtime wanting no auth

baseUrl includes the version segment

http://localhost:11434/v1, not http://localhost:11434.

/chat/completions is appended, with any trailing slashes trimmed first. There is no default: an endpoint nobody named is a backend that can only time out.

Pointing it somewhere

RuntimeCOPILOT_OPENAI_BASE_URL
Ollamahttp://localhost:11434/v1
vLLMhttp://localhost:8000/v1
LM Studiohttp://localhost:1234/v1
OpenAIhttps://api.openai.com/v1
LiteLLM, OpenRouter, Azurewhatever that deployment publishes

The model list is a list

One endpoint and one credential back several models, so a user can switch mid-conversation. A run naming a model outside the list raises an unknown-model error rather than quietly answering on the default.

Two pieces worth knowing about

The retry policy is pure. Which statuses retry, the backoff, and honouring Retry-After live in their own module with their own tests, and it runs pre-stream only — once bytes are flowing, a retry would replay a partial answer.

Tool-call fragments are assembled by index. Parallel tool calls interleave on the wire, arriving as fragments that have to be reassembled per call. That accumulator is the only piece here holding real state, so it is pinned directly rather than covered only through the provider.

capabilities is configurable here

Unlike the Claude adapter, there is no capability probe: the wire format is spoken by dozens of runtimes with different support for tools and streaming, and no endpoint reliably reports its own.

Declare what yours supports, or take the conservative default.