mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 01:00:20 +00:00
refactor: split bundled channel config metadata
This commit is contained in:
197
src/plugins/bundled-channel-config-metadata.ts
Normal file
197
src/plugins/bundled-channel-config-metadata.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { createJiti } from "jiti";
|
||||
import { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
|
||||
import type { ChannelConfigRuntimeSchema } from "../channels/plugins/types.plugin.js";
|
||||
import type {
|
||||
OpenClawPackageManifest,
|
||||
PluginManifest,
|
||||
PluginManifestChannelConfig,
|
||||
} from "./manifest.js";
|
||||
import {
|
||||
buildPluginLoaderAliasMap,
|
||||
buildPluginLoaderJitiOptions,
|
||||
shouldPreferNativeJiti,
|
||||
} from "./sdk-alias.js";
|
||||
import type { PluginConfigUiHint } from "./types.js";
|
||||
|
||||
const PUBLIC_SURFACE_SOURCE_EXTENSIONS = [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"] as const;
|
||||
const SOURCE_CONFIG_SCHEMA_CANDIDATES = [
|
||||
path.join("src", "config-schema.ts"),
|
||||
path.join("src", "config-schema.js"),
|
||||
path.join("src", "config-schema.mts"),
|
||||
path.join("src", "config-schema.mjs"),
|
||||
path.join("src", "config-schema.cts"),
|
||||
path.join("src", "config-schema.cjs"),
|
||||
] as const;
|
||||
const PUBLIC_CONFIG_SURFACE_BASENAMES = ["channel-config-api", "runtime-api", "api"] as const;
|
||||
|
||||
type ChannelConfigSurface = {
|
||||
schema: Record<string, unknown>;
|
||||
uiHints?: Record<string, PluginConfigUiHint>;
|
||||
runtime?: ChannelConfigRuntimeSchema;
|
||||
};
|
||||
|
||||
const jitiLoaders = new Map<string, ReturnType<typeof createJiti>>();
|
||||
|
||||
function trimString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function normalizeStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value.map((entry) => trimString(entry) ?? "").filter(Boolean);
|
||||
}
|
||||
|
||||
function isBuiltChannelConfigSchema(value: unknown): value is ChannelConfigSurface {
|
||||
if (!value || typeof value !== "object") {
|
||||
return false;
|
||||
}
|
||||
const candidate = value as { schema?: unknown };
|
||||
return Boolean(candidate.schema && typeof candidate.schema === "object");
|
||||
}
|
||||
|
||||
function resolveConfigSchemaExport(imported: Record<string, unknown>): ChannelConfigSurface | null {
|
||||
for (const [name, value] of Object.entries(imported)) {
|
||||
if (name.endsWith("ChannelConfigSchema") && isBuiltChannelConfigSchema(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, value] of Object.entries(imported)) {
|
||||
if (!name.endsWith("ConfigSchema") || name.endsWith("AccountConfigSchema")) {
|
||||
continue;
|
||||
}
|
||||
if (isBuiltChannelConfigSchema(value)) {
|
||||
return value;
|
||||
}
|
||||
if (value && typeof value === "object") {
|
||||
return buildChannelConfigSchema(value as never);
|
||||
}
|
||||
}
|
||||
|
||||
for (const value of Object.values(imported)) {
|
||||
if (isBuiltChannelConfigSchema(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
const tryNative =
|
||||
shouldPreferNativeJiti(modulePath) || modulePath.includes(`${path.sep}dist${path.sep}`);
|
||||
const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url);
|
||||
const cacheKey = JSON.stringify({
|
||||
tryNative,
|
||||
aliasMap: Object.entries(aliasMap).toSorted(([left], [right]) => left.localeCompare(right)),
|
||||
});
|
||||
const cached = jitiLoaders.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const loader = createJiti(import.meta.url, {
|
||||
...buildPluginLoaderJitiOptions(aliasMap),
|
||||
tryNative,
|
||||
});
|
||||
jitiLoaders.set(cacheKey, loader);
|
||||
return loader;
|
||||
}
|
||||
|
||||
function resolveChannelConfigSchemaModulePath(pluginDir: string): string | undefined {
|
||||
for (const relativePath of SOURCE_CONFIG_SCHEMA_CANDIDATES) {
|
||||
const candidate = path.join(pluginDir, relativePath);
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
for (const basename of PUBLIC_CONFIG_SURFACE_BASENAMES) {
|
||||
for (const extension of PUBLIC_SURFACE_SOURCE_EXTENSIONS) {
|
||||
const candidate = path.join(pluginDir, `${basename}${extension}`);
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function loadChannelConfigSurfaceModuleSync(modulePath: string): ChannelConfigSurface | null {
|
||||
try {
|
||||
const imported = getJiti(modulePath)(modulePath) as Record<string, unknown>;
|
||||
return resolveConfigSchemaExport(imported);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePackageChannelMeta(
|
||||
packageManifest: OpenClawPackageManifest | undefined,
|
||||
channelId: string,
|
||||
): OpenClawPackageManifest["channel"] | undefined {
|
||||
const channelMeta = packageManifest?.channel;
|
||||
return channelMeta?.id?.trim() === channelId ? channelMeta : undefined;
|
||||
}
|
||||
|
||||
export function collectBundledChannelConfigs(params: {
|
||||
pluginDir: string;
|
||||
manifest: PluginManifest;
|
||||
packageManifest?: OpenClawPackageManifest;
|
||||
}): Record<string, PluginManifestChannelConfig> | undefined {
|
||||
const channelIds = normalizeStringList(params.manifest.channels);
|
||||
const existingChannelConfigs: Record<string, PluginManifestChannelConfig> =
|
||||
params.manifest.channelConfigs && Object.keys(params.manifest.channelConfigs).length > 0
|
||||
? { ...params.manifest.channelConfigs }
|
||||
: {};
|
||||
if (channelIds.length === 0) {
|
||||
return Object.keys(existingChannelConfigs).length > 0 ? existingChannelConfigs : undefined;
|
||||
}
|
||||
|
||||
const surfaceModulePath = resolveChannelConfigSchemaModulePath(params.pluginDir);
|
||||
const surface = surfaceModulePath ? loadChannelConfigSurfaceModuleSync(surfaceModulePath) : null;
|
||||
|
||||
for (const channelId of channelIds) {
|
||||
const existing = existingChannelConfigs[channelId];
|
||||
const channelMeta = resolvePackageChannelMeta(params.packageManifest, channelId);
|
||||
const preferOver = normalizeStringList(channelMeta?.preferOver);
|
||||
const uiHints: Record<string, PluginConfigUiHint> | undefined =
|
||||
surface?.uiHints || existing?.uiHints
|
||||
? {
|
||||
...(surface?.uiHints && Object.keys(surface.uiHints).length > 0 ? surface.uiHints : {}),
|
||||
...(existing?.uiHints && Object.keys(existing.uiHints).length > 0
|
||||
? existing.uiHints
|
||||
: {}),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
if (!surface?.schema && !existing?.schema) {
|
||||
continue;
|
||||
}
|
||||
|
||||
existingChannelConfigs[channelId] = {
|
||||
schema: surface?.schema ?? existing?.schema ?? {},
|
||||
...(uiHints && Object.keys(uiHints).length > 0 ? { uiHints } : {}),
|
||||
...((surface?.runtime ?? existing?.runtime)
|
||||
? { runtime: surface?.runtime ?? existing?.runtime }
|
||||
: {}),
|
||||
...((trimString(existing?.label) ?? trimString(channelMeta?.label))
|
||||
? { label: trimString(existing?.label) ?? trimString(channelMeta?.label)! }
|
||||
: {}),
|
||||
...((trimString(existing?.description) ?? trimString(channelMeta?.blurb))
|
||||
? {
|
||||
description: trimString(existing?.description) ?? trimString(channelMeta?.blurb)!,
|
||||
}
|
||||
: {}),
|
||||
...(existing?.preferOver?.length
|
||||
? { preferOver: existing.preferOver }
|
||||
: preferOver.length > 0
|
||||
? { preferOver }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
return Object.keys(existingChannelConfigs).length > 0 ? existingChannelConfigs : undefined;
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createJiti } from "jiti";
|
||||
import { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
|
||||
import type { ChannelConfigRuntimeSchema } from "../channels/plugins/types.plugin.js";
|
||||
import { collectBundledChannelConfigs } from "./bundled-channel-config-metadata.js";
|
||||
import {
|
||||
collectBundledPluginPublicSurfaceArtifacts,
|
||||
collectBundledPluginRuntimeSidecarArtifacts,
|
||||
@@ -16,15 +14,8 @@ import {
|
||||
type OpenClawPackageManifest,
|
||||
type PackageManifest,
|
||||
type PluginManifest,
|
||||
type PluginManifestChannelConfig,
|
||||
} from "./manifest.js";
|
||||
import {
|
||||
buildPluginLoaderAliasMap,
|
||||
buildPluginLoaderJitiOptions,
|
||||
resolveLoaderPackageRoot,
|
||||
shouldPreferNativeJiti,
|
||||
} from "./sdk-alias.js";
|
||||
import type { PluginConfigUiHint } from "./types.js";
|
||||
import { resolveLoaderPackageRoot } from "./sdk-alias.js";
|
||||
|
||||
const OPENCLAW_PACKAGE_ROOT =
|
||||
resolveLoaderPackageRoot({
|
||||
@@ -35,16 +26,6 @@ const CURRENT_MODULE_PATH = fileURLToPath(import.meta.url);
|
||||
const RUNNING_FROM_BUILT_ARTIFACT =
|
||||
CURRENT_MODULE_PATH.includes(`${path.sep}dist${path.sep}`) ||
|
||||
CURRENT_MODULE_PATH.includes(`${path.sep}dist-runtime${path.sep}`);
|
||||
const PUBLIC_SURFACE_SOURCE_EXTENSIONS = [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"] as const;
|
||||
const SOURCE_CONFIG_SCHEMA_CANDIDATES = [
|
||||
path.join("src", "config-schema.ts"),
|
||||
path.join("src", "config-schema.js"),
|
||||
path.join("src", "config-schema.mts"),
|
||||
path.join("src", "config-schema.mjs"),
|
||||
path.join("src", "config-schema.cts"),
|
||||
path.join("src", "config-schema.cjs"),
|
||||
] as const;
|
||||
const PUBLIC_CONFIG_SURFACE_BASENAMES = ["channel-config-api", "runtime-api", "api"] as const;
|
||||
|
||||
type BundledPluginPathPair = {
|
||||
source: string;
|
||||
@@ -65,14 +46,7 @@ export type BundledPluginMetadata = {
|
||||
manifest: PluginManifest;
|
||||
};
|
||||
|
||||
type ChannelConfigSurface = {
|
||||
schema: Record<string, unknown>;
|
||||
uiHints?: Record<string, PluginConfigUiHint>;
|
||||
runtime?: ChannelConfigRuntimeSchema;
|
||||
};
|
||||
|
||||
const bundledPluginMetadataCache = new Map<string, readonly BundledPluginMetadata[]>();
|
||||
const jitiLoaders = new Map<string, ReturnType<typeof createJiti>>();
|
||||
|
||||
export function clearBundledPluginMetadataCache(): void {
|
||||
bundledPluginMetadataCache.clear();
|
||||
@@ -109,157 +83,6 @@ function readPackageManifest(pluginDir: string): PackageManifest | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
function isBuiltChannelConfigSchema(value: unknown): value is ChannelConfigSurface {
|
||||
if (!value || typeof value !== "object") {
|
||||
return false;
|
||||
}
|
||||
const candidate = value as { schema?: unknown };
|
||||
return Boolean(candidate.schema && typeof candidate.schema === "object");
|
||||
}
|
||||
|
||||
function resolveConfigSchemaExport(imported: Record<string, unknown>): ChannelConfigSurface | null {
|
||||
for (const [name, value] of Object.entries(imported)) {
|
||||
if (name.endsWith("ChannelConfigSchema") && isBuiltChannelConfigSchema(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, value] of Object.entries(imported)) {
|
||||
if (!name.endsWith("ConfigSchema") || name.endsWith("AccountConfigSchema")) {
|
||||
continue;
|
||||
}
|
||||
if (isBuiltChannelConfigSchema(value)) {
|
||||
return value;
|
||||
}
|
||||
if (value && typeof value === "object") {
|
||||
return buildChannelConfigSchema(value as never);
|
||||
}
|
||||
}
|
||||
|
||||
for (const value of Object.values(imported)) {
|
||||
if (isBuiltChannelConfigSchema(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
const tryNative =
|
||||
shouldPreferNativeJiti(modulePath) || modulePath.includes(`${path.sep}dist${path.sep}`);
|
||||
const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url);
|
||||
const cacheKey = JSON.stringify({
|
||||
tryNative,
|
||||
aliasMap: Object.entries(aliasMap).toSorted(([left], [right]) => left.localeCompare(right)),
|
||||
});
|
||||
const cached = jitiLoaders.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const loader = createJiti(import.meta.url, {
|
||||
...buildPluginLoaderJitiOptions(aliasMap),
|
||||
tryNative,
|
||||
});
|
||||
jitiLoaders.set(cacheKey, loader);
|
||||
return loader;
|
||||
}
|
||||
|
||||
function resolveChannelConfigSchemaModulePath(pluginDir: string): string | undefined {
|
||||
for (const relativePath of SOURCE_CONFIG_SCHEMA_CANDIDATES) {
|
||||
const candidate = path.join(pluginDir, relativePath);
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
for (const basename of PUBLIC_CONFIG_SURFACE_BASENAMES) {
|
||||
for (const extension of PUBLIC_SURFACE_SOURCE_EXTENSIONS) {
|
||||
const candidate = path.join(pluginDir, `${basename}${extension}`);
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function loadChannelConfigSurfaceModuleSync(modulePath: string): ChannelConfigSurface | null {
|
||||
try {
|
||||
const imported = getJiti(modulePath)(modulePath) as Record<string, unknown>;
|
||||
return resolveConfigSchemaExport(imported);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePackageChannelMeta(
|
||||
packageManifest: OpenClawPackageManifest | undefined,
|
||||
channelId: string,
|
||||
): OpenClawPackageManifest["channel"] | undefined {
|
||||
const channelMeta = packageManifest?.channel;
|
||||
return channelMeta?.id?.trim() === channelId ? channelMeta : undefined;
|
||||
}
|
||||
|
||||
function collectBundledChannelConfigs(params: {
|
||||
pluginDir: string;
|
||||
manifest: PluginManifest;
|
||||
packageManifest?: OpenClawPackageManifest;
|
||||
}): Record<string, PluginManifestChannelConfig> | undefined {
|
||||
const channelIds = normalizeStringList(params.manifest.channels);
|
||||
const existingChannelConfigs: Record<string, PluginManifestChannelConfig> =
|
||||
params.manifest.channelConfigs && Object.keys(params.manifest.channelConfigs).length > 0
|
||||
? { ...params.manifest.channelConfigs }
|
||||
: {};
|
||||
if (channelIds.length === 0) {
|
||||
return Object.keys(existingChannelConfigs).length > 0 ? existingChannelConfigs : undefined;
|
||||
}
|
||||
|
||||
const surfaceModulePath = resolveChannelConfigSchemaModulePath(params.pluginDir);
|
||||
const surface = surfaceModulePath ? loadChannelConfigSurfaceModuleSync(surfaceModulePath) : null;
|
||||
|
||||
for (const channelId of channelIds) {
|
||||
const existing = existingChannelConfigs[channelId];
|
||||
const channelMeta = resolvePackageChannelMeta(params.packageManifest, channelId);
|
||||
const preferOver = normalizeStringList(channelMeta?.preferOver);
|
||||
const uiHints: Record<string, PluginConfigUiHint> | undefined =
|
||||
surface?.uiHints || existing?.uiHints
|
||||
? {
|
||||
...(surface?.uiHints && Object.keys(surface.uiHints).length > 0 ? surface.uiHints : {}),
|
||||
...(existing?.uiHints && Object.keys(existing.uiHints).length > 0
|
||||
? existing.uiHints
|
||||
: {}),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
if (!surface?.schema && !existing?.schema) {
|
||||
continue;
|
||||
}
|
||||
|
||||
existingChannelConfigs[channelId] = {
|
||||
schema: surface?.schema ?? existing?.schema ?? {},
|
||||
...(uiHints && Object.keys(uiHints).length > 0 ? { uiHints } : {}),
|
||||
...((surface?.runtime ?? existing?.runtime)
|
||||
? { runtime: surface?.runtime ?? existing?.runtime }
|
||||
: {}),
|
||||
...((trimString(existing?.label) ?? trimString(channelMeta?.label))
|
||||
? { label: trimString(existing?.label) ?? trimString(channelMeta?.label)! }
|
||||
: {}),
|
||||
...((trimString(existing?.description) ?? trimString(channelMeta?.blurb))
|
||||
? {
|
||||
description: trimString(existing?.description) ?? trimString(channelMeta?.blurb)!,
|
||||
}
|
||||
: {}),
|
||||
...(existing?.preferOver?.length
|
||||
? { preferOver: existing.preferOver }
|
||||
: preferOver.length > 0
|
||||
? { preferOver }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
return Object.keys(existingChannelConfigs).length > 0 ? existingChannelConfigs : undefined;
|
||||
}
|
||||
|
||||
function collectBundledPluginMetadataForPackageRoot(
|
||||
packageRoot: string,
|
||||
includeChannelConfigs: boolean,
|
||||
|
||||
Reference in New Issue
Block a user