Files
moltbot/src/plugin-sdk/provider-entry.ts
2026-04-28 18:04:54 +01:00

204 lines
6.7 KiB
TypeScript

import { createProviderApiKeyAuthMethod } from "../plugins/provider-api-key-auth.js";
import type {
ProviderPlugin,
ProviderCatalogContext,
ProviderCatalogResult,
ProviderPluginCatalog,
ProviderPluginWizardSetup,
} from "../plugins/types.js";
import { definePluginEntry } from "./plugin-entry.js";
import type {
OpenClawPluginApi,
OpenClawPluginConfigSchema,
OpenClawPluginDefinition,
} from "./plugin-entry.js";
import { buildSingleProviderApiKeyCatalog } from "./provider-catalog-shared.js";
type ApiKeyAuthMethodOptions = Parameters<typeof createProviderApiKeyAuthMethod>[0];
export type SingleProviderPluginApiKeyAuthOptions = Omit<
ApiKeyAuthMethodOptions,
"providerId" | "expectedProviders" | "wizard"
> & {
expectedProviders?: string[];
wizard?: false | ProviderPluginWizardSetup;
};
export type SingleProviderPluginCatalogOptions =
| {
buildProvider: Parameters<typeof buildSingleProviderApiKeyCatalog>[0]["buildProvider"];
buildStaticProvider?: Parameters<typeof buildSingleProviderApiKeyCatalog>[0]["buildProvider"];
allowExplicitBaseUrl?: boolean;
run?: never;
order?: never;
staticRun?: never;
}
| {
run: ProviderPluginCatalog["run"];
staticRun?: ProviderPluginCatalog["run"];
order?: ProviderPluginCatalog["order"];
buildProvider?: never;
buildStaticProvider?: never;
allowExplicitBaseUrl?: never;
};
export type SingleProviderPluginOptions = {
id: string;
name: string;
description: string;
/**
* @deprecated Declare exclusive plugin kind in `openclaw.plugin.json` via
* manifest `kind`. Runtime-entry `kind` remains only as a compatibility
* fallback for older plugins.
*/
kind?: OpenClawPluginDefinition["kind"];
configSchema?: OpenClawPluginConfigSchema | (() => OpenClawPluginConfigSchema);
provider?: {
id?: string;
label: string;
docsPath: string;
aliases?: string[];
envVars?: string[];
auth?: SingleProviderPluginApiKeyAuthOptions[];
catalog: SingleProviderPluginCatalogOptions;
} & Omit<
ProviderPlugin,
"id" | "label" | "docsPath" | "aliases" | "envVars" | "auth" | "catalog" | "staticCatalog"
>;
register?: (api: OpenClawPluginApi) => void;
};
function resolveWizardSetup(params: {
providerId: string;
providerLabel: string;
auth: SingleProviderPluginApiKeyAuthOptions;
}): ProviderPluginWizardSetup | undefined {
if (params.auth.wizard === false) {
return undefined;
}
const wizard = params.auth.wizard ?? {};
const methodId = params.auth.methodId.trim();
return {
choiceId: wizard.choiceId ?? `${params.providerId}-${methodId}`,
choiceLabel: wizard.choiceLabel ?? params.auth.label,
...(wizard.choiceHint ? { choiceHint: wizard.choiceHint } : {}),
groupId: wizard.groupId ?? params.providerId,
groupLabel: wizard.groupLabel ?? params.providerLabel,
...((wizard.groupHint ?? params.auth.hint)
? { groupHint: wizard.groupHint ?? params.auth.hint }
: {}),
methodId,
...(wizard.onboardingScopes ? { onboardingScopes: wizard.onboardingScopes } : {}),
...(wizard.modelAllowlist ? { modelAllowlist: wizard.modelAllowlist } : {}),
};
}
function resolveEnvVars(params: {
envVars?: string[];
auth?: SingleProviderPluginApiKeyAuthOptions[];
}): string[] | undefined {
const combined = [
...(params.envVars ?? []),
...(params.auth ?? []).map((entry) => entry.envVar).filter(Boolean),
]
.map((value) => value.trim())
.filter(Boolean);
return combined.length > 0 ? [...new Set(combined)] : undefined;
}
export function defineSingleProviderPluginEntry(options: SingleProviderPluginOptions) {
return definePluginEntry({
id: options.id,
name: options.name,
description: options.description,
...(options.kind ? { kind: options.kind } : {}),
...(options.configSchema ? { configSchema: options.configSchema } : {}),
register(api) {
const provider = options.provider;
if (provider) {
const providerId = provider.id ?? options.id;
const envVars = resolveEnvVars({
envVars: provider.envVars,
auth: provider.auth,
});
const auth = (provider.auth ?? []).map((entry) => {
const { wizard: _wizard, ...authParams } = entry;
const wizard = resolveWizardSetup({
providerId,
providerLabel: provider.label,
auth: entry,
});
return createProviderApiKeyAuthMethod({
...authParams,
providerId,
expectedProviders: entry.expectedProviders ?? [providerId],
...(wizard ? { wizard } : {}),
});
});
let catalog: ProviderPluginCatalog;
if ("run" in provider.catalog) {
const catalogRun = provider.catalog.run;
catalog = {
order: provider.catalog.order ?? "simple",
run: catalogRun!,
};
} else {
const buildProvider = provider.catalog.buildProvider;
catalog = {
order: "simple",
run: (ctx: ProviderCatalogContext): Promise<ProviderCatalogResult> =>
buildSingleProviderApiKeyCatalog({
ctx,
providerId,
buildProvider,
...(provider.catalog.allowExplicitBaseUrl ? { allowExplicitBaseUrl: true } : {}),
}),
};
}
const staticCatalog: ProviderPluginCatalog | undefined =
"run" in provider.catalog
? provider.catalog.staticRun
? {
order: provider.catalog.order ?? "simple",
run: provider.catalog.staticRun,
}
: undefined
: provider.catalog.buildStaticProvider
? {
order: "simple",
run: async () => ({
provider: await provider.catalog.buildStaticProvider!(),
}),
}
: undefined;
api.registerProvider({
id: providerId,
label: provider.label,
docsPath: provider.docsPath,
...(provider.aliases ? { aliases: provider.aliases } : {}),
...(envVars ? { envVars } : {}),
auth,
catalog,
...(staticCatalog ? { staticCatalog } : {}),
...Object.fromEntries(
Object.entries(provider).filter(
([key]) =>
![
"id",
"label",
"docsPath",
"aliases",
"envVars",
"auth",
"catalog",
"staticCatalog",
].includes(key),
),
),
});
}
options.register?.(api);
},
});
}