feat(plugins): move provider runtimes into bundled plugins

This commit is contained in:
Peter Steinberger
2026-03-15 15:17:54 -07:00
parent 14137bef22
commit 4a0f72866b
36 changed files with 2089 additions and 518 deletions

View File

@@ -1,12 +1,17 @@
import type { IncomingMessage, ServerResponse } from "node:http";
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { StreamFn } from "@mariozechner/pi-agent-core";
import type { Api, Model } from "@mariozechner/pi-ai";
import type { ModelRegistry } from "@mariozechner/pi-coding-agent";
import type { Command } from "commander";
import type {
ApiKeyCredential,
AuthProfileCredential,
OAuthCredential,
} from "../agents/auth-profiles/types.js";
import type { ProviderCapabilities } from "../agents/provider-capabilities.js";
import type { AnyAgentTool } from "../agents/tools/common.js";
import type { ThinkLevel } from "../auto-reply/thinking.js";
import type { ReplyPayload } from "../auto-reply/types.js";
import type { ChannelDock } from "../channels/dock.js";
import type { ChannelId, ChannelPlugin } from "../channels/plugins/types.js";
@@ -166,9 +171,9 @@ export type ProviderAuthMethod = {
) => Promise<OpenClawConfig | null>;
};
export type ProviderDiscoveryOrder = "simple" | "profile" | "paired" | "late";
export type ProviderCatalogOrder = "simple" | "profile" | "paired" | "late";
export type ProviderDiscoveryContext = {
export type ProviderCatalogContext = {
config: OpenClawConfig;
agentDir?: string;
workspaceDir?: string;
@@ -179,17 +184,168 @@ export type ProviderDiscoveryContext = {
};
};
export type ProviderDiscoveryResult =
export type ProviderCatalogResult =
| { provider: ModelProviderConfig }
| { providers: Record<string, ModelProviderConfig> }
| null
| undefined;
export type ProviderPluginDiscovery = {
order?: ProviderDiscoveryOrder;
run: (ctx: ProviderDiscoveryContext) => Promise<ProviderDiscoveryResult>;
export type ProviderPluginCatalog = {
order?: ProviderCatalogOrder;
run: (ctx: ProviderCatalogContext) => Promise<ProviderCatalogResult>;
};
/**
* Fully-resolved runtime model shape used by the embedded runner.
*
* Catalog hooks publish config-time `models.providers` entries.
* Runtime hooks below operate on the final `pi-ai` model object after
* discovery/override merging, just before inference runs.
*/
export type ProviderRuntimeModel = Model<Api>;
export type ProviderRuntimeProviderConfig = {
baseUrl?: string;
api?: ModelProviderConfig["api"];
models?: ModelProviderConfig["models"];
headers?: unknown;
};
/**
* Sync hook for provider-owned model ids that are not present in the local
* registry/catalog yet.
*
* Use this for pass-through providers or provider-specific forward-compat
* behavior. The hook should be cheap and side-effect free; async refreshes
* belong in `prepareDynamicModel`.
*/
export type ProviderResolveDynamicModelContext = {
config?: OpenClawConfig;
agentDir?: string;
workspaceDir?: string;
provider: string;
modelId: string;
modelRegistry: ModelRegistry;
providerConfig?: ProviderRuntimeProviderConfig;
};
/**
* Optional async warm-up for dynamic model resolution.
*
* Called only from async model resolution paths, before retrying
* `resolveDynamicModel`. This is the place to refresh caches or fetch provider
* metadata over the network.
*/
export type ProviderPrepareDynamicModelContext = ProviderResolveDynamicModelContext;
/**
* Last-chance rewrite hook for provider-owned transport normalization.
*
* Runs after OpenClaw resolves an explicit/discovered/dynamic model and before
* the embedded runner uses it. Typical uses: swap API ids, fix base URLs, or
* patch provider-specific compat bits.
*/
export type ProviderNormalizeResolvedModelContext = {
config?: OpenClawConfig;
agentDir?: string;
workspaceDir?: string;
provider: string;
modelId: string;
model: ProviderRuntimeModel;
};
/**
* Runtime auth input for providers that need an extra exchange step before
* inference. The incoming `apiKey` is the raw credential resolved from auth
* profiles/env/config. The returned value should be the actual token/key to use
* for the request.
*/
export type ProviderPrepareRuntimeAuthContext = {
config?: OpenClawConfig;
agentDir?: string;
workspaceDir?: string;
env: NodeJS.ProcessEnv;
provider: string;
modelId: string;
model: ProviderRuntimeModel;
apiKey: string;
authMode: string;
profileId?: string;
};
/**
* Result of `prepareRuntimeAuth`.
*
* `apiKey` is required and becomes the runtime credential stored in auth
* storage. `baseUrl` is optional and lets providers like GitHub Copilot swap to
* an entitlement-specific endpoint at request time. `expiresAt` enables generic
* background refresh in long-running turns.
*/
export type ProviderPreparedRuntimeAuth = {
apiKey: string;
baseUrl?: string;
expiresAt?: number;
};
/**
* Provider-owned extra-param normalization before OpenClaw builds its generic
* stream option wrapper.
*
* Use this to set provider defaults or rewrite provider-specific config keys
* into the merged `extraParams` object. Return the full next extraParams object.
*/
export type ProviderPrepareExtraParamsContext = {
config?: OpenClawConfig;
agentDir?: string;
workspaceDir?: string;
provider: string;
modelId: string;
extraParams?: Record<string, unknown>;
thinkingLevel?: ThinkLevel;
};
/**
* Provider-owned stream wrapper hook after OpenClaw applies its generic
* transport-independent wrappers.
*
* Use this for provider-specific payload/header/model mutations that still run
* through the normal `pi-ai` stream path.
*/
export type ProviderWrapStreamFnContext = ProviderPrepareExtraParamsContext & {
streamFn?: StreamFn;
};
/**
* Provider-owned prompt-cache eligibility.
*
* Return `true` or `false` to override OpenClaw's built-in provider cache TTL
* detection for this provider. Return `undefined` to fall back to core rules.
*/
export type ProviderCacheTtlEligibilityContext = {
provider: string;
modelId: string;
};
/**
* @deprecated Use ProviderCatalogOrder.
*/
export type ProviderDiscoveryOrder = ProviderCatalogOrder;
/**
* @deprecated Use ProviderCatalogContext.
*/
export type ProviderDiscoveryContext = ProviderCatalogContext;
/**
* @deprecated Use ProviderCatalogResult.
*/
export type ProviderDiscoveryResult = ProviderCatalogResult;
/**
* @deprecated Use ProviderPluginCatalog.
*/
export type ProviderPluginDiscovery = ProviderPluginCatalog;
export type ProviderPluginWizardOnboarding = {
choiceId?: string;
choiceLabel?: string;
@@ -227,7 +383,93 @@ export type ProviderPlugin = {
aliases?: string[];
envVars?: string[];
auth: ProviderAuthMethod[];
/**
* Preferred hook for plugin-defined provider catalogs.
* Returns provider config/model definitions that merge into models.providers.
*/
catalog?: ProviderPluginCatalog;
/**
* Legacy alias for catalog.
* Kept for compatibility with existing provider plugins.
*/
discovery?: ProviderPluginDiscovery;
/**
* Sync runtime fallback for model ids not present in the local catalog.
*
* Hook order:
* 1. discovered/static model lookup
* 2. plugin `resolveDynamicModel`
* 3. core fallback heuristics
* 4. generic provider-config fallback
*
* Keep this hook cheap and deterministic. If you need network I/O first, use
* `prepareDynamicModel` to prime state for the async retry path.
*/
resolveDynamicModel?: (
ctx: ProviderResolveDynamicModelContext,
) => ProviderRuntimeModel | null | undefined;
/**
* Optional async prefetch for dynamic model resolution.
*
* OpenClaw calls this only from async model resolution paths. After it
* completes, `resolveDynamicModel` is called again.
*/
prepareDynamicModel?: (ctx: ProviderPrepareDynamicModelContext) => Promise<void>;
/**
* Provider-owned transport normalization.
*
* Use this to rewrite a resolved model without forking the generic runner:
* swap API ids, update base URLs, or adjust compat flags for a provider's
* transport quirks.
*/
normalizeResolvedModel?: (
ctx: ProviderNormalizeResolvedModelContext,
) => ProviderRuntimeModel | null | undefined;
/**
* Static provider capability overrides consumed by shared transcript/tooling
* logic.
*
* Use this when the provider behaves like OpenAI/Anthropic, needs transcript
* sanitization quirks, or requires provider-family hints.
*/
capabilities?: Partial<ProviderCapabilities>;
/**
* Provider-owned extra-param normalization before generic stream option
* wrapping.
*
* Typical uses: set provider-default `transport`, map provider-specific
* config aliases, or inject extra request metadata sourced from
* `agents.defaults.models.<provider>/<model>.params`.
*/
prepareExtraParams?: (
ctx: ProviderPrepareExtraParamsContext,
) => Record<string, unknown> | null | undefined;
/**
* Provider-owned stream wrapper applied after generic OpenClaw wrappers.
*
* Typical uses: provider attribution headers, request-body rewrites, or
* provider-specific compat payload patches that do not justify a separate
* transport implementation.
*/
wrapStreamFn?: (ctx: ProviderWrapStreamFnContext) => StreamFn | null | undefined;
/**
* Runtime auth exchange hook.
*
* Called after OpenClaw resolves the raw configured credential but before the
* runner stores it in runtime auth storage. This lets plugins exchange a
* source credential (for example a GitHub token) into a short-lived runtime
* token plus optional base URL override.
*/
prepareRuntimeAuth?: (
ctx: ProviderPrepareRuntimeAuthContext,
) => Promise<ProviderPreparedRuntimeAuth | null | undefined>;
/**
* Provider-owned cache TTL eligibility.
*
* Use this when a proxy provider supports Anthropic-style prompt caching for
* only a subset of upstream models.
*/
isCacheTtlEligible?: (ctx: ProviderCacheTtlEligibilityContext) => boolean | undefined;
wizard?: ProviderPluginWizard;
formatApiKey?: (cred: AuthProfileCredential) => string;
refreshOAuth?: (cred: OAuthCredential) => Promise<OAuthCredential>;