refactor: simplify bundled plugin contracts

This commit is contained in:
Peter Steinberger
2026-03-26 21:53:47 +00:00
parent 8b42ad08e5
commit 48a65f7749
5 changed files with 307 additions and 213 deletions

View File

@@ -1,10 +1,11 @@
import path from "node:path";
import { collectBundledPluginSources } from "./lib/bundled-plugin-source-utils.mjs";
import { formatGeneratedModule } from "./lib/format-generated-module.mjs";
import { reportGeneratedOutputCli, writeGeneratedOutput } from "./lib/generated-output-utils.mjs";
import { writeGeneratedOutput } from "./lib/generated-output-utils.mjs";
const GENERATED_BY = "scripts/generate-bundled-plugin-metadata.mjs";
const DEFAULT_OUTPUT_PATH = "src/plugins/bundled-plugin-metadata.generated.ts";
const DEFAULT_ENTRIES_OUTPUT_PATH = "src/generated/bundled-plugin-entries.generated.ts";
const MANIFEST_KEY = "openclaw";
const FORMATTER_CWD = path.resolve(import.meta.dirname, "..");
const CANONICAL_PACKAGE_ID_ALIASES = {
@@ -134,6 +135,19 @@ function formatTypeScriptModule(source, { outputPath }) {
});
}
function toIdentifier(dirName) {
const cleaned = String(dirName)
.replace(/[^a-zA-Z0-9]+(.)/g, (_match, next) => next.toUpperCase())
.replace(/[^a-zA-Z0-9]/g, "")
.replace(/^[^a-zA-Z]+/g, "");
const base = cleaned || "plugin";
return `${base[0].toLowerCase()}${base.slice(1)}Plugin`;
}
function normalizeGeneratedImportPath(dirName, builtPath) {
return `../../extensions/${dirName}/${String(builtPath).replace(/^\.\//u, "")}`;
}
export function collectBundledPluginMetadata(params = {}) {
const repoRoot = path.resolve(params.repoRoot ?? process.cwd());
const entries = [];
@@ -202,23 +216,73 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = ${JSON.stringify(entries, null,
`;
}
export function writeBundledPluginMetadataModule(params = {}) {
const repoRoot = path.resolve(params.repoRoot ?? process.cwd());
const outputPath = path.resolve(repoRoot, params.outputPath ?? DEFAULT_OUTPUT_PATH);
const next = formatTypeScriptModule(
renderBundledPluginMetadataModule(collectBundledPluginMetadata({ repoRoot })),
{ outputPath },
);
return writeGeneratedOutput({
repoRoot,
outputPath: params.outputPath ?? DEFAULT_OUTPUT_PATH,
next,
check: params.check,
});
export function renderBundledPluginEntriesModule(entries) {
const imports = entries
.map((entry) => {
const identifier = toIdentifier(entry.dirName);
const importPath = normalizeGeneratedImportPath(entry.dirName, entry.source.built);
return `import ${identifier} from "${importPath}";`;
})
.join("\n");
const identifiers = entries.map((entry) => toIdentifier(entry.dirName)).join(",\n ");
return `// Auto-generated by ${GENERATED_BY}. Do not edit directly.
${imports}
export const GENERATED_BUNDLED_PLUGIN_ENTRIES = [
${identifiers}
] as const;
`;
}
reportGeneratedOutputCli({
importMetaUrl: import.meta.url,
label: "bundled-plugin-metadata",
run: ({ check }) => writeBundledPluginMetadataModule({ check }),
});
export function writeBundledPluginMetadataModule(params = {}) {
const repoRoot = path.resolve(params.repoRoot ?? process.cwd());
const entries = collectBundledPluginMetadata({ repoRoot });
const outputPath = path.resolve(repoRoot, params.outputPath ?? DEFAULT_OUTPUT_PATH);
const entriesOutputPath = path.resolve(
repoRoot,
params.entriesOutputPath ?? DEFAULT_ENTRIES_OUTPUT_PATH,
);
const metadataNext = formatTypeScriptModule(renderBundledPluginMetadataModule(entries), {
outputPath,
});
const registryNext = formatTypeScriptModule(renderBundledPluginEntriesModule(entries), {
outputPath: entriesOutputPath,
});
const metadataResult = writeGeneratedOutput({
repoRoot,
outputPath: params.outputPath ?? DEFAULT_OUTPUT_PATH,
next: metadataNext,
check: params.check,
});
const entriesResult = writeGeneratedOutput({
repoRoot,
outputPath: params.entriesOutputPath ?? DEFAULT_ENTRIES_OUTPUT_PATH,
next: registryNext,
check: params.check,
});
return {
changed: metadataResult.changed || entriesResult.changed,
wrote: metadataResult.wrote || entriesResult.wrote,
outputPaths: [metadataResult.outputPath, entriesResult.outputPath],
};
}
if (import.meta.url === new URL(process.argv[1] ?? "", "file:").href) {
const check = process.argv.includes("--check");
const result = writeBundledPluginMetadataModule({ check });
if (!result.changed) {
process.exitCode = 0;
} else if (check) {
for (const outputPath of result.outputPaths) {
const relativeOutputPath = path.relative(process.cwd(), outputPath);
console.error(`[bundled-plugin-metadata] stale generated output at ${relativeOutputPath}`);
}
process.exitCode = 1;
} else {
for (const outputPath of result.outputPaths) {
const relativeOutputPath = path.relative(process.cwd(), outputPath);
console.log(`[bundled-plugin-metadata] wrote ${relativeOutputPath}`);
}
}
}

View File

@@ -5,8 +5,6 @@ import {
normalizeSpeechProviderId,
} from "../../tts/provider-registry.js";
import {
OPENAI_TTS_MODELS,
OPENAI_TTS_VOICES,
getTtsProvider,
isTtsEnabled,
isTtsProviderConfigured,
@@ -138,14 +136,8 @@ export const ttsHandlers: GatewayRequestHandlers = {
id: provider.id,
name: provider.label,
configured: provider.isConfigured({ cfg, config }),
models:
provider.id === "openai" && provider.models == null
? [...OPENAI_TTS_MODELS]
: [...(provider.models ?? [])],
voices:
provider.id === "openai" && provider.voices == null
? [...OPENAI_TTS_VOICES]
: [...(provider.voices ?? [])],
models: [...(provider.models ?? [])],
voices: [...(provider.voices ?? [])],
})),
active: getTtsProvider(config, prefsPath),
});

View File

@@ -0,0 +1,157 @@
// Auto-generated by scripts/generate-bundled-plugin-metadata.mjs. Do not edit directly.
import acpxPlugin from "../../extensions/acpx/index.js";
import amazonBedrockPlugin from "../../extensions/amazon-bedrock/index.js";
import anthropicPlugin from "../../extensions/anthropic/index.js";
import bluebubblesPlugin from "../../extensions/bluebubbles/index.js";
import bravePlugin from "../../extensions/brave/index.js";
import byteplusPlugin from "../../extensions/byteplus/index.js";
import chutesPlugin from "../../extensions/chutes/index.js";
import cloudflareAiGatewayPlugin from "../../extensions/cloudflare-ai-gateway/index.js";
import copilotProxyPlugin from "../../extensions/copilot-proxy/index.js";
import deepgramPlugin from "../../extensions/deepgram/index.js";
import deepseekPlugin from "../../extensions/deepseek/index.js";
import diagnosticsOtelPlugin from "../../extensions/diagnostics-otel/index.js";
import diffsPlugin from "../../extensions/diffs/index.js";
import discordPlugin from "../../extensions/discord/index.js";
import duckduckgoPlugin from "../../extensions/duckduckgo/index.js";
import elevenlabsPlugin from "../../extensions/elevenlabs/index.js";
import exaPlugin from "../../extensions/exa/index.js";
import falPlugin from "../../extensions/fal/index.js";
import feishuPlugin from "../../extensions/feishu/index.js";
import firecrawlPlugin from "../../extensions/firecrawl/index.js";
import githubCopilotPlugin from "../../extensions/github-copilot/index.js";
import googlePlugin from "../../extensions/google/index.js";
import googlechatPlugin from "../../extensions/googlechat/index.js";
import groqPlugin from "../../extensions/groq/index.js";
import huggingfacePlugin from "../../extensions/huggingface/index.js";
import imessagePlugin from "../../extensions/imessage/index.js";
import ircPlugin from "../../extensions/irc/index.js";
import kilocodePlugin from "../../extensions/kilocode/index.js";
import kimiCodingPlugin from "../../extensions/kimi-coding/index.js";
import linePlugin from "../../extensions/line/index.js";
import llmTaskPlugin from "../../extensions/llm-task/index.js";
import lobsterPlugin from "../../extensions/lobster/index.js";
import matrixPlugin from "../../extensions/matrix/index.js";
import mattermostPlugin from "../../extensions/mattermost/index.js";
import memoryCorePlugin from "../../extensions/memory-core/index.js";
import memoryLancedbPlugin from "../../extensions/memory-lancedb/index.js";
import microsoftFoundryPlugin from "../../extensions/microsoft-foundry/index.js";
import microsoftPlugin from "../../extensions/microsoft/index.js";
import minimaxPlugin from "../../extensions/minimax/index.js";
import mistralPlugin from "../../extensions/mistral/index.js";
import modelstudioPlugin from "../../extensions/modelstudio/index.js";
import moonshotPlugin from "../../extensions/moonshot/index.js";
import msteamsPlugin from "../../extensions/msteams/index.js";
import nextcloudTalkPlugin from "../../extensions/nextcloud-talk/index.js";
import nostrPlugin from "../../extensions/nostr/index.js";
import nvidiaPlugin from "../../extensions/nvidia/index.js";
import ollamaPlugin from "../../extensions/ollama/index.js";
import openProsePlugin from "../../extensions/open-prose/index.js";
import openaiPlugin from "../../extensions/openai/index.js";
import opencodeGoPlugin from "../../extensions/opencode-go/index.js";
import opencodePlugin from "../../extensions/opencode/index.js";
import openrouterPlugin from "../../extensions/openrouter/index.js";
import openshellPlugin from "../../extensions/openshell/index.js";
import perplexityPlugin from "../../extensions/perplexity/index.js";
import qianfanPlugin from "../../extensions/qianfan/index.js";
import sglangPlugin from "../../extensions/sglang/index.js";
import signalPlugin from "../../extensions/signal/index.js";
import slackPlugin from "../../extensions/slack/index.js";
import synologyChatPlugin from "../../extensions/synology-chat/index.js";
import syntheticPlugin from "../../extensions/synthetic/index.js";
import tavilyPlugin from "../../extensions/tavily/index.js";
import telegramPlugin from "../../extensions/telegram/index.js";
import tlonPlugin from "../../extensions/tlon/index.js";
import togetherPlugin from "../../extensions/together/index.js";
import twitchPlugin from "../../extensions/twitch/index.js";
import venicePlugin from "../../extensions/venice/index.js";
import vercelAiGatewayPlugin from "../../extensions/vercel-ai-gateway/index.js";
import vllmPlugin from "../../extensions/vllm/index.js";
import voiceCallPlugin from "../../extensions/voice-call/index.js";
import volcenginePlugin from "../../extensions/volcengine/index.js";
import whatsappPlugin from "../../extensions/whatsapp/index.js";
import xaiPlugin from "../../extensions/xai/index.js";
import xiaomiPlugin from "../../extensions/xiaomi/index.js";
import zaiPlugin from "../../extensions/zai/index.js";
import zaloPlugin from "../../extensions/zalo/index.js";
import zalouserPlugin from "../../extensions/zalouser/index.js";
export const GENERATED_BUNDLED_PLUGIN_ENTRIES = [
acpxPlugin,
amazonBedrockPlugin,
anthropicPlugin,
bluebubblesPlugin,
bravePlugin,
byteplusPlugin,
chutesPlugin,
cloudflareAiGatewayPlugin,
copilotProxyPlugin,
deepgramPlugin,
deepseekPlugin,
diagnosticsOtelPlugin,
diffsPlugin,
discordPlugin,
duckduckgoPlugin,
elevenlabsPlugin,
exaPlugin,
falPlugin,
feishuPlugin,
firecrawlPlugin,
githubCopilotPlugin,
googlePlugin,
googlechatPlugin,
groqPlugin,
huggingfacePlugin,
imessagePlugin,
ircPlugin,
kilocodePlugin,
kimiCodingPlugin,
linePlugin,
llmTaskPlugin,
lobsterPlugin,
matrixPlugin,
mattermostPlugin,
memoryCorePlugin,
memoryLancedbPlugin,
microsoftPlugin,
microsoftFoundryPlugin,
minimaxPlugin,
mistralPlugin,
modelstudioPlugin,
moonshotPlugin,
msteamsPlugin,
nextcloudTalkPlugin,
nostrPlugin,
nvidiaPlugin,
ollamaPlugin,
openProsePlugin,
openaiPlugin,
opencodePlugin,
opencodeGoPlugin,
openrouterPlugin,
openshellPlugin,
perplexityPlugin,
qianfanPlugin,
sglangPlugin,
signalPlugin,
slackPlugin,
synologyChatPlugin,
syntheticPlugin,
tavilyPlugin,
telegramPlugin,
tlonPlugin,
togetherPlugin,
twitchPlugin,
venicePlugin,
vercelAiGatewayPlugin,
vllmPlugin,
voiceCallPlugin,
volcenginePlugin,
whatsappPlugin,
xaiPlugin,
xiaomiPlugin,
zaiPlugin,
zaloPlugin,
zalouserPlugin,
] as const;

View File

@@ -0,0 +1,10 @@
import { GENERATED_BUNDLED_PLUGIN_ENTRIES } from "../generated/bundled-plugin-entries.generated.js";
import type { OpenClawPluginDefinition } from "./types.js";
type BundledRegistrablePlugin = OpenClawPluginDefinition & {
id: string;
register: NonNullable<OpenClawPluginDefinition["register"]>;
};
export const BUNDLED_PLUGIN_ENTRIES =
GENERATED_BUNDLED_PLUGIN_ENTRIES as unknown as readonly BundledRegistrablePlugin[];

View File

@@ -1,46 +1,7 @@
import amazonBedrockPlugin from "../../../extensions/amazon-bedrock/index.js";
import anthropicPlugin from "../../../extensions/anthropic/index.js";
import byteplusPlugin from "../../../extensions/byteplus/index.js";
import chutesPlugin from "../../../extensions/chutes/index.js";
import cloudflareAiGatewayPlugin from "../../../extensions/cloudflare-ai-gateway/index.js";
import copilotProxyPlugin from "../../../extensions/copilot-proxy/index.js";
import deepgramPlugin from "../../../extensions/deepgram/index.js";
import deepseekPlugin from "../../../extensions/deepseek/index.js";
import elevenLabsPlugin from "../../../extensions/elevenlabs/index.js";
import falPlugin from "../../../extensions/fal/index.js";
import githubCopilotPlugin from "../../../extensions/github-copilot/index.js";
import googlePlugin from "../../../extensions/google/index.js";
import groqPlugin from "../../../extensions/groq/index.js";
import huggingFacePlugin from "../../../extensions/huggingface/index.js";
import kilocodePlugin from "../../../extensions/kilocode/index.js";
import kimiCodingPlugin from "../../../extensions/kimi-coding/index.js";
import microsoftFoundryPlugin from "../../../extensions/microsoft-foundry/index.js";
import microsoftPlugin from "../../../extensions/microsoft/index.js";
import minimaxPlugin from "../../../extensions/minimax/index.js";
import mistralPlugin from "../../../extensions/mistral/index.js";
import modelStudioPlugin from "../../../extensions/modelstudio/index.js";
import moonshotPlugin from "../../../extensions/moonshot/index.js";
import nvidiaPlugin from "../../../extensions/nvidia/index.js";
import ollamaPlugin from "../../../extensions/ollama/index.js";
import openAIPlugin from "../../../extensions/openai/index.js";
import opencodeGoPlugin from "../../../extensions/opencode-go/index.js";
import opencodePlugin from "../../../extensions/opencode/index.js";
import openrouterPlugin from "../../../extensions/openrouter/index.js";
import qianfanPlugin from "../../../extensions/qianfan/index.js";
import sglangPlugin from "../../../extensions/sglang/index.js";
import syntheticPlugin from "../../../extensions/synthetic/index.js";
import togetherPlugin from "../../../extensions/together/index.js";
import venicePlugin from "../../../extensions/venice/index.js";
import vercelAiGatewayPlugin from "../../../extensions/vercel-ai-gateway/index.js";
import vllmPlugin from "../../../extensions/vllm/index.js";
import volcenginePlugin from "../../../extensions/volcengine/index.js";
import xaiPlugin from "../../../extensions/xai/index.js";
import xiaomiPlugin from "../../../extensions/xiaomi/index.js";
import zaiPlugin from "../../../extensions/zai/index.js";
import { bundledWebSearchPluginRegistrations } from "../../bundled-web-search-registry.js";
import { BUNDLED_PLUGIN_ENTRIES } from "../bundled-plugin-entries.js";
import { createCapturedPluginRegistration } from "../captured-registration.js";
import { loadPluginManifestRegistry } from "../manifest-registry.js";
import { resolvePluginProviders } from "../provider-auth-choice.runtime.js";
import type {
ImageGenerationProviderPlugin,
MediaUnderstandingProviderPlugin,
@@ -118,28 +79,6 @@ function dedupePlugins<T extends RegistrablePlugin>(
export let providerContractLoadError: Error | undefined;
function loadBundledProviderRegistry(): ProviderContractEntry[] {
try {
providerContractLoadError = undefined;
return resolvePluginProviders({
bundledProviderAllowlistCompat: true,
bundledProviderVitestCompat: true,
cache: false,
activate: false,
})
.filter((provider: ProviderPlugin): provider is ProviderPlugin & { pluginId: string } =>
Boolean(provider.pluginId),
)
.map((provider: ProviderPlugin & { pluginId: string }) => ({
pluginId: provider.pluginId,
provider,
}));
} catch (error) {
providerContractLoadError = error instanceof Error ? error : new Error(String(error));
return [];
}
}
function createLazyArrayView<T>(load: () => T[]): T[] {
return new Proxy([] as T[], {
get(_target, prop) {
@@ -181,23 +120,21 @@ let mediaUnderstandingProviderContractRegistryCache:
let imageGenerationProviderContractRegistryCache: ImageGenerationProviderContractEntry[] | null =
null;
let pluginRegistrationContractRegistryCache: PluginRegistrationContractEntry[] | null = null;
let providerRegistrationEntriesLoaded = false;
function loadProviderContractRegistry(): ProviderContractEntry[] {
if (!providerContractRegistryCache) {
providerContractRegistryCache = buildCapabilityContractRegistry({
plugins: bundledProviderPlugins,
select: (captured) => captured.providers,
}).map((entry) => ({
pluginId: entry.pluginId,
provider: entry.provider,
}));
}
if (!providerRegistrationEntriesLoaded) {
const registrationEntries = loadPluginRegistrationContractRegistry();
if (!providerRegistrationEntriesLoaded) {
mergeProviderContractRegistrations(registrationEntries, providerContractRegistryCache);
providerRegistrationEntriesLoaded = true;
try {
providerContractLoadError = undefined;
providerContractRegistryCache = buildCapabilityContractRegistry({
plugins: bundledProviderPlugins,
select: (captured) => captured.providers,
}).map((entry) => ({
pluginId: entry.pluginId,
provider: entry.provider,
}));
} catch (error) {
providerContractLoadError = error instanceof Error ? error : new Error(String(error));
providerContractRegistryCache = [];
}
}
return providerContractRegistryCache;
@@ -243,7 +180,7 @@ export function requireProviderContractProvider(providerId: string): ProviderPlu
const provider = uniqueProviderContractProviders.find((entry) => entry.id === providerId);
if (!provider) {
if (!providerContractLoadError) {
loadBundledProviderRegistry();
loadProviderContractRegistry();
}
if (providerContractLoadError) {
throw new Error(
@@ -338,92 +275,52 @@ export const mediaUnderstandingProviderContractRegistry: MediaUnderstandingProvi
export const imageGenerationProviderContractRegistry: ImageGenerationProviderContractEntry[] =
createLazyArrayView(loadImageGenerationProviderContractRegistry);
const bundledProviderPlugins = dedupePlugins([
amazonBedrockPlugin,
anthropicPlugin,
byteplusPlugin,
chutesPlugin,
cloudflareAiGatewayPlugin,
copilotProxyPlugin,
deepseekPlugin,
githubCopilotPlugin,
falPlugin,
googlePlugin,
huggingFacePlugin,
kilocodePlugin,
kimiCodingPlugin,
microsoftFoundryPlugin,
minimaxPlugin,
mistralPlugin,
modelStudioPlugin,
moonshotPlugin,
nvidiaPlugin,
ollamaPlugin,
openAIPlugin,
opencodePlugin,
opencodeGoPlugin,
openrouterPlugin,
qianfanPlugin,
sglangPlugin,
syntheticPlugin,
togetherPlugin,
venicePlugin,
vercelAiGatewayPlugin,
vllmPlugin,
volcenginePlugin,
xaiPlugin,
xiaomiPlugin,
zaiPlugin,
]);
const bundledRegistrablePluginsById = new Map(
dedupePlugins([
...bundledProviderPlugins,
elevenLabsPlugin,
microsoftPlugin,
deepgramPlugin,
groqPlugin,
...bundledWebSearchPlugins,
]).map((plugin) => [plugin.id, plugin]),
dedupePlugins([...BUNDLED_PLUGIN_ENTRIES, ...bundledWebSearchPlugins]).map((plugin) => [
plugin.id,
plugin,
]),
);
function resolveBundledCapabilityPluginIds(
capability: "speechProviders" | "mediaUnderstandingProviders" | "imageGenerationProviders",
function resolveBundledManifestPluginIds(
predicate: (plugin: ReturnType<typeof loadPluginManifestRegistry>["plugins"][number]) => boolean,
): string[] {
return loadPluginManifestRegistry({})
.plugins.filter(
(plugin) => plugin.origin === "bundled" && (plugin[capability]?.length ?? 0) > 0,
)
.plugins.filter((plugin) => plugin.origin === "bundled" && predicate(plugin))
.map((plugin) => plugin.id)
.toSorted((left, right) => left.localeCompare(right));
}
function resolveBundledCapabilityPlugins(
capability: "speechProviders" | "mediaUnderstandingProviders" | "imageGenerationProviders",
function resolveBundledRegistrablePlugins(
predicate: (plugin: ReturnType<typeof loadPluginManifestRegistry>["plugins"][number]) => boolean,
): RegistrablePlugin[] {
return resolveBundledCapabilityPluginIds(capability).flatMap((pluginId) => {
return resolveBundledManifestPluginIds(predicate).flatMap((pluginId) => {
const plugin = bundledRegistrablePluginsById.get(pluginId);
return plugin ? [plugin] : [];
});
}
const bundledSpeechPlugins = resolveBundledCapabilityPlugins("speechProviders");
const bundledMediaUnderstandingPlugins = resolveBundledCapabilityPlugins(
"mediaUnderstandingProviders",
const bundledProviderPlugins = resolveBundledRegistrablePlugins(
(plugin) => plugin.providers.length > 0,
);
const bundledSpeechPlugins = resolveBundledRegistrablePlugins(
(plugin) => (plugin.speechProviders?.length ?? 0) > 0,
);
const bundledMediaUnderstandingPlugins = resolveBundledRegistrablePlugins(
(plugin) => (plugin.mediaUnderstandingProviders?.length ?? 0) > 0,
);
const bundledImageGenerationPlugins = resolveBundledRegistrablePlugins(
(plugin) => (plugin.imageGenerationProviders?.length ?? 0) > 0,
);
const bundledImageGenerationPlugins = resolveBundledCapabilityPlugins("imageGenerationProviders");
const bundledPluginRegistrationList = dedupePlugins([
...bundledProviderPlugins,
...bundledSpeechPlugins,
...bundledMediaUnderstandingPlugins,
...bundledImageGenerationPlugins,
...bundledWebSearchPlugins,
]);
function mergeIds(existing: string[], next: string[]): string[] {
return next.length > 0 ? next : existing;
}
function upsertPluginRegistrationContractEntry(
entries: PluginRegistrationContractEntry[],
next: PluginRegistrationContractEntry,
@@ -433,46 +330,27 @@ function upsertPluginRegistrationContractEntry(
entries.push(next);
return;
}
existing.cliBackendIds = mergeIds(existing.cliBackendIds, next.cliBackendIds);
existing.providerIds = mergeIds(existing.providerIds, next.providerIds);
existing.speechProviderIds = mergeIds(existing.speechProviderIds, next.speechProviderIds);
existing.mediaUnderstandingProviderIds = mergeIds(
existing.mediaUnderstandingProviderIds,
next.mediaUnderstandingProviderIds,
existing.cliBackendIds = [
...new Set([...existing.cliBackendIds, ...next.cliBackendIds]),
].toSorted((left, right) => left.localeCompare(right));
existing.providerIds = [...new Set([...existing.providerIds, ...next.providerIds])].toSorted(
(left, right) => left.localeCompare(right),
);
existing.imageGenerationProviderIds = mergeIds(
existing.imageGenerationProviderIds,
next.imageGenerationProviderIds,
existing.speechProviderIds = [
...new Set([...existing.speechProviderIds, ...next.speechProviderIds]),
].toSorted((left, right) => left.localeCompare(right));
existing.mediaUnderstandingProviderIds = [
...new Set([...existing.mediaUnderstandingProviderIds, ...next.mediaUnderstandingProviderIds]),
].toSorted((left, right) => left.localeCompare(right));
existing.imageGenerationProviderIds = [
...new Set([...existing.imageGenerationProviderIds, ...next.imageGenerationProviderIds]),
].toSorted((left, right) => left.localeCompare(right));
existing.webSearchProviderIds = [
...new Set([...existing.webSearchProviderIds, ...next.webSearchProviderIds]),
].toSorted((left, right) => left.localeCompare(right));
existing.toolNames = [...new Set([...existing.toolNames, ...next.toolNames])].toSorted(
(left, right) => left.localeCompare(right),
);
existing.webSearchProviderIds = mergeIds(
existing.webSearchProviderIds,
next.webSearchProviderIds,
);
existing.toolNames = mergeIds(existing.toolNames, next.toolNames);
}
function mergeProviderContractRegistrations(
registrationEntries: PluginRegistrationContractEntry[],
providerEntries: ProviderContractEntry[],
): void {
const byPluginId = new Map<string, string[]>();
for (const entry of providerEntries) {
const providerIds = byPluginId.get(entry.pluginId) ?? [];
providerIds.push(entry.provider.id);
byPluginId.set(entry.pluginId, providerIds);
}
for (const [pluginId, providerIds] of byPluginId) {
upsertPluginRegistrationContractEntry(registrationEntries, {
pluginId,
cliBackendIds: [],
providerIds: providerIds.toSorted((left, right) => left.localeCompare(right)),
speechProviderIds: [],
mediaUnderstandingProviderIds: [],
imageGenerationProviderIds: [],
webSearchProviderIds: [],
toolNames: [],
});
}
}
function loadPluginRegistrationContractRegistry(): PluginRegistrationContractEntry[] {
@@ -497,13 +375,6 @@ function loadPluginRegistrationContractRegistry(): PluginRegistrationContractEnt
}
pluginRegistrationContractRegistryCache = entries;
}
if (providerContractRegistryCache && !providerRegistrationEntriesLoaded) {
mergeProviderContractRegistrations(
pluginRegistrationContractRegistryCache,
providerContractRegistryCache,
);
providerRegistrationEntriesLoaded = true;
}
return pluginRegistrationContractRegistryCache;
}