// Registry contract tests cover plugin contract registry contents and lookup behavior. import { sortUniqueStrings } from "@openclaw/normalization-core/string-normalization"; import { describe, expect, it } from "vitest"; import { loadPluginManifestRegistryCore, type PluginManifestRecord } from "../manifest-registry.js"; import { resolveManifestContractPluginIds } from "../plugin-registry.js"; import { BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS } from "./inventory/bundled-capability-metadata.js"; import { pluginRegistrationContractRegistry, providerContractLoadError } from "./registry.js"; const ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_IDS = ["codex", "qa-lab"] as const; const ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_ID_SET = new Set( ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_IDS, ); describe("plugin contract registry", () => { function expectUniqueIds(ids: readonly string[]) { expect(ids).toEqual([...new Set(ids)]); } function expectRegistryPluginIds(params: { actualPluginIds: readonly string[]; predicate: (plugin: PluginManifestRecord) => boolean; }) { expect(sortUniqueStrings(params.actualPluginIds)).toEqual( resolveBundledManifestPluginIds(params.predicate), ); } function resolveBundledManifestPluginIds(predicate: (plugin: PluginManifestRecord) => boolean) { if (process.env.VITEST) { return BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.map( (entry) => ({ id: entry.pluginId, origin: "bundled", providers: entry.providerIds, contracts: { embeddingProviders: entry.embeddingProviderIds, workerProviders: entry.workerProviderIds, speechProviders: entry.speechProviderIds, realtimeTranscriptionProviders: entry.realtimeTranscriptionProviderIds, realtimeVoiceProviders: entry.realtimeVoiceProviderIds, mediaUnderstandingProviders: entry.mediaUnderstandingProviderIds, transcriptSourceProviders: entry.transcriptSourceProviderIds, documentExtractors: entry.documentExtractorIds, imageGenerationProviders: entry.imageGenerationProviderIds, videoGenerationProviders: entry.videoGenerationProviderIds, musicGenerationProviders: entry.musicGenerationProviderIds, webContentExtractors: entry.webContentExtractorIds, webFetchProviders: entry.webFetchProviderIds, webSearchProviders: entry.webSearchProviderIds, migrationProviders: entry.migrationProviderIds, tools: entry.toolNames, }, }) as PluginManifestRecord, ) .filter(predicate) .map((plugin) => plugin.id) .toSorted((left, right) => left.localeCompare(right)); } const snapshotPluginIds = new Set( BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.map((entry) => entry.pluginId), ); return loadPluginManifestRegistryCore({}) .plugins.filter((plugin) => snapshotPluginIds.has(plugin.id) && predicate(plugin)) .map((plugin) => plugin.id) .toSorted((left, right) => left.localeCompare(right)); } it("loads bundled non-provider capability registries without import-time failure", () => { expect(providerContractLoadError).toBeUndefined(); expect(Array.from(pluginRegistrationContractRegistry)).toStrictEqual( BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS, ); }); it.each([ { name: "does not duplicate bundled provider ids", ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.providerIds), }, { name: "does not duplicate bundled worker provider ids", ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.workerProviderIds), }, { name: "does not duplicate bundled web fetch provider ids", ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.webFetchProviderIds), }, { name: "does not duplicate bundled web search provider ids", ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.webSearchProviderIds), }, { name: "does not duplicate bundled migration provider ids", ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.migrationProviderIds), }, { name: "does not duplicate bundled media provider ids", ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.mediaUnderstandingProviderIds), }, { name: "does not duplicate bundled transcripts source provider ids", ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.transcriptSourceProviderIds), }, { name: "does not duplicate bundled realtime transcription provider ids", ids: () => pluginRegistrationContractRegistry.flatMap( (entry) => entry.realtimeTranscriptionProviderIds, ), }, { name: "does not duplicate bundled realtime voice provider ids", ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.realtimeVoiceProviderIds), }, { name: "does not duplicate bundled image-generation provider ids", ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.imageGenerationProviderIds), }, ] as const)("$name", ({ ids }) => { expectUniqueIds(ids()); }); it("does not duplicate bundled speech provider ids", () => { expectUniqueIds(pluginRegistrationContractRegistry.flatMap((entry) => entry.speechProviderIds)); }); it.each([ ["azure-speech", "speechProviderIds", ["azure-speech", "azure"]], ["microsoft", "speechProviderIds", ["microsoft", "edge"]], ["tts-local-cli", "speechProviderIds", ["tts-local-cli", "cli"]], ["volcengine", "speechProviderIds", ["volcengine", "bytedance", "doubao"]], ["xiaomi", "speechProviderIds", ["xiaomi", "mimo"]], ["xai", "realtimeVoiceProviderIds", ["xai", "grok-voice", "xai-realtime-voice"]], ] as const)("declares canonical-first %s %s aliases", (pluginId, contract, providerIds) => { expect( pluginRegistrationContractRegistry.find((entry) => entry.pluginId === pluginId)?.[contract], ).toEqual(providerIds); }); it("covers every bundled worker provider plugin discovered from manifests", () => { expectRegistryPluginIds({ actualPluginIds: pluginRegistrationContractRegistry .filter((entry) => entry.workerProviderIds.length > 0) .map((entry) => entry.pluginId), predicate: (plugin) => plugin.origin === "bundled" && (plugin.contracts?.workerProviders?.length ?? 0) > 0, }); }); it("keeps video-only provider auth choices out of text onboarding", () => { const registry = loadPluginManifestRegistryCore({}); for (const pluginId of ["alibaba", "runway"]) { const plugin = registry.plugins.find( (entry) => entry.origin === "bundled" && entry.id === pluginId, ); expect(plugin?.providerAuthChoices).toEqual([ { provider: pluginId, method: "api-key", choiceId: pluginId === "alibaba" ? "alibaba-model-studio-api-key" : "runway-api-key", choiceLabel: pluginId === "alibaba" ? "Alibaba Model Studio API key" : "Runway API key", groupId: pluginId, groupLabel: pluginId === "alibaba" ? "Alibaba Model Studio" : "Runway", groupHint: pluginId === "alibaba" ? "DashScope / Model Studio API key" : "API key", onboardingScopes: ["image-generation"], optionKey: pluginId === "alibaba" ? "alibabaModelStudioApiKey" : "runwayApiKey", cliFlag: pluginId === "alibaba" ? "--alibaba-model-studio-api-key" : "--runway-api-key", cliOption: pluginId === "alibaba" ? "--alibaba-model-studio-api-key " : "--runway-api-key ", cliDescription: pluginId === "alibaba" ? "Alibaba Model Studio API key" : "Runway API key", }, ]); } }); it("exposes the GitHub Copilot non-interactive onboarding token flag from manifest metadata", () => { const registry = loadPluginManifestRegistryCore({}); const plugin = registry.plugins.find( (entry) => entry.origin === "bundled" && entry.id === "github-copilot", ); expect(plugin?.providerAuthChoices).toEqual([ { provider: "github-copilot", method: "device", appGuidedAuth: "device-code", credentialOnly: true, channelLogin: {}, appGuidedSecret: true, choiceId: "github-copilot", choiceLabel: "GitHub Copilot", choiceHint: "Device login with your GitHub account", assistantPriority: 1, groupId: "copilot", groupLabel: "Copilot", groupHint: "GitHub, GitHub Enterprise + Local Proxy", optionKey: "githubCopilotToken", cliFlag: "--github-copilot-token", cliOption: "--github-copilot-token ", cliDescription: "GitHub Copilot OAuth token", }, { provider: "github-copilot", method: "device-enterprise", appGuidedAuth: "device-code", credentialOnly: true, choiceId: "github-copilot-enterprise", choiceLabel: "GitHub Copilot (Enterprise / data residency)", choiceHint: "Device login against your GitHub Enterprise (*.ghe.com) tenant", assistantPriority: 2, groupId: "copilot", groupLabel: "Copilot", groupHint: "GitHub, GitHub Enterprise + Local Proxy", }, ]); }); it("covers every bundled speech plugin discovered from manifests", () => { expectRegistryPluginIds({ actualPluginIds: pluginRegistrationContractRegistry .filter((entry) => entry.speechProviderIds.length > 0) .map((entry) => entry.pluginId), predicate: (plugin) => plugin.origin === "bundled" && (plugin.contracts?.speechProviders?.length ?? 0) > 0, }); }); it("covers every bundled realtime voice plugin discovered from manifests", () => { expectRegistryPluginIds({ actualPluginIds: pluginRegistrationContractRegistry .filter((entry) => entry.realtimeVoiceProviderIds.length > 0) .map((entry) => entry.pluginId), predicate: (plugin) => plugin.origin === "bundled" && (plugin.contracts?.realtimeVoiceProviders?.length ?? 0) > 0, }); }); it("covers every bundled realtime transcription plugin discovered from manifests", () => { expectRegistryPluginIds({ actualPluginIds: pluginRegistrationContractRegistry .filter((entry) => entry.realtimeTranscriptionProviderIds.length > 0) .map((entry) => entry.pluginId), predicate: (plugin) => plugin.origin === "bundled" && (plugin.contracts?.realtimeTranscriptionProviders?.length ?? 0) > 0, }); }); it("covers every bundled transcripts source plugin discovered from manifests", () => { expectRegistryPluginIds({ actualPluginIds: pluginRegistrationContractRegistry .filter((entry) => entry.transcriptSourceProviderIds.length > 0) .map((entry) => entry.pluginId), predicate: (plugin) => plugin.origin === "bundled" && (plugin.contracts?.transcriptSourceProviders?.length ?? 0) > 0, }); }); it("covers every bundled web fetch plugin from the shared resolver", () => { const bundledWebFetchPluginIds = resolveManifestContractPluginIds({ contract: "webFetchProviders", origin: "bundled", }); expect( sortUniqueStrings( pluginRegistrationContractRegistry .filter((entry) => entry.webFetchProviderIds.length > 0) .map((entry) => entry.pluginId), ), ).toEqual(bundledWebFetchPluginIds); }); it("covers every bundled web search plugin from the shared resolver", () => { const snapshotPluginIds = new Set( BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.map((entry) => entry.pluginId), ); const bundledWebSearchPluginIds = resolveManifestContractPluginIds({ contract: "webSearchProviders", origin: "bundled", }).filter( (pluginId) => snapshotPluginIds.has(pluginId) && !ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_ID_SET.has(pluginId), ); const expectedPluginIds = sortUniqueStrings([ ...bundledWebSearchPluginIds, ...ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_IDS, ]); const actualPluginIds = sortUniqueStrings( pluginRegistrationContractRegistry .filter((entry) => entry.webSearchProviderIds.length > 0) .map((entry) => entry.pluginId), ); expect(actualPluginIds).toEqual(expectedPluginIds); expect( actualPluginIds.filter((pluginId) => !bundledWebSearchPluginIds.includes(pluginId)), ).toEqual([...ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_IDS]); }); it("covers every bundled migration provider plugin discovered from manifests", () => { expectRegistryPluginIds({ actualPluginIds: pluginRegistrationContractRegistry .filter((entry) => entry.migrationProviderIds.length > 0) .map((entry) => entry.pluginId), predicate: (plugin) => plugin.origin === "bundled" && (plugin.contracts?.migrationProviders?.length ?? 0) > 0, }); }); });