refactor: load bundled provider catalogs dynamically

This commit is contained in:
Peter Steinberger 2026-03-28 16:57:16 +00:00
parent 54313a8730
commit 5194cf2019
3 changed files with 189 additions and 27 deletions

View File

@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest";
import {
loadBundledProviderCatalogExportMap,
resolveBundledProviderCatalogEntries,
} from "./models-config.providers.static.js";
describe("models-config bundled provider catalogs", () => {
it("detects provider catalogs from plugin folders via metadata artifacts", () => {
const entries = resolveBundledProviderCatalogEntries();
expect(entries.map((entry) => entry.dirName)).toEqual(
expect.arrayContaining(["openrouter", "volcengine"]),
);
expect(entries.find((entry) => entry.dirName === "volcengine")).toMatchObject({
dirName: "volcengine",
pluginId: "volcengine",
});
});
it("loads provider catalog exports from detected plugin folders", async () => {
const exports = await loadBundledProviderCatalogExportMap();
expect(exports.buildOpenrouterProvider).toBeTypeOf("function");
expect(exports.buildDoubaoProvider).toBeTypeOf("function");
expect(exports.buildDoubaoCodingProvider).toBeTypeOf("function");
});
});

View File

@ -1,27 +1,125 @@
export {
ANTHROPIC_VERTEX_DEFAULT_MODEL_ID,
MODELSTUDIO_BASE_URL,
MODELSTUDIO_DEFAULT_MODEL_ID,
QIANFAN_BASE_URL,
QIANFAN_DEFAULT_MODEL_ID,
XIAOMI_DEFAULT_MODEL_ID,
buildAnthropicVertexProvider,
buildBytePlusCodingProvider,
buildBytePlusProvider,
buildDeepSeekProvider,
buildDoubaoCodingProvider,
buildDoubaoProvider,
buildKilocodeProvider,
buildKimiCodingProvider,
buildMinimaxPortalProvider,
buildMinimaxProvider,
buildModelStudioProvider,
buildMoonshotProvider,
buildNvidiaProvider,
buildOpenAICodexProvider,
buildOpenrouterProvider,
buildQianfanProvider,
buildSyntheticProvider,
buildTogetherProvider,
buildXiaomiProvider,
} from "../plugin-sdk/provider-catalog.js";
import path from "node:path";
import { pathToFileURL } from "node:url";
import {
BUNDLED_PLUGIN_METADATA,
resolveBundledPluginPublicSurfacePath,
} from "../plugins/bundled-plugin-metadata.js";
const PROVIDER_CATALOG_ARTIFACT_BASENAME = "provider-catalog.js";
const DEFAULT_PROVIDER_CATALOG_ROOT = path.resolve(import.meta.dirname, "../..");
export type BundledProviderCatalogEntry = {
dirName: string;
pluginId: string;
providers: readonly string[];
artifactPath: string;
};
type ProviderCatalogModule = Record<string, unknown>;
type ProviderCatalogExportMap = Record<string, unknown>;
let providerCatalogEntriesCache: ReadonlyArray<BundledProviderCatalogEntry> | null = null;
let providerCatalogModulesPromise: Promise<Readonly<Record<string, ProviderCatalogModule>>> | null =
null;
let providerCatalogExportMapPromise: Promise<Readonly<ProviderCatalogExportMap>> | null = null;
export function resolveBundledProviderCatalogEntries(params?: {
rootDir?: string;
}): ReadonlyArray<BundledProviderCatalogEntry> {
const rootDir = params?.rootDir ?? DEFAULT_PROVIDER_CATALOG_ROOT;
if (rootDir === DEFAULT_PROVIDER_CATALOG_ROOT && providerCatalogEntriesCache) {
return providerCatalogEntriesCache;
}
const entries: BundledProviderCatalogEntry[] = [];
for (const entry of BUNDLED_PLUGIN_METADATA) {
if (!entry.publicSurfaceArtifacts?.includes(PROVIDER_CATALOG_ARTIFACT_BASENAME)) {
continue;
}
const artifactPath = resolveBundledPluginPublicSurfacePath({
rootDir,
dirName: entry.dirName,
artifactBasename: PROVIDER_CATALOG_ARTIFACT_BASENAME,
});
if (!artifactPath) {
continue;
}
entries.push({
dirName: entry.dirName,
pluginId: entry.manifest.id,
providers: entry.manifest.providers ?? [],
artifactPath,
});
}
entries.sort((left, right) => left.dirName.localeCompare(right.dirName));
if (rootDir === DEFAULT_PROVIDER_CATALOG_ROOT) {
providerCatalogEntriesCache = entries;
}
return entries;
}
export async function loadBundledProviderCatalogModules(params?: {
rootDir?: string;
}): Promise<Readonly<Record<string, ProviderCatalogModule>>> {
const rootDir = params?.rootDir ?? DEFAULT_PROVIDER_CATALOG_ROOT;
if (rootDir === DEFAULT_PROVIDER_CATALOG_ROOT && providerCatalogModulesPromise) {
return providerCatalogModulesPromise;
}
const loadPromise = (async () => {
const entries = resolveBundledProviderCatalogEntries({ rootDir });
const modules = await Promise.all(
entries.map(async (entry) => {
const module = (await import(
pathToFileURL(entry.artifactPath).href
)) as ProviderCatalogModule;
return [entry.dirName, module] as const;
}),
);
return Object.freeze(Object.fromEntries(modules));
})();
if (rootDir === DEFAULT_PROVIDER_CATALOG_ROOT) {
providerCatalogModulesPromise = loadPromise;
}
return loadPromise;
}
export async function loadBundledProviderCatalogExportMap(params?: {
rootDir?: string;
}): Promise<Readonly<ProviderCatalogExportMap>> {
const rootDir = params?.rootDir ?? DEFAULT_PROVIDER_CATALOG_ROOT;
if (rootDir === DEFAULT_PROVIDER_CATALOG_ROOT && providerCatalogExportMapPromise) {
return providerCatalogExportMapPromise;
}
const loadPromise = (async () => {
const modules = await loadBundledProviderCatalogModules({ rootDir });
const exports: ProviderCatalogExportMap = {};
const exportOwners = new Map<string, string>();
for (const [dirName, module] of Object.entries(modules)) {
for (const [exportName, exportValue] of Object.entries(module)) {
if (exportName === "default") {
continue;
}
const existingOwner = exportOwners.get(exportName);
if (existingOwner && existingOwner !== dirName) {
throw new Error(
`Duplicate provider catalog export "${exportName}" from folders "${existingOwner}" and "${dirName}"`,
);
}
exportOwners.set(exportName, dirName);
exports[exportName] = exportValue;
}
}
return Object.freeze(exports);
})();
if (rootDir === DEFAULT_PROVIDER_CATALOG_ROOT) {
providerCatalogExportMapPromise = loadPromise;
}
return loadPromise;
}

