perf(channels): read bundled channel metadata directly

This commit is contained in:
Vincent Koc
2026-04-13 17:43:24 +01:00
parent 88111453cb
commit fdf7dbd6eb
3 changed files with 125 additions and 26 deletions

View File

@@ -0,0 +1,114 @@
import fs from "node:fs";
import path from "node:path";
import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js";
import type { PluginPackageChannel } from "../plugins/manifest.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
type ChannelCatalogEntryLike = {
openclaw?: {
channel?: PluginPackageChannel;
};
};
export type BundledChannelCatalogEntry = {
id: string;
channel: PluginPackageChannel;
aliases: readonly string[];
order: number;
};
const OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH = path.join("dist", "channel-catalog.json");
function listPackageRoots(): string[] {
return [
resolveOpenClawPackageRootSync({ cwd: process.cwd() }),
resolveOpenClawPackageRootSync({ moduleUrl: import.meta.url }),
].filter((entry, index, all): entry is string => Boolean(entry) && all.indexOf(entry) === index);
}
function listBundledExtensionPackageJsonPaths(): string[] {
for (const packageRoot of listPackageRoots()) {
const extensionsRoot = path.join(packageRoot, "extensions");
if (!fs.existsSync(extensionsRoot)) {
continue;
}
try {
return fs
.readdirSync(extensionsRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => path.join(extensionsRoot, entry.name, "package.json"))
.filter((entry) => fs.existsSync(entry));
} catch {
continue;
}
}
return [];
}
function readBundledExtensionCatalogEntriesSync(): ChannelCatalogEntryLike[] {
const entries: ChannelCatalogEntryLike[] = [];
for (const packageJsonPath of listBundledExtensionPackageJsonPaths()) {
try {
const payload = JSON.parse(
fs.readFileSync(packageJsonPath, "utf8"),
) as ChannelCatalogEntryLike;
entries.push(payload);
} catch {
continue;
}
}
return entries;
}
function readOfficialCatalogFileSync(): ChannelCatalogEntryLike[] {
for (const packageRoot of listPackageRoots()) {
const candidate = path.join(packageRoot, OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH);
if (!fs.existsSync(candidate)) {
continue;
}
try {
const payload = JSON.parse(fs.readFileSync(candidate, "utf8")) as {
entries?: unknown;
};
return Array.isArray(payload.entries) ? (payload.entries as ChannelCatalogEntryLike[]) : [];
} catch {
continue;
}
}
return [];
}
function toBundledChannelEntry(entry: ChannelCatalogEntryLike): BundledChannelCatalogEntry | null {
const channel = entry.openclaw?.channel;
const id = normalizeOptionalLowercaseString(channel?.id);
if (!id || !channel) {
return null;
}
const aliases = Array.isArray(channel.aliases)
? channel.aliases
.map((alias) => normalizeOptionalLowercaseString(alias))
.filter((alias): alias is string => Boolean(alias))
: [];
const order =
typeof channel.order === "number" && Number.isFinite(channel.order)
? channel.order
: Number.MAX_SAFE_INTEGER;
return {
id,
channel,
aliases,
order,
};
}
export function listBundledChannelCatalogEntries(): BundledChannelCatalogEntry[] {
const bundledEntries = readBundledExtensionCatalogEntriesSync()
.map((entry) => toBundledChannelEntry(entry))
.filter((entry): entry is BundledChannelCatalogEntry => Boolean(entry));
if (bundledEntries.length > 0) {
return bundledEntries;
}
return readOfficialCatalogFileSync()
.map((entry) => toBundledChannelEntry(entry))
.filter((entry): entry is BundledChannelCatalogEntry => Boolean(entry));
}

View File

@@ -1,6 +1,6 @@
import { listChannelCatalogEntries } from "../plugins/channel-catalog-registry.js";
import type { PluginPackageChannel } from "../plugins/manifest.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { listBundledChannelCatalogEntries } from "./bundled-channel-catalog-read.js";
import { CHAT_CHANNEL_ORDER, type ChatChannelId } from "./ids.js";
import { resolveChannelExposure } from "./plugins/exposure.js";
import type { ChannelMeta } from "./plugins/types.core.js";
@@ -65,12 +65,8 @@ function toChatChannelMeta(params: {
export function buildChatChannelMetaById(): Record<ChatChannelId, ChatChannelMeta> {
const entries = new Map<ChatChannelId, ChatChannelMeta>();
for (const entry of listChannelCatalogEntries({ origin: "bundled" })) {
const channel = entry.channel;
if (!channel) {
continue;
}
const rawId = normalizeOptionalString(channel.id);
for (const entry of listBundledChannelCatalogEntries()) {
const rawId = normalizeOptionalString(entry.id);
if (!rawId || !CHAT_CHANNEL_ID_SET.has(rawId)) {
continue;
}
@@ -79,7 +75,7 @@ export function buildChatChannelMetaById(): Record<ChatChannelId, ChatChannelMet
id,
toChatChannelMeta({
id,
channel,
channel: entry.channel,
}),
);
}

View File

@@ -1,5 +1,5 @@
import { listChannelCatalogEntries } from "../plugins/channel-catalog-registry.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import { listBundledChannelCatalogEntries } from "./bundled-channel-catalog-read.js";
export type ChatChannelId = string;
@@ -10,23 +10,12 @@ type BundledChatChannelEntry = {
};
function listBundledChatChannelEntries(): BundledChatChannelEntry[] {
return listChannelCatalogEntries({ origin: "bundled" })
.flatMap(({ channel }) => {
const id = normalizeOptionalLowercaseString(channel.id);
if (!id) {
return [];
}
const aliases = (channel.aliases ?? [])
.map((alias) => normalizeOptionalLowercaseString(alias))
.filter((alias): alias is string => Boolean(alias));
return [
{
id,
aliases,
order: typeof channel.order === "number" ? channel.order : Number.MAX_SAFE_INTEGER,
},
];
})
return listBundledChannelCatalogEntries()
.map((entry) => ({
id: normalizeOptionalLowercaseString(entry.id) ?? entry.id,
aliases: entry.aliases,
order: entry.order,
}))
.toSorted(
(left, right) =>
left.order - right.order || left.id.localeCompare(right.id, "en", { sensitivity: "base" }),