Provider hook wiring
Per-hook provider wiring for auth exchange, headers, transport identity, usage, and the hook order table
Wire individual provider hooks when a family builder does not cover the behavior. Part of the Building provider plugins guide; start with Provider hook families for the shared builders.
Model route policy
The lightweight provider-policy-api artifact resolves model routes through
resolveModelRoutes. Its context and result types are exported by
openclaw/plugin-sdk/provider-model-types.
ProviderResolveModelRoutesContext.routeIntent carries prepared, secret-free
consumer intent: an optional runtimeId, an optional authRequirement
("subscription" or "api-key"), and source ("explicit" or "inherited").
The host projects existing model/provider policy and inherited agent defaults;
plugins must not reload config or credentials to reconstruct it. This fact does
not grant credential access or change which runtimes can execute a route.
A ProviderModelRouteResolution with kind: "routes" can set
preferredAuthRequirement. Core applies that preference only when both
authentication classes have eligible profiles and selection is automatic.
Preparation and availability apply the same precedence: required consumer or
provider profile bindings select the account; configured provider authentication
constrains automatic selection to that billing route; explicit auth order ranks
the remaining eligible profiles. Inherited routeIntent and
preferredAuthRequirement only break ties after those choices. An environment
credential supplies fallback material without clearing configured authentication;
its mode is inferred only when no mode is configured. A preference does not create
a credential or make an unavailable or cooldown-blocked profile eligible.
Single-class selection keeps its existing behavior.
Candidate order remains separate from credential precedence, and
runtimePolicy.compatibleIds continues to describe execution compatibility.
For example, OpenAI keeps both routes available for supported models using a
legacy official Completions adapter, prefers subscription authentication when
both kinds are eligible, and honors explicit API route intent. These are
additive fields on the existing contract; they add no hook or user setting.
Credential lookup cancellation
Credential consumers using resolveApiKeyForProvider from
openclaw/plugin-sdk/provider-auth-runtime should pass their request's optional
signal. It ends the caller's wait for queued admission, a profile lock, or
OAuth settlement, not an already-claimed refresh's credential write. Started lock
acquisition remains owned through cleanup. Preserve non-missing authentication
errors rather than converting every failure into an absent API key.
buildTimeoutAbortSignal from openclaw/plugin-sdk/extension-shared combines a
caller signal with an operation timeout. Start it before credential preparation
when authentication shares the request budget, and call its cleanup in
finally to release the timer.
Hook examples
Token exchange
For providers that need a token exchange before each inference call:
prepareRuntimeAuth: async (ctx) => {
const exchanged = await exchangeToken(ctx.apiKey);
return {
apiKey: exchanged.token,
baseUrl: exchanged.baseUrl,
expiresAt: exchanged.expiresAt,
};
},Custom headers
For providers that need custom request headers or body modifications:
// wrapStreamFn returns a StreamFn derived from ctx.streamFn
wrapStreamFn: (ctx) => {
if (!ctx.streamFn) return undefined;
const inner = ctx.streamFn;
return (model, context, options) =>
inner(model, context, {
...options,
headers: {
...options?.headers,
"X-Acme-Version": "2",
},
});
},Existing wrappers may still pass the deprecated maxRetries stream option,
including 0. Built-in text transports ignore it: the embedded runner owns
retry budgeting, and SDK-internal retries stay disabled. New wrappers should
omit the option. This shipped source contract is retained until a future
Plugin SDK major release and a published-plugin reader sweep confirm removal
is safe; it does not change image-generation or native-runtime retry policy.
Native transport identity
For providers that need native request/session headers or metadata on generic HTTP or WebSocket transports:
resolveTransportTurnState: (ctx) => ({
headers: {
"x-request-id": ctx.turnId,
},
metadata: {
session_id: ctx.sessionId ?? "",
turn_id: ctx.turnId,
},
websocket: {
headers: {
"x-session-id": ctx.sessionId ?? "",
},
degradeCooldownMs: 60_000,
},
}),The older resolveWebSocketSessionPolicy hook remains supported but is
deprecated. Move its fields under resolveTransportTurnState.websocket;
fields from the new hook take precedence during migration. The hook carries
a TypeScript @deprecated annotation only: it has no compatibility-registry
record and therefore no published removal date. See the removal
timeline for the surfaces that do.
Usage and billing
For providers that expose usage/billing data:
resolveUsageAuth: async (ctx) => {
const auth = await ctx.resolveOAuthToken();
return auth ? { token: auth.token } : null;
},
fetchUsageSnapshot: async (ctx) => {
// fetchAcmeUsage is your plugin's own vendor API call, not an SDK export.
return await fetchAcmeUsage(ctx.token, ctx.timeoutMs, {
fetch: ctx.fetchFn,
signal: ctx.signal,
});
},Both usage hooks receive an optional ctx.signal for collection cancellation.
ctx.fetchFn already combines it with request cancellation; custom transports
must forward ctx.signal to their I/O. Check cancellation before starting
additional auth work after an await. An exhausted budget invokes neither hook
and produces a visible Timeout snapshot. Core retains completed siblings and
tracks unfinished work through cleanup, including auth-owned credential refresh.
resolveUsageAuth has three outcomes. Return
{ token, accountId?, subscriptionType?, rateLimitTier? } when the
provider has a usage/billing credential (the optional fields carry
non-secret plan metadata from the resolved profile into
fetchUsageSnapshot). Return
{ handled: true } only when the provider has definitively handled usage
auth but has no usable usage token, and OpenClaw must skip generic
API-key/OAuth fallback. Return null or undefined when the provider did
not handle the request and OpenClaw should continue with generic fallback.
Declare the provider id in contracts.usageProviders. When that manifest
contract and both hooks are present, OpenClaw automatically includes
the provider in usage collection without loading unrelated provider
plugins. No core allowlist update is required.
fetchUsageSnapshot returns the shared provider-neutral shape:
plan: provider-reported subscription or key labelwindows: resettable quota windows as used percentagesbilling: typedbalance,spend, orbudgetentries;unitcan be an ISO currency or a provider unit such ascreditssummary: compact provider-specific context that does not fit those structured fields
Keep currency semantics exact. A provider credit is not USD unless the
upstream contract says so. A plugin that implements only
fetchUsageSnapshot remains available for explicit/synthetic callers but
is not auto-discovered, because OpenClaw cannot resolve its usage credential.
Set supportsSystemPromptCacheBoundary: true on a provider registration
only when its createStreamFn transport understands the stable/dynamic
system-prompt boundary. Use splitSystemPromptCacheBoundary from
openclaw/plugin-sdk/provider-transport-runtime to checkpoint the stable
prefix separately, and consume the marker before sending any payload.
Use stripSystemPromptCacheBoundary when caching is disabled. By default,
OpenClaw strips the marker before invoking a custom transport.
For custom createStreamFn transports that accumulate JSON tool arguments,
use createToolArgumentPreviewSchedule() from openclaw/plugin-sdk/llm.
Create one schedule per tool call and pass the accumulated raw string's
length to it before calling parseStreamingJson. The returned function
admits preview refreshes at geometric growth checkpoints, so intermediate
arguments snapshots can remain unchanged while raw fragments arrive.
Keep emitting every raw delta and validate the complete arguments at the
transport's terminal boundary, even when the last preview was not refreshed.
Common provider hooks
OpenClaw calls hooks in roughly this order for model/provider plugins.
Most providers only use 2-3. This is not the full ProviderPlugin
contract - see Internals: Provider Runtime
Hooks for the
complete, currently-accurate hook list and fallback notes.
Compatibility-only provider fields that OpenClaw no longer calls, such as
ProviderPlugin.capabilities and suppressBuiltInModel, are not listed
here.
Keep resolveSyntheticAuth synchronous and bounded. External process/network login
checks belong in prepareSyntheticAuth, which receives the captured config,
environment, and cancellation signal and returns a synthetic auth result or
no result. OpenClaw retains completed availability within that preparation
generation. Read-only workers receive the final provider-ref outcome (including
unavailable), preserving alias precedence without rerunning external checks.
Cancelled preparation must reject after cleanup, not report a missing login.
| Hook | When to use |
|---|---|
catalog | Model catalog or base URL defaults |
applyConfigDefaults | Provider-owned global defaults during config materialization |
normalizeModelId | Legacy/preview model-id alias cleanup before lookup |
normalizeTransport | Provider-family api / baseUrl cleanup before generic model assembly |
normalizeConfig | Normalize models.providers.<id> config |
applyNativeStreamingUsageCompat | Native streaming-usage compat rewrites for config providers |
resolveConfigApiKey | Provider-owned env-marker auth resolution |
resolveSyntheticAuth | Local/self-hosted or config-backed synthetic auth |
prepareSyntheticAuth | Asynchronously verify external auth before synchronous availability reads |
resolveExternalAuthProfiles | Overlay provider-owned external auth profiles for CLI/app-managed credentials |
shouldDeferSyntheticProfileAuth | Lower synthetic stored-profile placeholders behind env/config auth |
resolveDynamicModel | Accept arbitrary upstream model IDs |
prepareDynamicModel | Return an asynchronously discovered model, or warm reusable metadata before sync resolution |
normalizeResolvedModel | Transport rewrites before the runner |
normalizeToolSchemas | Provider-owned tool-schema cleanup before registration |
inspectToolSchemas | Provider-owned tool-schema diagnostics |
resolveReasoningOutputMode | Tagged vs native reasoning-output contract |
prepareExtraParams | Default request params |
createStreamFn | Fully custom StreamFn transport |
wrapStreamFn | Custom headers/body wrappers on the normal stream path |
reconcileLocalService | Cheap, idempotent managed-service repair after health and before every request |
resolveTransportTurnState | Native per-turn headers/metadata and WebSocket headers/cool-down |
resolveWebSocketSessionPolicy | Deprecated WebSocket compatibility hook; use resolveTransportTurnState |
formatApiKey | Custom runtime token shape |
loginOAuth | Callback-based OAuth login for the session SDK AuthStorage API |
refreshOAuth | Custom OAuth refresh |
buildAuthDoctorHint | Auth repair guidance |
matchesContextOverflowError | Provider-owned overflow detection |
classifyFailoverReason | Provider-owned rate-limit/overload classification |
isCacheTtlEligible | Prompt cache TTL gating |
buildMissingAuthMessage | Custom missing-auth hint |
augmentModelCatalog | Synthetic forward-compat rows (deprecated - prefer registerModelCatalogProvider) |
resolveThinkingProfile | Model-specific /think option set |
isBinaryThinking | Binary thinking on/off compatibility (deprecated - prefer resolveThinkingProfile) |
supportsXHighThinking | xhigh reasoning support compatibility (deprecated - prefer resolveThinkingProfile) |
resolveDefaultThinkingLevel | Default /think policy compatibility (deprecated - prefer resolveThinkingProfile) |
isModernModelRef | Live/smoke model matching |
prepareRuntimeAuth | Token exchange before inference |
resolveUsageAuth | Custom usage credential parsing |
fetchUsageSnapshot | Custom usage endpoint |
createEmbeddingProvider | Provider-owned embedding adapter for memory/search |
buildReplayPolicy | Custom transcript replay/compaction policy |
sanitizeReplayHistoryAsync | Provider-specific replay rewrites with awaited transcript metadata after generic cleanup |
sanitizeReplayHistory | Deprecated third-party replay compatibility hook; migrate to sanitizeReplayHistoryAsync |
validateReplayTurns | Strict replay-turn validation before the embedded runner |
onModelSelected | Post-selection callback (e.g. telemetry) |
reconcileLocalService is called only for a configured local service,
including a healthy process reused by a restarted Gateway. Honor its
abort signal and reject when reconciliation fails; OpenClaw blocks the
provider request and releases the request lease.
Runtime fallback notes:
isCacheTtlEligible(ctx)receivesprovider,modelId, optionalmodelApi, and the resolved route factsbaseUrlandsupportsPromptCacheKey. The same bounded context is used when installing cache-TTL pruning and recording cache touches; it does not include the full model, headers, or extra request parameters. OpenAI defaults to eligible on official Platform and Codex endpoints, honors an explicitsupportsPromptCacheKey: false, and requires explicit opt-in for custom proxy routes. This controls client-side idle pruning, not a guarantee of a provider cache hit.- Error classification uses the prepared provider owner or already loaded provider hooks.
matchesContextOverflowErrorandclassifyFailoverReasonnever trigger plugin discovery while handling an error; provider preparation owns loading those hooks. - Config assembly calls
normalizeConfigonly through the owning bundled provider's lightweight policy surface, without loading provider runtime or scanning other providers. Google's own hook normalizesgoogle/google-vertex/google-antigravityconfig entries through that surface. - Config assembly likewise uses the lightweight policy surface for
resolveConfigApiKey. Amazon Bedrock keeps AWS env-marker resolution in its provider plugin; runtime auth itself still uses the AWS SDK default chain when configured withauth: "aws-sdk". resolveThinkingProfile(ctx)receives the selectedprovider,modelId, optional catalog route factsapiandbaseUrl, optional mergedreasoningcatalog hint, and optional merged modelcompatfacts. Usecompatonly to select the provider's thinking UI/profile.normalizeResolvedModel(ctx)can setcompactionThinkingDefaulton the returnedProviderRuntimeModelwhen the provider has a preferred embedded-summary effort. This is prepared runtime metadata, not an operator setting or catalog field. Explicitagents.defaults.compaction.thinkingLeveltakes precedence; otherwise the host uses this preference and thenlow. The chosen effort is still clamped to the actual compaction candidate.resolveSystemPromptContributionlets a provider inject cache-aware system-prompt guidance for a model family. Prefer it over the legacy plugin-widebefore_prompt_buildhook when the behavior belongs to one provider/model family and should preserve the stable/dynamic cache split.
Bundled HTTP adapters can preserve numeric response status with
createProviderHttpError from the private-local openclaw/plugin-sdk/provider-http
entrypoint. Adapters that already bound and redact their diagnostics can construct
ProviderHttpError(message, { status }). Keep that error instance when adjusting
its message so status and retry metadata survive; search tools use those fields
for safe authentication and quota guidance without exposing response bodies.
Bundled and trusted official provider policies can use
resolveEffortThinkingProfile(compat?.supportedReasoningEfforts) from the
private openclaw/plugin-sdk/provider-thinking-runtime helper. It accepts
exact off, minimal, low, medium, high, xhigh, and max values,
maps none to off, and prepends off while preserving the first occurrence
of each remaining level. The default preference is medium, high, low,
then off. Missing, null, or empty metadata returns undefined; a nonempty
list without supported values returns an off-only profile. Keep model-specific
overrides and API fallbacks in the provider policy.
Provider stream adapters can call resolveOpenAIRequestReasoning(model, level)
from openclaw/plugin-sdk/llm to resolve declared efforts, native-label maps,
logical Off, and scalar-effort disablement through the shared transport owner.
Translate its effort and thinkingEnabled results into the provider's wire
dialect instead of defining another effort ladder. Read the level from each
stream call's options before falling back to the wrapper context.
Bundled and trusted official plugins can also export
resolveToolSearchMode(ctx) from their lightweight provider-policy-api
artifact. The context contains the final provider, modelId, api, and
optional baseUrl; its type is exported from
openclaw/plugin-sdk/provider-model-types. Return "tools" to prefer
structured Tool Search, false to veto the managed-local-service default,
or undefined to leave that decision to the host. The host records the
result on the resolved runtime model rather than writing configuration.
Explicit tools.toolSearch settings take precedence. This hook changes
schema exposure, not tool permissions or availability.
resolveNativeWebSearch(ctx) can be exported from the same policy artifact
when a provider supplies hosted search. Its ProviderNativeWebSearchPolicyContext
(from openclaw/plugin-sdk/provider-model-types) contains config, provider,
optional modelId, api, and baseUrl. Return true only when that route
will inject hosted search; share this policy with payload construction. Keep
the hook synchronous and free of runtime activation or credential checks.
The host applies tool permissions independently and removes managed
web_search before building Tool Search and Code Mode catalogs. Explicit
managed-provider selection must remain authoritative.
resolveFastModeSupport(ctx) can be exported from the same policy artifact
and registered on the provider. Return false only for a confirmed no-op
Fast choice, true for an applicable local request mapping, or undefined
when facts are missing. ProviderFastModePolicyContext carries the selected
model, route, auth mode, runtime, request parameters and transport policy;
credentials are not included. Share the policy with request construction.
The host publishes only supportsFastMode, preserving unknown behavior
and clearing saved preferences. This describes local applicability, not
upstream entitlement or fulfillment, and does not reject /fast commands.
resolveServiceTiers(ctx) can publish known model/route tier restrictions through
the same lightweight artifact and provider registration. It receives
ProviderFastModePolicyContext; return undefined when the provider has no
restriction to add. The API-key OpenAI Responses catalog intersects a returned
list with account observations or its route defaults, preserving "default"
as Standard processing. A false Fast capability together with ["default"]
keeps the Control UI on disabled Standard controls without confusing an explicit
configured tier with a model limitation. Share this capability decision with the
provider request builder; it does not itself alter configuration or grant access.