mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 21:10:43 +00:00
453 lines
14 KiB
TypeScript
453 lines
14 KiB
TypeScript
import { normalizeProviderId } from "../agents/provider-id.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import {
|
|
normalizePluginsConfigWithResolver,
|
|
type NormalizedPluginsConfig,
|
|
} from "./config-normalization-shared.js";
|
|
import { isInstalledPluginEnabled } from "./installed-plugin-index.js";
|
|
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
|
|
import type {
|
|
BundledChannelConfigCollector,
|
|
PluginManifestContractListKey,
|
|
PluginManifestRecord,
|
|
PluginManifestRegistry,
|
|
} from "./manifest-registry.js";
|
|
import { isPackageIncludedInCoreBundle } from "./manifest.js";
|
|
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js";
|
|
import type { PluginOrigin } from "./plugin-origin.types.js";
|
|
import {
|
|
createPluginRegistryIdNormalizer,
|
|
type PluginRegistryIdNormalizerOptions,
|
|
} from "./plugin-registry-id-normalizer.js";
|
|
import {
|
|
loadPluginRegistrySnapshot,
|
|
type LoadPluginRegistryParams,
|
|
type PluginRegistrySnapshot,
|
|
} from "./plugin-registry-snapshot.js";
|
|
export {
|
|
createPluginRegistryIdNormalizer,
|
|
type PluginRegistryIdNormalizerOptions,
|
|
} from "./plugin-registry-id-normalizer.js";
|
|
|
|
export type PluginLookUpTable = Pick<
|
|
PluginMetadataSnapshot,
|
|
"index" | "manifestRegistry" | "plugins" | "normalizePluginId" | "owners"
|
|
>;
|
|
|
|
export type PluginRegistryContributionOptions = LoadPluginRegistryParams & {
|
|
includeDisabled?: boolean;
|
|
lookUpTable?: PluginLookUpTable;
|
|
};
|
|
|
|
export type LoadPluginRegistryManifestParams = LoadPluginRegistryParams & {
|
|
includeDisabled?: boolean;
|
|
pluginIds?: readonly string[];
|
|
bundledChannelConfigCollector?: BundledChannelConfigCollector;
|
|
};
|
|
|
|
export type PluginRegistryContributionKey =
|
|
| "providers"
|
|
| "channels"
|
|
| "channelConfigs"
|
|
| "setupProviders"
|
|
| "cliBackends"
|
|
| "modelCatalogProviders"
|
|
| "commandAliases"
|
|
| "contracts";
|
|
|
|
export type ResolvePluginContributionOwnersParams = PluginRegistryContributionOptions & {
|
|
contribution: PluginRegistryContributionKey;
|
|
matches: string | ((contributionId: string) => boolean);
|
|
};
|
|
|
|
export type ListPluginContributionIdsParams = PluginRegistryContributionOptions & {
|
|
contribution: PluginRegistryContributionKey;
|
|
};
|
|
|
|
export type ResolveProviderOwnersParams = PluginRegistryContributionOptions & {
|
|
providerId: string;
|
|
};
|
|
|
|
export type ResolveChannelOwnersParams = PluginRegistryContributionOptions & {
|
|
channelId: string;
|
|
};
|
|
|
|
export type ResolveCliBackendOwnersParams = PluginRegistryContributionOptions & {
|
|
cliBackendId: string;
|
|
};
|
|
|
|
export type ResolveSetupProviderOwnersParams = PluginRegistryContributionOptions & {
|
|
setupProviderId: string;
|
|
};
|
|
|
|
export type ResolveManifestContractPluginIdsParams = LoadPluginRegistryParams & {
|
|
contract: PluginManifestContractListKey;
|
|
origin?: PluginOrigin;
|
|
onlyPluginIds?: readonly string[];
|
|
};
|
|
|
|
export type ResolveManifestContractOwnerPluginIdParams = LoadPluginRegistryParams & {
|
|
contract: PluginManifestContractListKey;
|
|
value: string | undefined;
|
|
origin?: PluginOrigin;
|
|
};
|
|
|
|
export type ResolveManifestContractPluginIdsByCompatibilityRuntimePathParams =
|
|
LoadPluginRegistryParams & {
|
|
contract: PluginManifestContractListKey;
|
|
path: string | undefined;
|
|
origin?: PluginOrigin;
|
|
};
|
|
|
|
function normalizeContributionId(value: string): string {
|
|
return value.trim();
|
|
}
|
|
|
|
function sortUnique(values: Iterable<string>): string[] {
|
|
return [...new Set([...values].map((value) => value.trim()).filter(Boolean))].toSorted(
|
|
(left, right) => left.localeCompare(right),
|
|
);
|
|
}
|
|
|
|
function isCoreBundledManifestSurface(plugin: PluginManifestRecord): boolean {
|
|
return plugin.origin !== "bundled" || isPackageIncludedInCoreBundle(plugin.packageManifest);
|
|
}
|
|
|
|
function collectObjectKeys(value: Record<string, unknown> | undefined): readonly string[] {
|
|
return value ? Object.keys(value) : [];
|
|
}
|
|
|
|
function collectContractKeys(plugin: PluginManifestRecord): readonly string[] {
|
|
const contracts = plugin.contracts;
|
|
if (!contracts) {
|
|
return [];
|
|
}
|
|
return Object.entries(contracts).flatMap(([key, value]) =>
|
|
Array.isArray(value) && value.length > 0 ? [key] : [],
|
|
);
|
|
}
|
|
|
|
function listManifestContractValues(
|
|
plugin: PluginManifestRecord,
|
|
contract: PluginManifestContractListKey,
|
|
): readonly string[] {
|
|
return plugin.contracts?.[contract] ?? [];
|
|
}
|
|
|
|
function loadManifestContractRegistry(
|
|
params: LoadPluginRegistryParams & {
|
|
onlyPluginIds?: readonly string[];
|
|
},
|
|
): PluginManifestRegistry {
|
|
return loadPluginManifestRegistryForPluginRegistry({
|
|
...params,
|
|
pluginIds: params.onlyPluginIds,
|
|
includeDisabled: true,
|
|
});
|
|
}
|
|
|
|
function listManifestContributionIds(
|
|
plugin: PluginManifestRecord,
|
|
contribution: PluginRegistryContributionKey,
|
|
): readonly string[] {
|
|
switch (contribution) {
|
|
case "providers":
|
|
return plugin.providers;
|
|
case "channels":
|
|
return plugin.channels;
|
|
case "channelConfigs":
|
|
return collectObjectKeys(plugin.channelConfigs);
|
|
case "setupProviders":
|
|
return plugin.setup?.providers?.map((provider) => provider.id) ?? [];
|
|
case "cliBackends":
|
|
return [...plugin.cliBackends, ...(plugin.setup?.cliBackends ?? [])];
|
|
case "modelCatalogProviders":
|
|
return [
|
|
...collectObjectKeys(plugin.modelCatalog?.providers),
|
|
...collectObjectKeys(plugin.modelCatalog?.aliases),
|
|
];
|
|
case "commandAliases":
|
|
return plugin.commandAliases?.map((alias) => alias.name) ?? [];
|
|
case "contracts":
|
|
return collectContractKeys(plugin);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function resolveContributionPluginIds(params: {
|
|
index: PluginRegistrySnapshot;
|
|
includeDisabled?: boolean;
|
|
config?: OpenClawConfig;
|
|
}): readonly string[] {
|
|
if (params.includeDisabled) {
|
|
return params.index.plugins.map((plugin) => plugin.pluginId);
|
|
}
|
|
return params.index.plugins
|
|
.filter((plugin) => isInstalledPluginEnabled(params.index, plugin.pluginId, params.config))
|
|
.map((plugin) => plugin.pluginId);
|
|
}
|
|
|
|
function loadContributionManifestRegistry(
|
|
params: LoadPluginRegistryParams & {
|
|
index: PluginRegistrySnapshot;
|
|
includeDisabled?: boolean;
|
|
},
|
|
): PluginManifestRegistry {
|
|
return loadPluginManifestRegistryForInstalledIndex({
|
|
index: params.index,
|
|
config: params.config,
|
|
workspaceDir: params.workspaceDir,
|
|
env: params.env,
|
|
pluginIds: resolveContributionPluginIds({
|
|
index: params.index,
|
|
includeDisabled: params.includeDisabled,
|
|
config: params.config,
|
|
}),
|
|
includeDisabled: true,
|
|
});
|
|
}
|
|
|
|
function listContributionManifestPlugins(
|
|
params: PluginRegistryContributionOptions & {
|
|
index: PluginRegistrySnapshot;
|
|
},
|
|
): readonly PluginManifestRecord[] {
|
|
const plugins = params.lookUpTable?.plugins;
|
|
if (plugins) {
|
|
const enabledPluginIds = new Set(
|
|
resolveContributionPluginIds({
|
|
index: params.index,
|
|
includeDisabled: params.includeDisabled,
|
|
config: params.config,
|
|
}),
|
|
);
|
|
return plugins.filter((plugin) => enabledPluginIds.has(plugin.id));
|
|
}
|
|
return loadContributionManifestRegistry({
|
|
...params,
|
|
index: params.index,
|
|
}).plugins;
|
|
}
|
|
|
|
function resolveContributionOwnerMap(
|
|
table: PluginLookUpTable,
|
|
contribution: PluginRegistryContributionKey,
|
|
): ReadonlyMap<string, readonly string[]> | undefined {
|
|
switch (contribution) {
|
|
case "channels":
|
|
return table.owners.channels;
|
|
case "channelConfigs":
|
|
return table.owners.channelConfigs;
|
|
case "providers":
|
|
return table.owners.providers;
|
|
case "modelCatalogProviders":
|
|
return table.owners.modelCatalogProviders;
|
|
case "cliBackends":
|
|
return table.owners.cliBackends;
|
|
case "setupProviders":
|
|
return table.owners.setupProviders;
|
|
case "commandAliases":
|
|
return table.owners.commandAliases;
|
|
case "contracts":
|
|
return table.owners.contracts;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function filterContributionOwnerIds(params: {
|
|
owners: readonly string[];
|
|
index: PluginRegistrySnapshot;
|
|
includeDisabled?: boolean;
|
|
config?: OpenClawConfig;
|
|
}): readonly string[] {
|
|
const enabledPluginIds = new Set(
|
|
resolveContributionPluginIds({
|
|
index: params.index,
|
|
includeDisabled: params.includeDisabled,
|
|
config: params.config,
|
|
}),
|
|
);
|
|
return sortUnique(params.owners.filter((owner) => enabledPluginIds.has(owner)));
|
|
}
|
|
|
|
export function loadPluginManifestRegistryForPluginRegistry(
|
|
params: LoadPluginRegistryManifestParams = {},
|
|
): PluginManifestRegistry {
|
|
const index = loadPluginRegistrySnapshot(params);
|
|
return loadPluginManifestRegistryForInstalledIndex({
|
|
index,
|
|
config: params.config,
|
|
workspaceDir: params.workspaceDir,
|
|
env: params.env,
|
|
pluginIds: params.pluginIds,
|
|
includeDisabled: params.includeDisabled,
|
|
...(params.bundledChannelConfigCollector
|
|
? { bundledChannelConfigCollector: params.bundledChannelConfigCollector }
|
|
: {}),
|
|
});
|
|
}
|
|
|
|
export function normalizePluginsConfigWithRegistry(
|
|
config: OpenClawConfig["plugins"] | undefined,
|
|
index: PluginRegistrySnapshot,
|
|
options: PluginRegistryIdNormalizerOptions = {},
|
|
): NormalizedPluginsConfig {
|
|
return normalizePluginsConfigWithResolver(
|
|
config,
|
|
createPluginRegistryIdNormalizer(index, options),
|
|
);
|
|
}
|
|
|
|
export function listPluginContributionIds(
|
|
params: ListPluginContributionIdsParams,
|
|
): readonly string[] {
|
|
const index = params.lookUpTable?.index ?? loadPluginRegistrySnapshot(params);
|
|
const plugins = listContributionManifestPlugins({ ...params, index });
|
|
return sortUnique(
|
|
plugins.flatMap((plugin) => listManifestContributionIds(plugin, params.contribution)),
|
|
);
|
|
}
|
|
|
|
export function resolvePluginContributionOwners(
|
|
params: ResolvePluginContributionOwnersParams,
|
|
): readonly string[] {
|
|
const index = params.lookUpTable?.index ?? loadPluginRegistrySnapshot(params);
|
|
if (params.lookUpTable && typeof params.matches === "string") {
|
|
const ownerMap = resolveContributionOwnerMap(params.lookUpTable, params.contribution);
|
|
const owners = ownerMap?.get(params.matches);
|
|
if (owners) {
|
|
return filterContributionOwnerIds({
|
|
owners,
|
|
index,
|
|
includeDisabled: params.includeDisabled,
|
|
config: params.config,
|
|
});
|
|
}
|
|
return [];
|
|
}
|
|
const matcher =
|
|
typeof params.matches === "string"
|
|
? (contributionId: string) => contributionId === params.matches
|
|
: params.matches;
|
|
const plugins = listContributionManifestPlugins({ ...params, index });
|
|
return sortUnique(
|
|
plugins.flatMap((plugin) =>
|
|
listManifestContributionIds(plugin, params.contribution).some(matcher) ? [plugin.id] : [],
|
|
),
|
|
);
|
|
}
|
|
|
|
export function resolveProviderOwners(params: ResolveProviderOwnersParams): readonly string[] {
|
|
const providerId = normalizeProviderId(params.providerId);
|
|
if (!providerId) {
|
|
return [];
|
|
}
|
|
if (params.lookUpTable) {
|
|
const index = params.lookUpTable.index;
|
|
const owners: string[] = [];
|
|
for (const [contributionId, ownerIds] of params.lookUpTable.owners.providers.entries()) {
|
|
if (normalizeProviderId(contributionId) === providerId) {
|
|
owners.push(...ownerIds);
|
|
}
|
|
}
|
|
return filterContributionOwnerIds({
|
|
owners,
|
|
index,
|
|
includeDisabled: params.includeDisabled,
|
|
config: params.config,
|
|
});
|
|
}
|
|
return resolvePluginContributionOwners({
|
|
...params,
|
|
contribution: "providers",
|
|
matches: (contributionId) => normalizeProviderId(contributionId) === providerId,
|
|
});
|
|
}
|
|
|
|
export function resolveChannelOwners(params: ResolveChannelOwnersParams): readonly string[] {
|
|
const channelId = normalizeContributionId(params.channelId);
|
|
if (!channelId) {
|
|
return [];
|
|
}
|
|
return resolvePluginContributionOwners({
|
|
...params,
|
|
contribution: "channels",
|
|
matches: channelId,
|
|
});
|
|
}
|
|
|
|
export function resolveCliBackendOwners(params: ResolveCliBackendOwnersParams): readonly string[] {
|
|
const cliBackendId = normalizeContributionId(params.cliBackendId);
|
|
if (!cliBackendId) {
|
|
return [];
|
|
}
|
|
return resolvePluginContributionOwners({
|
|
...params,
|
|
contribution: "cliBackends",
|
|
matches: cliBackendId,
|
|
});
|
|
}
|
|
|
|
export function resolveSetupProviderOwners(
|
|
params: ResolveSetupProviderOwnersParams,
|
|
): readonly string[] {
|
|
const setupProviderId = normalizeContributionId(params.setupProviderId);
|
|
if (!setupProviderId) {
|
|
return [];
|
|
}
|
|
return resolvePluginContributionOwners({
|
|
...params,
|
|
contribution: "setupProviders",
|
|
matches: setupProviderId,
|
|
});
|
|
}
|
|
|
|
export function resolveManifestContractPluginIds(
|
|
params: ResolveManifestContractPluginIdsParams,
|
|
): string[] {
|
|
return loadManifestContractRegistry(params)
|
|
.plugins.filter(
|
|
(plugin) =>
|
|
(!params.origin || plugin.origin === params.origin) &&
|
|
isCoreBundledManifestSurface(plugin) &&
|
|
listManifestContractValues(plugin, params.contract).length > 0,
|
|
)
|
|
.map((plugin) => plugin.id)
|
|
.toSorted((left, right) => left.localeCompare(right));
|
|
}
|
|
|
|
export function resolveManifestContractPluginIdsByCompatibilityRuntimePath(
|
|
params: ResolveManifestContractPluginIdsByCompatibilityRuntimePathParams,
|
|
): string[] {
|
|
const normalizedPath = params.path?.trim();
|
|
if (!normalizedPath) {
|
|
return [];
|
|
}
|
|
return loadManifestContractRegistry(params)
|
|
.plugins.filter(
|
|
(plugin) =>
|
|
(!params.origin || plugin.origin === params.origin) &&
|
|
isCoreBundledManifestSurface(plugin) &&
|
|
listManifestContractValues(plugin, params.contract).length > 0 &&
|
|
(plugin.configContracts?.compatibilityRuntimePaths ?? []).includes(normalizedPath),
|
|
)
|
|
.map((plugin) => plugin.id)
|
|
.toSorted((left, right) => left.localeCompare(right));
|
|
}
|
|
|
|
export function resolveManifestContractOwnerPluginId(
|
|
params: ResolveManifestContractOwnerPluginIdParams,
|
|
): string | undefined {
|
|
const normalizedValue = normalizeContributionId(params.value ?? "").toLowerCase();
|
|
if (!normalizedValue) {
|
|
return undefined;
|
|
}
|
|
return loadManifestContractRegistry(params).plugins.find(
|
|
(plugin) =>
|
|
(!params.origin || plugin.origin === params.origin) &&
|
|
listManifestContractValues(plugin, params.contract).some(
|
|
(candidate) => normalizeContributionId(candidate).toLowerCase() === normalizedValue,
|
|
),
|
|
)?.id;
|
|
}
|