6.2 KiB
Adding a provider
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).