OpenAI-Compatible Provider Catalog
Every OpenAI-compatible provider in the catalog (80 as of 2026-09-29; the
directory is the list) is registered from one JSON file each, under
src/lib/providers/catalog/<id>.json, and served by one generic class,
ConfiguredOpenAICompatProvider
(src/lib/providers/configuredOpenAICompat.ts). Adding another provider to
this family means adding one JSON file — no subclass, no registry edit, no
hand-written enum member, no test edit.
The JSON is the source of truth
OPENAI_COMPAT_CATALOG still exists and keeps its name and element type,
but it is now built by the loader (src/lib/providers/catalog/loader.ts)
from the JSON files rather than hand-written. Two consumers read the JSON:
- Codegen (
pnpm run codegen:catalog) writes the compile-time artifacts into marked regions — theAIProviderNamemember, the<Name>Modelsenum, theNeurolinkCredentialskey — plus the generated index. Pre-commit and CI fail on stale output. - The loader builds the runtime entry; the descriptor, config options, context windows, pricing, vision map and model-choice tables all derive from it, as do the provider test suites' rows and counts.
Each file is validated by a zod schema (src/lib/providers/catalog/schema.ts)
with a mirrored provider-catalog.schema.json for editor squiggles.
Probe evidence (roster/auth/billing dates, live-matrix result, PR URL)
lives in the file's evidence block — the old
docs/provider-integration/manifests/<id>.json files were folded into it.
Field-by-field reference and the escape hatches:
tiers/tier-2-catalog-entry.md. Design rationale and the approved rulings:
docs/superpowers/plans/2026-08-28-provider-json-catalog-spec.md.
When a provider belongs in the catalog
A provider belongs in the JSON catalog if it needs only:
- a credential (API key, optionally an extra field like Cloudflare's account id)
- a base URL (static default + optional env override, or computed from an extra credential field)
- a default/fallback model
- error-message classification (auth / rate-limit / invalid-model / generic)
- a named, closed catalog quirk. DeepSeek is the worked example: it 400s on
json_schemastructured-output requests, sodeepseek.jsonsetsquirks.responseFormatDowngrade: "json-schema-to-json-object"and the genericConfiguredOpenAICompatProviderdowngrades tojson_objectbefore sending — no subclass. It also setsquirks.replayReasoningContent: true: DeepSeek documents that each assistant turn'sreasoning_contentmust go back on later requests once tools are in play, so the shared message converter and the streaming tool loop send it — for this quirk only, since strict OpenAI-compatible backends reject the unknown field. - a wire-proven capability such as
capabilities.structuredOutputWithTools. The generic provider suppressesresponse_formatwhen tools are attached by default; an explicittruekeeps it on the samegenerate()orstream()request. Set this only after a combined tools-plus-schema request returned successfully. Separate tool and structured-output probes are not evidence for the combined capability. A stale opt-in is still protected by the runtime conflict retry, which drops structured output and retries rather than losing the turn.
The currently opted-in providers are Baseten, DeepSeek, Fireworks AI, GMI Cloud, Inception Labs, io.net Intelligence, Novita AI, Together AI, Upstage and xAI. API Route remains opted out because its evidence verifies the features separately, not together in one request. Mistral remains opted out because its combined probes returned 429 twice, not a successful capability response.
When a provider needs a dedicated subclass instead
Azure OpenAI is deliberately not in the catalog because it overrides real request-shaping behavior that no named catalog quirk expresses:
- Azure OpenAI (
src/lib/providers/azureOpenai.ts) overrides four hooks:getChatCompletionsURL(deployment-name URL routing across two Azure endpoint schemes),getAuthHeaders(Azure'sapi-keyheader instead ofAuthorization: Bearer),adjustRequestBody(renamesmax_tokenstomax_completion_tokensfor o-series/gpt-5+ deployments), andsuppressResponseFormatWithTools(Azure supports both at once).
If a future provider needs any hook beyond the 3 mandatory ones
(getProviderName, getDefaultModel, formatProviderError) or the 2
purely-declarative optional ones (getFallbackModelName,
getFallbackModels) and that hook is not already a named catalog quirk, it
needs either a new closed quirk (the DeepSeek route) or a dedicated subclass
(the Azure OpenAI route).
Error-message fidelity
Each entry's errorRules is a direct, order-preserving translation of its
original subclass's formatProviderError if/else ladder into rule data
(status code and/or case-insensitive pattern), classified via
classifyProviderError()
(src/lib/utils/errorClassifier.ts). Every bespoke message string is
preserved verbatim — including xAI's "top up your account" quota URL and
Groq's decommissioned-vs-not-found distinction — via each rule's own
message field (string | ((ctx) => string)), with model-name
interpolation carried through ctx.modelName. There is no message-wording
regression here. Timeout classification is likewise unchanged: 15 of the 16
providers map TimeoutError to NetworkError (the classifier's default),
and Groq alone maps it to ProviderError. Groq's subclass override is
preserved verbatim via the JSON's quirks.timeoutErrorClass, so no
provider's timeout class changed during migration.
Known pre-existing quirk this migration preserved (not fixed)
Mistral's provider registration passes a defaultModel value to
ProviderFactory.registerProvider() that does not check MISTRAL_MODEL
(MistralModels.MISTRAL_LARGE_LATEST, a bare literal), while
ConfiguredOpenAICompatProvider.getDefaultModel() for Mistral does
check MISTRAL_MODEL (falling back to MistralModels.MISTRAL_SMALL_2506).
Every other catalog provider's registry default and class default agree.
This is expressed via the JSON's quirks.registryDefaultIgnoresModelEnvVar
(true only for Mistral). The JSON migration did reconcile one half of it:
getDefaultModel(MISTRAL) now returns the real generation default
(mistral-small-2506) rather than the registry literal — a disclosed
bug-fix-grade delta, since the two disagreed before.
What comes next
The credential-free growth queue in docs/provider-integration/growth-queue.json lists every
verified candidate vendor by wave (W1 = hosted OpenAI-compatible, the catalog path). Entries
onboarded from it carry evidence.liveMatrix: null until a live run with a real key.