A provider is described in two halves:
- Catalog half (
packages/catalog): one entry in theCATALOG_PROVIDERStable (packages/catalog/src/provider-models/descriptors.ts) carrying theid,defaultModel, runtime model-discovery factory, and catalog-generation wiring.KnownProvider,PROVIDER_DESCRIPTORS, andDEFAULT_MODEL_PER_PROVIDERare derived from this table. - Auth half (
packages/ai): one declarativeProviderDefinitionin the registry carrying env-key fallbacks and login/refresh flows. TheOAuthProviderunion, the env-key map, the/loginprovider list, therefreshOAuthToken/AuthStorage.logindispatch, and the coding-agent callback maps are derived from the registry.
Scope. This is for a provider that reuses an existing wire API
(openai-completions, anthropic-messages, google-generative-ai, β¦) β the
common case for gateways and API-key providers, since stream dispatch keys on
model.api, not model.provider. Adding a new wire protocol (a new
KnownApi) is a separate task that also touches stream.ts dispatch,
api-registry.ts, and the catalog types.ts.
Shape
For the common case, a provider is one catalog entry + one def file + one registry line:
- Add an entry to
CATALOG_PROVIDERSinpackages/catalog/src/provider-models/descriptors.tswith theid,defaultModel, the plain API-key env var(s) asenvVars, and (usually) acreateModelManagerOptionsfactory. For a simple OpenAI-compatible gateway, build the factory inpackages/catalog/src/provider-models/openai-compat.tsor inline with the exportedcreateSimpleOpenAICompletionsOptions(providerId, baseUrl, config). - Create
packages/ai/src/registry/<id>.tsexporting oneexport const <camelId>Provider = { β¦ } as const satisfies ProviderDefinition;with the auth fields (login, β¦). Plain env-var names live in the catalog entryβsenvVars; setenvKeysonly for computed resolvers (Foundry/ADC/ Bedrock-style probes). - Add it to the
ALLarray inpackages/ai/src/registry/registry.ts(one import + one array entry).ALLorder is the/loginlist order for loginable providers.
That is the full change for:
- env-key-only providers,
- providers with a simple inline API-key login flow,
- most OpenAI-compatible gateways.
For a non-trivial provider-local OAuth flow, put the implementation in
packages/ai/src/registry/oauth/<vendor>.ts and lazy-import it from the def
file. The shared OAuth flow infrastructure it builds on lives in the same
registry/oauth/ directory.
Descriptors, the default-model map, env-key map, login list, and refresh
dispatch all update automatically; the KnownProvider union gains the new id
from the catalog table and OAuthProvider from the registry.
Field reference
Catalog table entry (ProviderCatalogEntry, see
packages/catalog/src/provider-models/descriptor-types.ts for JSDoc):
| Field | Effect |
|---|---|
id | Required. Member of KnownProvider. |
defaultModel | Required. Preferred model when no explicit selection is made. |
envVars | Env var name(s), in order, for the runtime API-key fallback (getEnvApiKey). |
createModelManagerOptions | Runtime model-discovery factory. Present (and not specialModelManager) β appears in PROVIDER_DESCRIPTORS. |
allowUnauthenticated | Runtime creates a model manager even without a key. |
dynamicModelsAuthoritative | Successful discovery replaces bundled models. |
catalogDiscovery | { label, envVars?, oauthProvider?, allowUnauthenticated? } for offline catalog generation (generate-models.ts). envVars here overrides the entry-level list when generation uses different credentials (e.g. cursor). |
specialModelManager | Bespoke runtime factory (google-antigravity / google-gemini-cli / openai-codex); excluded from PROVIDER_DESCRIPTORS. |
Registry definition (ProviderDefinition, see
packages/ai/src/registry/types.ts):
| Field | Effect |
|---|---|
id, name | Required. name shows in the /login list. |
envKeys | Computed env fallback for getEnvApiKey, overriding the catalog entryβs envVars: a var name string or a () => string | undefined resolver. Omit when envVars covers it. |
login | Interactive login. Present β member of OAuthProvider, shown in /login, dispatchable via AuthStorage.login. Returns an api-key string or OAuthCredentials. |
refreshToken | OAuth refresher; omit for static-token providers (the dispatch returns credentials unchanged). |
storeCredentialsAs | Store credentials under a different provider id (e.g. openai-codex-device β openai-codex). |
callbackPort | Present β entry in the auth-broker CALLBACK_PORTS map. |
pasteCodeFlow | OAuth flow needs a pasted code/redirect URL β member of PASTE_CODE_LOGIN_PROVIDERS. |
Conventions
- Use
... as const satisfies ProviderDefinitionso the literalidis preserved for the union derivation. login/refreshTokenfor simple API-key or validation-based flows can live directly in the provider def file (export the named login function there so tests can import it directly).login/refreshTokenfor heavy provider-local OAuth flows MUST reach the adjacentregistry/oauth/*module via a dynamic-import thunk (const { loginX } = await import("./oauth/x"); return loginX(cb);), keeping those flows out of the eager startup graph.- All OAuth code lives under
registry/oauth/: the shared flow infra (callback-server,pkce,google-oauth-shared,types, the runtime APIindex) plus every provider flow, including thegithub-copilot/kimi/openai-codexhelpers reused by the streaming and usage layers. The non-OAuth API-key helpers (api-key-login,api-key-validation) sit beside the def files inregistry/, since they back simple paste-an-API-key logins. - For a simple OpenAI-compatible gateway, build the manager inline with the
exported
createSimpleOpenAICompletionsOptions(providerId, baseUrl, config)β no edits toopenai-compat.tsrequired. - A
ProviderDefinitionmay also be registered at runtime by an extension viaregisterOAuthProvider(theAuthStorage.logindispatcher handles built-ins and extensions through the same path).