refactor: split plugin metadata normalizer

This commit is contained in:
Peter Steinberger
2026-04-27 18:27:27 +01:00
parent e3ad82d86d
commit 147752ecc3
5 changed files with 157 additions and 114 deletions

View File

@@ -1,6 +1,6 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js";
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js";
let currentPluginMetadataSnapshot: PluginMetadataSnapshot | undefined;
let currentPluginMetadataSnapshotConfigFingerprint: string | undefined;

View File

@@ -1,62 +1,29 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js";
import type { InstalledPluginIndex } from "./installed-plugin-index.js";
import {
loadPluginManifestRegistryForInstalledIndex,
resolveInstalledManifestRegistryIndexFingerprint,
} from "./manifest-registry-installed.js";
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
import type { PluginDiagnostic } from "./manifest-types.js";
import { createPluginRegistryIdNormalizer } from "./plugin-registry-contributions.js";
import {
loadPluginRegistrySnapshotWithMetadata,
type PluginRegistrySnapshot,
type PluginRegistrySnapshotDiagnostic,
} from "./plugin-registry-snapshot.js";
export type PluginMetadataSnapshotOwnerMaps = {
channels: ReadonlyMap<string, readonly string[]>;
channelConfigs: ReadonlyMap<string, readonly string[]>;
providers: ReadonlyMap<string, readonly string[]>;
modelCatalogProviders: ReadonlyMap<string, readonly string[]>;
cliBackends: ReadonlyMap<string, readonly string[]>;
setupProviders: ReadonlyMap<string, readonly string[]>;
commandAliases: ReadonlyMap<string, readonly string[]>;
contracts: ReadonlyMap<string, readonly string[]>;
};
export type PluginMetadataSnapshotMetrics = {
registrySnapshotMs: number;
manifestRegistryMs: number;
ownerMapsMs: number;
totalMs: number;
indexPluginCount: number;
manifestPluginCount: number;
};
export type PluginMetadataSnapshot = {
policyHash: string;
workspaceDir?: string;
index: PluginRegistrySnapshot;
registryDiagnostics: readonly PluginRegistrySnapshotDiagnostic[];
manifestRegistry: PluginManifestRegistry;
plugins: readonly PluginManifestRecord[];
diagnostics: readonly PluginDiagnostic[];
byPluginId: ReadonlyMap<string, PluginManifestRecord>;
normalizePluginId: (pluginId: string) => string;
owners: PluginMetadataSnapshotOwnerMaps;
metrics: PluginMetadataSnapshotMetrics;
};
export type LoadPluginMetadataSnapshotParams = {
config: OpenClawConfig;
workspaceDir?: string;
env: NodeJS.ProcessEnv;
index?: PluginRegistrySnapshot;
};
import type { PluginManifestRecord } from "./manifest-registry.js";
import type {
LoadPluginMetadataSnapshotParams,
PluginMetadataSnapshot,
PluginMetadataSnapshotOwnerMaps,
} from "./plugin-metadata-snapshot.types.js";
import { createPluginRegistryIdNormalizer } from "./plugin-registry-id-normalizer.js";
import { loadPluginRegistrySnapshotWithMetadata } from "./plugin-registry-snapshot.js";
export type {
LoadPluginMetadataSnapshotParams,
PluginMetadataSnapshot,
PluginMetadataSnapshotMetrics,
PluginMetadataSnapshotOwnerMaps,
PluginMetadataSnapshotRegistryDiagnostic,
} from "./plugin-metadata-snapshot.types.js";
function indexesMatch(
left: PluginRegistrySnapshot | undefined,
right: PluginRegistrySnapshot | undefined,
left: InstalledPluginIndex | undefined,
right: InstalledPluginIndex | undefined,
): boolean {
if (!left || !right) {
return true;
@@ -71,7 +38,7 @@ export function isPluginMetadataSnapshotCompatible(params: {
snapshot: Pick<PluginMetadataSnapshot, "index" | "policyHash" | "workspaceDir">;
config: OpenClawConfig;
workspaceDir?: string;
index?: PluginRegistrySnapshot;
index?: InstalledPluginIndex;
}): boolean {
return (
params.snapshot.policyHash === resolveInstalledPluginIndexPolicyHash(params.config) &&

View File

@@ -0,0 +1,55 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { InstalledPluginIndex } from "./installed-plugin-index.js";
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
import type { PluginDiagnostic } from "./manifest-types.js";
export type PluginMetadataSnapshotOwnerMaps = {
channels: ReadonlyMap<string, readonly string[]>;
channelConfigs: ReadonlyMap<string, readonly string[]>;
providers: ReadonlyMap<string, readonly string[]>;
modelCatalogProviders: ReadonlyMap<string, readonly string[]>;
cliBackends: ReadonlyMap<string, readonly string[]>;
setupProviders: ReadonlyMap<string, readonly string[]>;
commandAliases: ReadonlyMap<string, readonly string[]>;
contracts: ReadonlyMap<string, readonly string[]>;
};
export type PluginMetadataSnapshotMetrics = {
registrySnapshotMs: number;
manifestRegistryMs: number;
ownerMapsMs: number;
totalMs: number;
indexPluginCount: number;
manifestPluginCount: number;
};
export type PluginMetadataSnapshotRegistryDiagnostic = {
level: "info" | "warn";
code:
| "persisted-registry-disabled"
| "persisted-registry-missing"
| "persisted-registry-stale-policy"
| "persisted-registry-stale-source";
message: string;
};
export type PluginMetadataSnapshot = {
policyHash: string;
workspaceDir?: string;
index: InstalledPluginIndex;
registryDiagnostics: readonly PluginMetadataSnapshotRegistryDiagnostic[];
manifestRegistry: PluginManifestRegistry;
plugins: readonly PluginManifestRecord[];
diagnostics: readonly PluginDiagnostic[];
byPluginId: ReadonlyMap<string, PluginManifestRecord>;
normalizePluginId: (pluginId: string) => string;
owners: PluginMetadataSnapshotOwnerMaps;
metrics: PluginMetadataSnapshotMetrics;
};
export type LoadPluginMetadataSnapshotParams = {
config: OpenClawConfig;
workspaceDir?: string;
env: NodeJS.ProcessEnv;
index?: InstalledPluginIndex;
};

View File

@@ -13,11 +13,19 @@ import type {
PluginManifestRegistry,
} from "./manifest-registry.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 = {
index: PluginRegistrySnapshot;
@@ -101,23 +109,10 @@ export type ResolveManifestContractPluginIdsByCompatibilityRuntimePathParams =
origin?: PluginOrigin;
};
export type PluginRegistryIdNormalizerOptions = {
manifestRegistry?: PluginManifestRegistry;
lookUpTable?: Pick<PluginLookUpTable, "manifestRegistry">;
};
function normalizeContributionId(value: string): string {
return value.trim();
}
function normalizePluginRegistryAlias(value: string): string {
return value.trim();
}
function normalizePluginRegistryAliasKey(value: string): string {
return normalizePluginRegistryAlias(value).toLowerCase();
}
function sortUnique(values: Iterable<string>): string[] {
return [...new Set([...values].map((value) => value.trim()).filter(Boolean))].toSorted(
(left, right) => left.localeCompare(right),
@@ -298,54 +293,6 @@ export function loadPluginManifestRegistryForPluginRegistry(
});
}
export function createPluginRegistryIdNormalizer(
index: PluginRegistrySnapshot,
options: PluginRegistryIdNormalizerOptions = {},
): (pluginId: string) => string {
const aliases = new Map<string, string>();
for (const plugin of index.plugins) {
const pluginId = normalizePluginRegistryAlias(plugin.pluginId);
if (pluginId) {
aliases.set(normalizePluginRegistryAliasKey(pluginId), plugin.pluginId);
}
}
const registry =
options.lookUpTable?.manifestRegistry ??
options.manifestRegistry ??
loadPluginManifestRegistryForInstalledIndex({
index,
includeDisabled: true,
});
for (const plugin of [...registry.plugins].toSorted((left, right) =>
left.id.localeCompare(right.id),
)) {
const pluginId = normalizePluginRegistryAlias(plugin.id);
if (!pluginId) {
continue;
}
aliases.set(normalizePluginRegistryAliasKey(pluginId), plugin.id);
for (const alias of [
plugin.id,
...listManifestContributionIds(plugin, "providers"),
...listManifestContributionIds(plugin, "channels"),
...listManifestContributionIds(plugin, "setupProviders"),
...listManifestContributionIds(plugin, "cliBackends"),
...listManifestContributionIds(plugin, "modelCatalogProviders"),
...(plugin.legacyPluginIds ?? []),
]) {
const normalizedAlias = normalizePluginRegistryAlias(alias);
const normalizedAliasKey = normalizePluginRegistryAliasKey(alias);
if (normalizedAlias && !aliases.has(normalizedAliasKey)) {
aliases.set(normalizedAliasKey, pluginId);
}
}
}
return (pluginId: string) => {
const trimmed = normalizePluginRegistryAlias(pluginId);
return aliases.get(normalizePluginRegistryAliasKey(trimmed)) ?? trimmed;
};
}
export function normalizePluginsConfigWithRegistry(
config: OpenClawConfig["plugins"] | undefined,
index: PluginRegistrySnapshot,

View File

@@ -0,0 +1,74 @@
import type { InstalledPluginIndex } from "./installed-plugin-index.js";
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
export type PluginRegistryIdNormalizerOptions = {
manifestRegistry?: PluginManifestRegistry;
lookUpTable?: Pick<{ manifestRegistry: PluginManifestRegistry }, "manifestRegistry">;
};
function normalizePluginRegistryAlias(value: string): string {
return value.trim();
}
function normalizePluginRegistryAliasKey(value: string): string {
return normalizePluginRegistryAlias(value).toLowerCase();
}
function collectObjectKeys(value: Record<string, unknown> | undefined): readonly string[] {
return value ? Object.keys(value) : [];
}
function listPluginRegistryNormalizerAliases(plugin: PluginManifestRecord): readonly string[] {
return [
plugin.id,
...plugin.providers,
...plugin.channels,
...(plugin.setup?.providers?.map((provider) => provider.id) ?? []),
...plugin.cliBackends,
...(plugin.setup?.cliBackends ?? []),
...collectObjectKeys(plugin.modelCatalog?.providers),
...collectObjectKeys(plugin.modelCatalog?.aliases),
...(plugin.legacyPluginIds ?? []),
];
}
export function createPluginRegistryIdNormalizer(
index: InstalledPluginIndex,
options: PluginRegistryIdNormalizerOptions = {},
): (pluginId: string) => string {
const aliases = new Map<string, string>();
for (const plugin of index.plugins) {
const pluginId = normalizePluginRegistryAlias(plugin.pluginId);
if (pluginId) {
aliases.set(normalizePluginRegistryAliasKey(pluginId), plugin.pluginId);
}
}
const registry =
options.lookUpTable?.manifestRegistry ??
options.manifestRegistry ??
loadPluginManifestRegistryForInstalledIndex({
index,
includeDisabled: true,
});
for (const plugin of [...registry.plugins].toSorted((left, right) =>
left.id.localeCompare(right.id),
)) {
const pluginId = normalizePluginRegistryAlias(plugin.id);
if (!pluginId) {
continue;
}
aliases.set(normalizePluginRegistryAliasKey(pluginId), plugin.id);
for (const alias of listPluginRegistryNormalizerAliases(plugin)) {
const normalizedAlias = normalizePluginRegistryAlias(alias);
const normalizedAliasKey = normalizePluginRegistryAliasKey(alias);
if (normalizedAlias && !aliases.has(normalizedAliasKey)) {
aliases.set(normalizedAliasKey, pluginId);
}
}
}
return (pluginId: string) => {
const trimmed = normalizePluginRegistryAlias(pluginId);
return aliases.get(normalizePluginRegistryAliasKey(trimmed)) ?? trimmed;
};
}