Skip to main content
Catalog reference for provider plugins: shared live model discovery, catalog helpers, pricing normalization, and the narrower single-provider entry point. Part of the Building provider plugins guide.

Live model discovery

If your provider exposes an OpenAI-compatible /models API, opt the single-provider helper into shared discovery:
liveModelDiscovery: true is a public Plugin SDK contract with these behaviors: Bundled providers set discoveryMode: "strict" in their catalog options. This code option keeps successful empty results empty and reports failed acquisition through ProviderCatalogResult.outcomes, rather than returning seed models as a successful refresh. HTTP 401/403 produces a catalog-scoped auth-rejected outcome; other acquisition failures produce unavailable. Neither a static catalog nor skipped discovery produces a live outcome. Each outcome carries the profile selected for the actual request, when one supplied its credential. Family providers report each sibling independently. Public metadata requests declare authentication: "none" in discovery options. The prepared request then has no credential or profile identity; its cache key is independent of the configured inference credential. The returned provider configuration still retains its inference credential. External calls that omit discoveryMode retain the advisory contract above. The public Chutes, Hugging Face, KiloCode, and Vercel AI Gateway discovery functions and builders also retain that default. Their bundled catalog hooks pass { discoveryMode: "strict" } explicitly; Hugging Face discovery accepts this options object after its existing timeout argument. The Chutes public default retains its anonymous retry after HTTP 401; strict calls never retry without the selected credential. The strict and advisory paths share the same guarded transport and cache. Custom live builders can use runLiveProviderCatalog at their catalog hook to convert acquisition errors into outcomes. Keep metadata-feed fallback separate from account discovery; do not retry a rejected account request anonymously or substitute seed rows inside a strict builder. Custom catalog hooks may receive optional mode metadata from ctx.resolveProviderApiKey(): api_key, oauth, or token. When present, it describes that lookup’s selected credential. Use it when choosing a vendor authentication scheme; a separate resolveProviderAuth() call may select a different profile. Omitted mode metadata does not change existing callback behavior. ctx.resolveProviderAuth() may set preparationFailed: true when OAuth preparation exhausted its candidates. Do not treat that flag as absent configuration or restart resolution of the same profiles. A hook may still choose another credential source. Its returned provider configuration or explicit outcome remains authoritative; otherwise the catalog owner reports the consumed preparation failure with the attempted profile identities. For a non-Bearer or nonstandard list endpoint, pass options instead of true:
Do not use endpointUrl as an unconditional alternate host. Its requireBaseUrl check is the credential-isolation boundary for providers whose model-list host differs from their inference host. If the provider needs custom model semantics rather than the conservative OpenAI-compatible projection, keep only that projection in the plugin. Pass it as projectRows; the shared runtime still owns guarded fetches, provider-auth headers, cache admission, and static fallback. Use buildLiveModelProviderConfig when the live API only tells you which provider-owned static catalog rows are currently available:
index.ts
run should stay auth-gated and return null when no usable credential is available. Keep an offline staticRun or static fallback so setup, docs, tests, and picker surfaces do not depend on live network access. Use a TTL appropriate for model-list freshness, avoid request-time filesystem polling, and pass a provider-specific readRows / readModelId only when the upstream response is not an OpenAI-compatible { data: [{ id, object }] } shape. For a separate authoritative metadata feed, the same provider-catalog-live-runtime subpath exposes ProviderCatalogSnapshot: each entry pairs a runtime model with its lifecycle status. projectUpstreamProviderCatalogSnapshot rebuilds that snapshot from a trusted seed and accepted upstream rows, dropping withdrawn upstream-only models. projectProviderCatalogSnapshotRows intersects advertised IDs with active snapshot entries, deduplicating in endpoint order; listProviderCatalogSnapshotEntries projects the same lifecycle facts for catalog consumers. Keep seed lifecycle policy and model-specific decoration in the owning plugin. Derive static fallback eligibility after refreshing metadata so the first failed or fully filtered discovery uses current status. Public metadata never establishes account entitlement or expands the credential scope of discovery. Official plugins use the private, pure openclaw/plugin-sdk/model-catalog-pricing runtime subpath. It exposes normalizeModelPricingCatalog(rows, normalizePricing, options?) for provider-owned pricing feeds. It returns a map of complete costs: absent prices are omitted, while malformed declared prices, invalid or duplicate model IDs, and a feed with no usable prices return undefined. Supply the provider’s unit conversion. Options can select readModelId(model) (default model.id), readPricing(model) (default model.pricing), and isSupportedPricing(rawPricing) (default true). Declared prices are normalized and validated before unsupported schedules are omitted; duplicate IDs are rejected even on unpriced or unsupported rows. Non-token domains can return undefined from readPricing. No auth, discovery, or runtime loader is imported. DeepInfra’s pricing-api.ts uses these selectors for its native array and model_name identities. Release plugins using the options contract (including DeepInfra and Venice) with a matching host, and coordinate their plugin API and minimum-host floors at release time. The private subpath is not an independently versioned third-party compatibility API. This subpath also exposes normalizeOpenRouterModelPricing(pricing) for native OpenRouter pricing objects. It converts per-token rates and static prompt-length overrides into a complete per-million cost schedule, without network access or prices from another source. Overrides apply strictly above min_prompt_tokens, counting uncached input, cache reads, and cache writes. Matching entries apply in source order: later entries win per price key, including at equal thresholds; omitted keys inherit the native base or an earlier matching entry. Cache rates absent from the base default to zero. Invalid effective token rates return undefined. Entries with time-based or unknown conditions are skipped; other known charge dimensions are ignored. When ctx.providerIds is present, it contains the normalized provider identities selected for that catalog owner. Return null before resolving credentials or making network requests when the hook serves none of them; OpenClaw also filters returned identities to that scope. An absent scope means the caller requested the full catalog. If the upstream provider uses different control tokens than OpenClaw, add a small bidirectional text transform instead of replacing the stream path:
input rewrites the final system prompt and text message content before transport. output rewrites assistant text deltas and final text before OpenClaw parses its own control markers or channel delivery. For bundled providers that only register one text provider with API-key auth plus a single catalog-backed runtime, prefer the narrower defineSingleProviderPluginEntry(...) helper:
buildProvider is the live catalog path used when OpenClaw can resolve real provider auth. It may perform provider-specific discovery. Use buildStaticProvider only for offline rows that are safe to show before auth is configured; it must not require credentials or make network requests. OpenClaw’s models list --all display currently executes static catalogs only for bundled provider plugins, with an empty config, empty env, and no agent/workspace paths. If your auth flow also needs to patch models.providers.*, aliases, and the agent default model during onboarding, use the preset helpers from openclaw/plugin-sdk/provider-onboard. The narrowest helpers are createDefaultModelPresetAppliers(...), createDefaultModelsPresetAppliers(...), and createModelCatalogPresetAppliers(...). When a provider’s native endpoint supports streamed usage blocks on the normal openai-completions transport, prefer the shared catalog helpers in openclaw/plugin-sdk/provider-catalog-shared instead of hardcoding provider-id checks. supportsNativeStreamingUsageCompat(...) and applyProviderNativeStreamingUsageCompat(...) detect support from the endpoint capability map, so native Moonshot/DashScope-style endpoints still opt in even when a plugin is using a custom provider id. The live discovery examples above cover /models-style provider APIs. Keep that discovery inside catalog.run, gated on usable auth, and keep staticRun network-free for offline catalog generation. Official provider plugins that share credentials can use resolveFirstProviderCatalogAuth(ctx.resolveProviderApiKey, providerIds) from the private runtime openclaw/plugin-sdk/provider-catalog-shared subpath. Keep provider precedence in the caller’s ordered IDs. The helper stops at the first result with an apiKey or discoveryApiKey and returns that whole result, preserving its profile and auth mode. An unresolved SecretRef marker takes precedence over another provider’s live key; fields are never mixed across accounts. It returns undefined when no provider has auth and propagates lookup failures. Official plugin releases using this host export must require a host version that provides it in their compat.pluginApi.