跳到正文
FunCoding

搜索

搜索文档、文章、Skill 和 MCP

Provider runtime hooks and catalogs

The provider hook order table, worked provider example, bundled hook shapes, and model catalog registration

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.

Synthetic-auth lookup includes auth-only discovery entries from the declared provider or CLI backend owner. Static model-catalog rows do not replace those auth implementations. If the owner supplies no synthetic-auth hook, lookup returns no synthetic result without loading unrelated discovery entries. A lightweight entry fallback remains available for aliases with no declared owner. External-auth captures still prepare fresh outcomes before read-only worker work.

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.

HookWhat it doesWhen to use
catalogPublish provider config into models.providers during models.json generationProvider owns a catalog or base URL defaults
applyConfigDefaultsApply provider-owned global config defaults during config materializationDefaults 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)
normalizeModelIdNormalize legacy or preview model-id aliases before lookupProvider owns alias cleanup before canonical model resolution
normalizeTransportNormalize provider-family api / baseUrl before generic model assemblyProvider owns transport cleanup for custom provider ids in the same transport family
normalizeConfigNormalize models.providers.<id> before runtime/provider resolutionProvider needs config cleanup that should live with the owning plugin
applyNativeStreamingUsageCompatApply native streaming-usage compat rewrites to config providersProvider needs endpoint-driven native streaming usage metadata fixes
resolveConfigApiKeyResolve env-marker auth for config providers before runtime auth loadingProviders expose their own env-marker API-key resolution hooks
resolveSyntheticAuthSurface local/self-hosted or config-backed auth without persisting plaintextProvider can operate with a synthetic/local credential marker
resolveExternalAuthProfilesOverlay provider-owned external auth profiles; default persistence is runtime-only for CLI/app-owned credsProvider reuses external auth credentials without persisting copied refresh tokens; declare contracts.externalAuthProviders in the manifest
shouldDeferSyntheticProfileAuthLower stored synthetic profile placeholders behind env/config-backed authProvider stores synthetic placeholder profiles that should not win precedence
resolveDynamicModelSync fallback for provider-owned model ids not in the local registry yetProvider accepts arbitrary upstream model ids
prepareDynamicModelReturn an asynchronously prepared model, or warm reusable metadata before retrying resolveDynamicModelProvider needs network metadata before resolving unknown ids
normalizeResolvedModelFinal rewrite before the embedded runner uses the resolved modelProvider needs transport rewrites but still uses a core transport
normalizeToolSchemasNormalize tool schemas before the embedded runner sees themProvider needs transport-family schema cleanup
inspectToolSchemasSurface provider-owned schema diagnostics after normalizationProvider wants keyword warnings without teaching core provider-specific rules
resolveReasoningOutputModeSelect native vs tagged reasoning-output contractProvider needs tagged reasoning/final output instead of native fields
prepareExtraParamsRequest-param normalization before generic stream option wrappersProvider needs default request params or per-provider param cleanup
createStreamFnFully replace the normal stream path with a custom transportProvider needs a custom wire protocol, not just a wrapper
wrapStreamFnStream wrapper after generic wrappers are appliedProvider needs request headers/body/model compat wrappers without a custom transport
reconcileLocalServiceReconcile provider-owned state after local-service health and before every requestA managed local router must reload durable provider state without moving provider policy into core
resolveTransportTurnStateAttach native per-turn headers, metadata, or WebSocket policyProvider wants generic transports to send provider-native turn identity or tune WebSocket headers and fallback cool-down
resolveWebSocketSessionPolicyDeprecated compatibility hook for WebSocket policyExisting plugins migrate WebSocket fields into resolveTransportTurnState
formatApiKeyAuth-profile formatter: stored profile becomes the runtime apiKey stringProvider stores extra auth metadata and needs a custom runtime token shape
refreshOAuthOAuth refresh override for custom refresh endpoints or refresh-failure policyProvider does not fit the shared OpenClaw refreshers
buildAuthDoctorHintRepair hint appended when OAuth refresh failsProvider needs provider-owned auth repair guidance after refresh failure
matchesContextOverflowErrorProvider-owned context-window overflow matcherProvider has raw overflow errors generic heuristics would miss
classifyFailoverReasonProvider-owned failover reason classificationProvider can map raw API/transport errors to rate-limit/overload/etc
isCacheTtlEligiblePrompt-cache policy for proxy/backhaul providersProvider needs proxy-specific cache TTL gating
buildMissingAuthMessageReplacement for the generic missing-auth recovery messageProvider needs a provider-specific missing-auth recovery hint
augmentModelCatalogSynthetic/final catalog rows appended after discovery (deprecated, see below)Provider needs synthetic forward-compat rows in models list and pickers
resolveThinkingProfileModel-specific /think level set, display labels, and defaultProvider exposes a custom thinking ladder or binary label for selected models
isBinaryThinkingOn/off reasoning toggle compatibility hookProvider exposes only binary thinking on/off
supportsXHighThinkingxhigh reasoning support compatibility hookProvider wants xhigh on only a subset of models
resolveDefaultThinkingLevelDefault /think level compatibility hookProvider owns default /think policy for a model family
isModernModelRefModern-model matcher for live profile filters and smoke selectionProvider owns live/smoke preferred-model matching
prepareRuntimeAuthExchange a configured credential into the actual runtime token/key just before inferenceProvider needs a token exchange or short-lived request credential
resolveUsageAuthResolve usage/billing credentials for /usage and related status surfacesProvider needs custom usage/quota token parsing or a different usage credential
fetchUsageSnapshotFetch and normalize provider-specific usage/quota snapshots after auth is resolvedProvider needs a provider-specific usage endpoint or payload parser
createEmbeddingProviderBuild a provider-owned embedding adapter for memory/searchMemory embedding behavior belongs with the provider plugin
buildReplayPolicyReturn a replay policy controlling transcript handling for the providerProvider needs custom transcript policy (for example, thinking-block stripping)
sanitizeReplayHistoryAsyncRewrite replay history after generic transcript cleanup, awaiting transcript metadataProvider needs provider-specific replay rewrites beyond shared compaction helpers
sanitizeReplayHistoryDeprecated third-party compatibility hookExisting plugins migrating to the awaited hook and its V2 session-state contract
validateReplayTurnsFinal replay-turn validation or reshaping before the embedded runnerProvider transport needs stricter turn validation after generic sanitation
onModelSelectedRun provider-owned post-selection side effectsProvider 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 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.
  • Config assembly calls normalizeConfig and resolveConfigApiKey through the owning bundled provider's lightweight policy surface. It never loads provider runtime, scans other providers' hooks, or falls through after the owning hook returns no change.

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

example-proxy, exchangeToken, and fetchExampleProxyUsage are placeholders for your own provider id and vendor API calls, not exported OpenClaw helpers.

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 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 was a legacy alias for catalog. OpenClaw removed the alias and its deprecation warnings in 2026.4.26
  • rename discovery to catalog. A provider plugin that still registers discovery publishes no catalog rows
  • augmentModelCatalog is deprecated; bundled providers should publish supplemental rows through registerModelCatalogProvider. Its removal gate is 2026-10-01