Plugin maintainer reference
Provider runtime hooks and catalogs
The three provider layers, the full hook order table, a worked provider example, the bundled hook shapes, and how provider catalogs merge. Part of the Plugin architecture internals guide.
Provider runtime hooks
Provider plugins have three layers:
- Manifest metadata for cheap pre-runtime lookup:
setup.providers[].envVars,providerAuthAliases,providerAuthChoices, andchannelConfigs. - Config-time hooks:
catalogplusapplyConfigDefaults. - Runtime hooks: 40+ optional hooks covering auth, model resolution, stream wrapping, thinking levels, replay policy, and usage endpoints. See Hook order and usage.
OpenClaw still owns the generic agent loop, failover, transcript handling, and tool policy. These hooks are the extension surface for provider-specific behavior without needing a whole custom inference transport.
Hook lookup uses the prepared generation or a matching loaded registry first. On a miss, provider/model-scoped discovery reuses the loader's registry cache; explicit runtime-discovery invalidation clears that lookup rather than leaving another provider cache holding old hooks. Attempt-prepared provider handles retain their selected plugin, while each hook receives the current call context.
Use manifest setup.providers[].envVars when the provider has env-based
credentials that generic auth/status/model-picker paths should see without
loading plugin runtime. Use manifest providerAuthAliases
when one provider id should reuse another provider id's env vars, auth profiles,
config-backed auth, and API-key onboarding choice. Use manifest
providerAuthChoices when onboarding/auth-choice CLI surfaces should know the
provider's choice id, group labels, and simple one-flag auth wiring without
loading provider runtime. Keep provider runtime
envVars for operator-facing hints such as onboarding labels or OAuth
client-id/client-secret setup vars.
Describe env-driven channel setup and auth through the owning
channelConfigs.<id>.schema and setup descriptors.
Hook order and usage
For model/provider plugins, OpenClaw calls hooks in this rough order.
The "When to use" column is the quick decision guide.
Compatibility-only provider fields that OpenClaw no longer calls, such as
ProviderPlugin.capabilities and suppressBuiltInModel, are intentionally not
listed here.
| Hook | What it does | When to use |
|---|---|---|
catalog |
Publish provider config into models.providers during models.json generation |
Provider owns a catalog or base URL defaults |
applyConfigDefaults |
Apply provider-owned global config defaults during config materialization | Defaults depend on auth mode, env, or provider model-family semantics |
| (built-in model lookup) | OpenClaw tries the normal registry/catalog path first | (not a plugin hook) |
normalizeModelId |
Normalize legacy or preview model-id aliases before lookup | Provider owns alias cleanup before canonical model resolution |
normalizeTransport |
Normalize provider-family api / baseUrl before generic model assembly |
Provider owns transport cleanup for custom provider ids in the same transport family |
normalizeConfig |
Normalize models.providers.<id> before runtime/provider resolution |
Provider needs config cleanup that should live with the owning plugin |
applyNativeStreamingUsageCompat |
Apply native streaming-usage compat rewrites to config providers | Provider needs endpoint-driven native streaming usage metadata fixes |
resolveConfigApiKey |
Resolve env-marker auth for config providers before runtime auth loading | Providers expose their own env-marker API-key resolution hooks |
resolveSyntheticAuth |
Surface local/self-hosted or config-backed auth without persisting plaintext | Provider can operate with a synthetic/local credential marker |
resolveExternalAuthProfiles |
Overlay provider-owned external auth profiles; default persistence is runtime-only for CLI/app-owned creds |
Provider reuses external auth credentials without persisting copied refresh tokens; declare contracts.externalAuthProviders in the manifest |
shouldDeferSyntheticProfileAuth |
Lower stored synthetic profile placeholders behind env/config-backed auth | Provider stores synthetic placeholder profiles that should not win precedence |
resolveDynamicModel |
Sync fallback for provider-owned model ids not in the local registry yet | Provider accepts arbitrary upstream model ids |
prepareDynamicModel |
Return an asynchronously prepared model, or warm reusable metadata before retrying resolveDynamicModel |
Provider needs network metadata before resolving unknown ids |
normalizeResolvedModel |
Final rewrite before the embedded runner uses the resolved model | Provider needs transport rewrites but still uses a core transport |
normalizeToolSchemas |
Normalize tool schemas before the embedded runner sees them | Provider needs transport-family schema cleanup |
inspectToolSchemas |
Surface provider-owned schema diagnostics after normalization | Provider wants keyword warnings without teaching core provider-specific rules |
resolveReasoningOutputMode |
Select native vs tagged reasoning-output contract | Provider needs tagged reasoning/final output instead of native fields |
prepareExtraParams |
Request-param normalization before generic stream option wrappers | Provider needs default request params or per-provider param cleanup |
createStreamFn |
Fully replace the normal stream path with a custom transport | Provider needs a custom wire protocol, not just a wrapper |
wrapStreamFn |
Stream wrapper after generic wrappers are applied | Provider needs request headers/body/model compat wrappers without a custom transport |
reconcileLocalService |
Reconcile provider-owned state after local-service health and before every request | A managed local router must reload durable provider state without moving provider policy into core |
resolveTransportTurnState |
Attach native per-turn headers, metadata, or WebSocket policy | Provider wants generic transports to send provider-native turn identity or tune WebSocket headers and fallback cool-down |
resolveWebSocketSessionPolicy |
Deprecated compatibility hook for WebSocket policy | Existing plugins migrate WebSocket fields into resolveTransportTurnState |
formatApiKey |
Auth-profile formatter: stored profile becomes the runtime apiKey string |
Provider stores extra auth metadata and needs a custom runtime token shape |
refreshOAuth |
OAuth refresh override for custom refresh endpoints or refresh-failure policy | Provider does not fit the shared OpenClaw refreshers |
buildAuthDoctorHint |
Repair hint appended when OAuth refresh fails | Provider needs provider-owned auth repair guidance after refresh failure |
matchesContextOverflowError |
Provider-owned context-window overflow matcher | Provider has raw overflow errors generic heuristics would miss |
classifyFailoverReason |
Provider-owned failover reason classification | Provider can map raw API/transport errors to rate-limit/overload/etc |
isCacheTtlEligible |
Prompt-cache policy for proxy/backhaul providers | Provider needs proxy-specific cache TTL gating |
buildMissingAuthMessage |
Replacement for the generic missing-auth recovery message | Provider needs a provider-specific missing-auth recovery hint |
augmentModelCatalog |
Synthetic/final catalog rows appended after discovery (deprecated, see below) | Provider needs synthetic forward-compat rows in models list and pickers |
resolveThinkingProfile |
Model-specific /think level set, display labels, and default |
Provider exposes a custom thinking ladder or binary label for selected models |
isBinaryThinking |
On/off reasoning toggle compatibility hook | Provider exposes only binary thinking on/off |
supportsXHighThinking |
xhigh reasoning support compatibility hook |
Provider wants xhigh on only a subset of models |
resolveDefaultThinkingLevel |
Default /think level compatibility hook |
Provider owns default /think policy for a model family |
isModernModelRef |
Modern-model matcher for live profile filters and smoke selection | Provider owns live/smoke preferred-model matching |
prepareRuntimeAuth |
Exchange a configured credential into the actual runtime token/key just before inference | Provider needs a token exchange or short-lived request credential |
resolveUsageAuth |
Resolve usage/billing credentials for /usage and related status surfaces |
Provider needs custom usage/quota token parsing or a different usage credential |
fetchUsageSnapshot |
Fetch and normalize provider-specific usage/quota snapshots after auth is resolved | Provider needs a provider-specific usage endpoint or payload parser |
createEmbeddingProvider |
Build a provider-owned embedding adapter for memory/search | Memory embedding behavior belongs with the provider plugin |
buildReplayPolicy |
Return a replay policy controlling transcript handling for the provider | Provider needs custom transcript policy (for example, thinking-block stripping) |
sanitizeReplayHistory |
Rewrite replay history after generic transcript cleanup | Provider needs provider-specific replay rewrites beyond shared compaction helpers |
validateReplayTurns |
Final replay-turn validation or reshaping before the embedded runner | Provider transport needs stricter turn validation after generic sanitation |
onModelSelected |
Run provider-owned post-selection side effects | Provider needs telemetry or provider-owned state when a model becomes active |
reconcileLocalService runs only for configured local services, including a
healthy process reused from outside the current Gateway process. Keep it cheap,
idempotent, and abort-aware. A rejection blocks the provider request and
releases its lease without classifying the healthy process as a startup failure.
Normalization dispatch is hook-specific:
- Model references apply manifest-declared model-ID normalization once before
normalizeModelIddispatch. The matched provider hook can refine that prepared model ID; an empty result keeps it unchanged. OpenClaw does not try other providers' normalization hooks or reapply manifest rules afterward. Reference parsing reads the selected runtime registry without activating plugins. Executable normalization requires a prepared runtime owner; reads without one use static manifest policies only. A directly registered provider owns its ID; compatibility aliases match only when no literal provider exists, preserving alias-only routes and explicit API-owner eligibility. normalizeTransporttries the matched provider first. Only if that does not changeapiorbaseUrland the provider has nomodels.providers.<id>entry does it try other transport hooks, stopping at the first change.normalizeConfiguses the owning bundled provider's lightweight policy surface first. If that surface has nonormalizeConfighook, OpenClaw may call the matched runtime owner, provided runtime loading is allowed and, when a config is supplied, that owner has explicit plugin activation. It never scans other providers' hooks or falls through after the owning hook returns no change. Config assembly passesallowRuntimePluginLoad: false, so it uses bundled policy without loading provider runtime.
Google-family config cleanup is implemented by the Google plugin's own
normalizeConfig hook, shared with its lightweight policy surface. It is not a
separate core compatibility backstop.
If the provider needs a fully custom wire protocol or custom request executor, that is a different class of extension. These hooks are for provider behavior that still runs on OpenClaw's normal inference loop.
resolveUsageAuth decides whether OpenClaw should call fetchUsageSnapshot or
fall back to generic credential resolution for usage/status surfaces. Return
{ token, accountId?, subscriptionType?, rateLimitTier? } when the provider
has a usage credential (the optional plan metadata flows into
fetchUsageSnapshot), return
{ handled: true } when provider-owned usage auth has handled the request and
must suppress generic API-key/OAuth fallback, and return null or undefined
when the provider did not handle usage auth.
Declare organization or billing credentials in manifest
providerUsageAuthEnvVars. This lets generic discovery and secret-scrubbing
surfaces recognize them without making them inference auth candidates.
Provider example
api.registerProvider({ id: "example-proxy", label: "Example Proxy", auth: [], catalog: { order: "simple", run: async (ctx) => { const apiKey = ctx.resolveProviderApiKey("example-proxy").apiKey; if (!apiKey) { return null; } return { provider: { baseUrl: "https://proxy.example.com/v1", apiKey, api: "openai-completions", models: [{ id: "auto", name: "Auto" }], }, }; }, }, resolveDynamicModel: (ctx) => ({ id: ctx.modelId, name: ctx.modelId, provider: "example-proxy", api: "openai-completions", baseUrl: "https://proxy.example.com/v1", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 8192, }), prepareRuntimeAuth: async (ctx) => { const exchanged = await exchangeToken(ctx.apiKey); return { apiKey: exchanged.token, baseUrl: exchanged.baseUrl, expiresAt: exchanged.expiresAt, }; }, resolveUsageAuth: async (ctx) => { const auth = await ctx.resolveOAuthToken(); return auth ? { token: auth.token } : null; }, fetchUsageSnapshot: async (ctx) => { return await fetchExampleProxyUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn); },});Built-in examples
Bundled provider plugins combine the hooks above to fit each vendor's catalog,
auth, thinking, replay, and usage needs. The authoritative hook set lives with
each plugin under extensions/; this page illustrates the shapes rather than
mirroring the list.
Pass-through catalog providers
OpenRouter, Kilocode, Z.AI, xAI register catalog plus
resolveDynamicModel / prepareDynamicModel so they can surface upstream
model ids ahead of OpenClaw's static catalog.
OAuth and usage endpoint providers
GitHub Copilot, Gemini CLI, ChatGPT Codex, MiniMax, Xiaomi, z.ai pair
prepareRuntimeAuth or formatApiKey with resolveUsageAuth +
fetchUsageSnapshot to own token exchange and /usage integration.
Replay and transcript cleanup families
Shared named families (google-gemini, passthrough-gemini,
anthropic-by-model, hybrid-anthropic-openai) let providers opt into
transcript policy via buildReplayPolicy instead of each plugin
re-implementing cleanup.
Catalog-only providers
byteplus, cloudflare-ai-gateway, huggingface, kimi-coding, nvidia,
qianfan, synthetic, together, venice, vercel-ai-gateway, and
volcengine register just catalog and ride the shared inference loop.
Anthropic-specific stream helpers
Beta headers, /fast / serviceTier, and context1m live inside the
Anthropic plugin's public api.ts / contract-api.ts seam
(wrapAnthropicProviderStream, resolveAnthropicBetas,
resolveAnthropicFastMode, resolveAnthropicServiceTier) rather than in
the generic SDK.
Provider catalogs
Provider plugins can define model catalogs for inference with
registerProvider({ catalog: { run(...) { ... } } }).
catalog.run(...) returns the same shape OpenClaw writes into
models.providers:
{ provider }for one provider entry{ providers }for multiple provider entries
Use catalog when the plugin owns provider-specific model ids, base URL
defaults, or auth-gated model metadata.
catalog.order controls when a plugin's catalog merges relative to OpenClaw's
built-in implicit providers:
simple: plain API-key or env-driven providersprofile: providers that appear when auth profiles existpaired: providers that synthesize multiple related provider entrieslate: last pass, after other implicit providers
Later providers win on key collision, so plugins can intentionally override a built-in provider entry with the same provider id.
Plugins can also publish read-only model rows through
api.registerModelCatalogProvider({ provider, kinds, staticCatalog, liveCatalog }). This is the forward path for list/help/picker surfaces and supports
text, voice, image_generation, video_generation, and music_generation
rows. Provider plugins still own live endpoint calls, token exchange, and
vendor response mapping; core owns the common row shape, source labels, and
media tool help formatting. Media-generation provider registrations synthesize
static catalog rows automatically from defaultModel, models, and
capabilities.
Compatibility:
discoverystill works as a legacy alias, but emits a deprecation warning- if both
cataloganddiscoveryare registered, OpenClaw usescatalogand emits a warning augmentModelCatalogis deprecated; bundled providers should publish supplemental rows throughregisterModelCatalogProvider