Skip to main content
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, and channelConfigs.
  • Config-time hooks: catalog plus applyConfigDefaults.
  • 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. 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 normalizeModelId dispatch. 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.
  • normalizeTransport tries the matched provider first. Only if that does not change api or baseUrl and the provider has no models.providers.<id> entry does it try other transport hooks, stopping at the first change.
  • normalizeConfig uses the owning bundled provider’s lightweight policy surface first. If that surface has no normalizeConfig hook, 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 passes allowRuntimePluginLoad: 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

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.
OpenRouter, Kilocode, Z.AI, xAI register catalog plus resolveDynamicModel / prepareDynamicModel so they can surface upstream model ids ahead of OpenClaw’s static catalog.
GitHub Copilot, Gemini CLI, ChatGPT Codex, MiniMax, Xiaomi, z.ai pair prepareRuntimeAuth or formatApiKey with resolveUsageAuth + fetchUsageSnapshot to own token exchange and /usage integration.
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.
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.
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 providers
  • profile: providers that appear when auth profiles exist
  • paired: providers that synthesize multiple related provider entries
  • late: 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:
  • discovery still works as a legacy alias, but emits a deprecation warning
  • if both catalog and discovery are registered, OpenClaw uses catalog and emits a warning
  • augmentModelCatalog is deprecated; bundled providers should publish supplemental rows through registerModelCatalogProvider