View File

@ -3,6 +3,8 @@ import path from "node:path";
import { GENERATED_BUNDLED_PLUGIN_METADATA } from "./bundled-plugin-metadata.generated.js";
import type { PluginManifest, OpenClawPackageManifest } from "./manifest.js";
const PUBLIC_SURFACE_SOURCE_EXTENSIONS = [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"] as const;
type GeneratedBundledPluginPathPair = {
source: string;
built: string;
@ -44,3 +46,40 @@ export function resolveBundledPluginGeneratedPath(
}
return null;
}
export function resolveBundledPluginPublicSurfacePath(params: {
rootDir: string;
dirName: string;
artifactBasename: string;
}): string | null {
const artifactBasename = params.artifactBasename.replace(/^\.\//u, "");
if (!artifactBasename) {
return null;
}
const builtCandidate = path.resolve(
params.rootDir,
"dist",
"extensions",
params.dirName,
artifactBasename,
);
if (fs.existsSync(builtCandidate)) {
return builtCandidate;
}
const sourceBaseName = artifactBasename.replace(/\.js$/u, "");
for (const ext of PUBLIC_SURFACE_SOURCE_EXTENSIONS) {
const sourceCandidate = path.resolve(
params.rootDir,
"extensions",
params.dirName,
`${sourceBaseName}${ext}`,
);
if (fs.existsSync(sourceCandidate)) {
return sourceCandidate;
}
}
return null;
}