import { beforeEach, describe, expect, it, vi } from "vitest"; type LoadOpenClawProviderIndex = typeof import("../model-catalog/index.js").loadOpenClawProviderIndex; type LoadPluginRegistrySnapshot = typeof import("./plugin-registry.js").loadPluginRegistrySnapshot; type ResolveManifestProviderAuthChoices = typeof import("./provider-auth-choices.js").resolveManifestProviderAuthChoices; type ListOfficialExternalProviderCatalogEntries = typeof import("./official-external-plugin-catalog.js").listOfficialExternalProviderCatalogEntries; const loadOpenClawProviderIndex = vi.hoisted(() => vi.fn(() => ({ version: 1, providers: {} })), ); vi.mock("../model-catalog/index.js", async () => { const actual = await vi.importActual( "../model-catalog/index.js", ); return { ...actual, loadOpenClawProviderIndex, }; }); const loadPluginRegistrySnapshot = vi.hoisted(() => vi.fn(() => ({ version: 1, hostContractVersion: "test", compatRegistryVersion: "test", migrationVersion: 1, policyHash: "test", generatedAtMs: 0, installRecords: {}, plugins: [], diagnostics: [], })), ); vi.mock("./plugin-registry.js", () => ({ loadPluginRegistrySnapshot, })); const resolveManifestProviderAuthChoices = vi.hoisted(() => vi.fn(() => []), ); vi.mock("./provider-auth-choices.js", () => ({ resolveManifestProviderAuthChoices, })); const listOfficialExternalProviderCatalogEntries = vi.hoisted(() => vi.fn(() => []), ); vi.mock("./official-external-plugin-catalog.js", async () => { const actual = await vi.importActual( "./official-external-plugin-catalog.js", ); return { ...actual, listOfficialExternalProviderCatalogEntries, }; }); import { resolveProviderInstallCatalogEntries, resolveProviderInstallCatalogEntry, } from "./provider-install-catalog.js"; describe("provider install catalog", () => { beforeEach(() => { vi.clearAllMocks(); loadOpenClawProviderIndex.mockReturnValue({ version: 1, providers: {} }); loadPluginRegistrySnapshot.mockReturnValue({ version: 1, hostContractVersion: "test", compatRegistryVersion: "test", migrationVersion: 1, policyHash: "test", generatedAtMs: 0, installRecords: {}, plugins: [], diagnostics: [], }); resolveManifestProviderAuthChoices.mockReturnValue([]); listOfficialExternalProviderCatalogEntries.mockReturnValue([]); }); it("merges manifest auth-choice metadata with registry install metadata", () => { loadPluginRegistrySnapshot.mockReturnValue({ version: 1, hostContractVersion: "test", compatRegistryVersion: "test", migrationVersion: 1, policyHash: "test", generatedAtMs: 0, installRecords: {}, plugins: [ { pluginId: "openai", origin: "bundled", manifestPath: "/repo/extensions/openai/openclaw.plugin.json", manifestHash: "hash", rootDir: "/repo/extensions/openai", enabled: true, startup: { sidecar: false, memory: false, deferConfiguredChannelFullLoadUntilAfterListen: false, agentHarnesses: [], }, compat: [], packageName: "@openclaw/openai", packageInstall: { defaultChoice: "npm", npm: { spec: "@openclaw/openai@1.2.3", packageName: "@openclaw/openai", selector: "1.2.3", selectorKind: "exact-version", exactVersion: true, expectedIntegrity: "sha512-openai", pinState: "exact-with-integrity", }, local: { path: "extensions/openai", }, warnings: [], }, }, ], diagnostics: [], }); resolveManifestProviderAuthChoices.mockReturnValue([ { pluginId: "openai", providerId: "openai", methodId: "api-key", choiceId: "openai-api-key", choiceLabel: "OpenAI API key", groupId: "openai", groupLabel: "OpenAI", }, ]); expect(resolveProviderInstallCatalogEntries()).toEqual([ { pluginId: "openai", providerId: "openai", methodId: "api-key", choiceId: "openai-api-key", choiceLabel: "OpenAI API key", groupId: "openai", groupLabel: "OpenAI", label: "OpenAI", origin: "bundled", install: { npmSpec: "@openclaw/openai@1.2.3", localPath: "extensions/openai", defaultChoice: "npm", expectedIntegrity: "sha512-openai", }, installSource: { defaultChoice: "npm", npm: { spec: "@openclaw/openai@1.2.3", packageName: "@openclaw/openai", selector: "1.2.3", selectorKind: "exact-version", exactVersion: true, expectedIntegrity: "sha512-openai", pinState: "exact-with-integrity", }, local: { path: "extensions/openai", }, warnings: [], }, }, ]); }); it("prefers durable install records over package-authored install intent", () => { loadPluginRegistrySnapshot.mockReturnValue({ version: 1, hostContractVersion: "test", compatRegistryVersion: "test", migrationVersion: 1, policyHash: "test", generatedAtMs: 0, installRecords: { vllm: { source: "npm", spec: "@openclaw/vllm", resolvedSpec: "@openclaw/vllm@2.0.0", integrity: "sha512-vllm", }, }, plugins: [ { pluginId: "vllm", origin: "global", manifestPath: "/Users/test/.openclaw/plugins/vllm/openclaw.plugin.json", manifestHash: "hash", rootDir: "/Users/test/.openclaw/plugins/vllm", enabled: true, startup: { sidecar: false, memory: false, deferConfiguredChannelFullLoadUntilAfterListen: false, agentHarnesses: [], }, compat: [], packageName: "@openclaw/vllm", packageInstall: { npm: { spec: "@openclaw/vllm-fork@1.0.0", packageName: "@openclaw/vllm-fork", selector: "1.0.0", selectorKind: "exact-version", exactVersion: true, expectedIntegrity: "sha512-old", pinState: "exact-with-integrity", }, warnings: [], }, }, ], diagnostics: [], }); resolveManifestProviderAuthChoices.mockReturnValue([ { pluginId: "vllm", providerId: "vllm", methodId: "server", choiceId: "vllm", choiceLabel: "vLLM", groupLabel: "vLLM", }, ]); expect(resolveProviderInstallCatalogEntry("vllm")).toEqual({ pluginId: "vllm", providerId: "vllm", methodId: "server", choiceId: "vllm", choiceLabel: "vLLM", groupLabel: "vLLM", label: "vLLM", origin: "global", install: { npmSpec: "@openclaw/vllm@2.0.0", expectedIntegrity: "sha512-vllm", defaultChoice: "npm", }, installSource: { defaultChoice: "npm", npm: { spec: "@openclaw/vllm@2.0.0", packageName: "@openclaw/vllm", selector: "2.0.0", selectorKind: "exact-version", exactVersion: true, expectedIntegrity: "sha512-vllm", pinState: "exact-with-integrity", }, warnings: [], }, }); }); it("preserves durable ClawHub install records for provider setup reinstall hints", () => { loadPluginRegistrySnapshot.mockReturnValue({ version: 1, hostContractVersion: "test", compatRegistryVersion: "test", migrationVersion: 1, policyHash: "test", generatedAtMs: 0, installRecords: { vllm: { source: "clawhub", spec: "clawhub:openclaw/vllm@2026.5.2", integrity: "sha256-clawpack", clawhubPackage: "openclaw/vllm", }, }, plugins: [ { pluginId: "vllm", origin: "global", manifestPath: "/Users/test/.openclaw/plugins/vllm/openclaw.plugin.json", manifestHash: "hash", rootDir: "/Users/test/.openclaw/plugins/vllm", enabled: true, startup: { sidecar: false, memory: false, deferConfiguredChannelFullLoadUntilAfterListen: false, agentHarnesses: [], }, compat: [], packageName: "@openclaw/vllm", packageInstall: { npm: { spec: "@openclaw/vllm-fork@1.0.0", packageName: "@openclaw/vllm-fork", selector: "1.0.0", selectorKind: "exact-version", exactVersion: true, expectedIntegrity: "sha512-old", pinState: "exact-with-integrity", }, warnings: [], }, }, ], diagnostics: [], }); resolveManifestProviderAuthChoices.mockReturnValue([ { pluginId: "vllm", providerId: "vllm", methodId: "server", choiceId: "vllm", choiceLabel: "vLLM", groupLabel: "vLLM", }, ]); expect(resolveProviderInstallCatalogEntry("vllm")).toMatchObject({ install: { clawhubSpec: "clawhub:openclaw/vllm@2026.5.2", defaultChoice: "clawhub", }, installSource: { defaultChoice: "clawhub", clawhub: { spec: "clawhub:openclaw/vllm@2026.5.2", packageName: "openclaw/vllm", version: "2026.5.2", exactVersion: true, }, warnings: [], }, }); }); it("does not expose untrusted global package install intent without an install record", () => { loadPluginRegistrySnapshot.mockReturnValue({ version: 1, hostContractVersion: "test", compatRegistryVersion: "test", migrationVersion: 1, policyHash: "test", generatedAtMs: 0, installRecords: {}, plugins: [ { pluginId: "demo-provider", origin: "global", manifestPath: "/Users/test/.openclaw/plugins/demo-provider/openclaw.plugin.json", manifestHash: "hash", rootDir: "/Users/test/.openclaw/plugins/demo-provider", enabled: true, startup: { sidecar: false, memory: false, deferConfiguredChannelFullLoadUntilAfterListen: false, agentHarnesses: [], }, compat: [], packageName: "@vendor/demo-provider", packageInstall: { npm: { spec: "@vendor/demo-provider@1.2.3", packageName: "@vendor/demo-provider", selector: "1.2.3", selectorKind: "exact-version", exactVersion: true, expectedIntegrity: "sha512-demo", pinState: "exact-with-integrity", }, warnings: [], }, }, ], diagnostics: [], }); resolveManifestProviderAuthChoices.mockReturnValue([ { pluginId: "demo-provider", providerId: "demo-provider", methodId: "api-key", choiceId: "demo-provider-api-key", choiceLabel: "Demo Provider API key", }, ]); expect(resolveProviderInstallCatalogEntries()).toStrictEqual([]); }); it("skips untrusted workspace package install metadata when the plugin is disabled", () => { loadPluginRegistrySnapshot.mockReturnValue({ version: 1, hostContractVersion: "test", compatRegistryVersion: "test", migrationVersion: 1, policyHash: "test", generatedAtMs: 0, installRecords: {}, plugins: [ { pluginId: "demo-provider", origin: "workspace", manifestPath: "/repo/extensions/demo-provider/openclaw.plugin.json", manifestHash: "hash", rootDir: "/repo/extensions/demo-provider", enabled: false, startup: { sidecar: false, memory: false, deferConfiguredChannelFullLoadUntilAfterListen: false, agentHarnesses: [], }, compat: [], packageInstall: { local: { path: "extensions/demo-provider", }, warnings: [], }, }, ], diagnostics: [], }); resolveManifestProviderAuthChoices.mockReturnValue([ { pluginId: "demo-provider", providerId: "demo-provider", methodId: "api-key", choiceId: "demo-provider-api-key", choiceLabel: "Demo Provider API key", }, ]); expect( resolveProviderInstallCatalogEntries({ config: { plugins: { enabled: false, }, }, includeUntrustedWorkspacePlugins: false, }), ).toStrictEqual([]); }); it("surfaces provider-index install metadata when the provider plugin is not installed", () => { loadOpenClawProviderIndex.mockReturnValue({ version: 1, providers: { moonshot: { id: "moonshot", name: "Moonshot AI", plugin: { id: "moonshot", package: "@openclaw/plugin-moonshot", install: { npmSpec: "@openclaw/plugin-moonshot@1.2.3", defaultChoice: "npm", expectedIntegrity: "sha512-moonshot", }, }, authChoices: [ { method: "api-key", choiceId: "moonshot-api-key", choiceLabel: "Moonshot API key", groupId: "moonshot", groupLabel: "Moonshot AI", onboardingScopes: ["text-inference"], }, ], }, }, }); expect(resolveProviderInstallCatalogEntry("moonshot-api-key")).toEqual({ pluginId: "moonshot", providerId: "moonshot", methodId: "api-key", choiceId: "moonshot-api-key", choiceLabel: "Moonshot API key", groupId: "moonshot", groupLabel: "Moonshot AI", onboardingScopes: ["text-inference"], label: "Moonshot AI", origin: "bundled", install: { npmSpec: "@openclaw/plugin-moonshot@1.2.3", defaultChoice: "npm", expectedIntegrity: "sha512-moonshot", }, installSource: { defaultChoice: "npm", npm: { spec: "@openclaw/plugin-moonshot@1.2.3", packageName: "@openclaw/plugin-moonshot", selector: "1.2.3", selectorKind: "exact-version", exactVersion: true, expectedIntegrity: "sha512-moonshot", pinState: "exact-with-integrity", }, warnings: [], }, }); }); it("surfaces official external provider install metadata when the provider plugin is not installed", () => { listOfficialExternalProviderCatalogEntries.mockReturnValue([ { name: "@openclaw/codex", source: "official", kind: "provider", openclaw: { plugin: { id: "codex", label: "Codex" }, providers: [ { id: "codex", name: "Codex", authChoices: [ { method: "app-server", choiceId: "codex", choiceLabel: "Codex app-server", choiceHint: "Use the Codex app-server runtime.", groupId: "codex", groupLabel: "Codex", onboardingScopes: ["text-inference"], }, ], }, ], install: { npmSpec: "@openclaw/codex", defaultChoice: "npm", }, }, }, ]); expect(resolveProviderInstallCatalogEntry("codex")).toMatchObject({ pluginId: "codex", providerId: "codex", methodId: "app-server", choiceId: "codex", choiceLabel: "Codex app-server", choiceHint: "Use the Codex app-server runtime.", groupId: "codex", groupLabel: "Codex", onboardingScopes: ["text-inference"], label: "Codex", install: { npmSpec: "@openclaw/codex", defaultChoice: "npm", }, }); }); it("surfaces provider-index ClawHub install metadata as the preferred source", () => { loadOpenClawProviderIndex.mockReturnValue({ version: 1, providers: { moonshot: { id: "moonshot", name: "Moonshot AI", plugin: { id: "moonshot", package: "@openclaw/plugin-moonshot", install: { clawhubSpec: "clawhub:openclaw/moonshot@2026.5.2", npmSpec: "@openclaw/plugin-moonshot@2026.5.2", defaultChoice: "clawhub", expectedIntegrity: "sha512-moonshot", }, }, authChoices: [ { method: "api-key", choiceId: "moonshot-api-key", choiceLabel: "Moonshot API key", groupId: "moonshot", groupLabel: "Moonshot AI", }, ], }, }, }); expect(resolveProviderInstallCatalogEntry("moonshot-api-key")).toEqual({ pluginId: "moonshot", providerId: "moonshot", methodId: "api-key", choiceId: "moonshot-api-key", choiceLabel: "Moonshot API key", groupId: "moonshot", groupLabel: "Moonshot AI", label: "Moonshot AI", origin: "bundled", install: { clawhubSpec: "clawhub:openclaw/moonshot@2026.5.2", npmSpec: "@openclaw/plugin-moonshot@2026.5.2", defaultChoice: "clawhub", expectedIntegrity: "sha512-moonshot", }, installSource: { defaultChoice: "clawhub", clawhub: { spec: "clawhub:openclaw/moonshot@2026.5.2", packageName: "openclaw/moonshot", version: "2026.5.2", exactVersion: true, }, npm: { spec: "@openclaw/plugin-moonshot@2026.5.2", packageName: "@openclaw/plugin-moonshot", selector: "2026.5.2", selectorKind: "exact-version", exactVersion: true, expectedIntegrity: "sha512-moonshot", pinState: "exact-with-integrity", }, warnings: [], }, }); }); it("keeps provider-index entries hidden when the plugin is already installed", () => { loadPluginRegistrySnapshot.mockReturnValue({ version: 1, hostContractVersion: "test", compatRegistryVersion: "test", migrationVersion: 1, policyHash: "test", generatedAtMs: 0, installRecords: {}, plugins: [ { pluginId: "moonshot", origin: "bundled", manifestPath: "/repo/extensions/moonshot/openclaw.plugin.json", manifestHash: "hash", rootDir: "/repo/extensions/moonshot", enabled: true, startup: { sidecar: false, memory: false, deferConfiguredChannelFullLoadUntilAfterListen: false, agentHarnesses: [], }, compat: [], }, ], diagnostics: [], }); loadOpenClawProviderIndex.mockReturnValue({ version: 1, providers: { moonshot: { id: "moonshot", name: "Moonshot AI", plugin: { id: "moonshot", package: "@openclaw/plugin-moonshot", install: { npmSpec: "@openclaw/plugin-moonshot@1.2.3", expectedIntegrity: "sha512-moonshot", }, }, authChoices: [ { method: "api-key", choiceId: "moonshot-api-key", choiceLabel: "Moonshot API key", }, ], }, }, }); expect(resolveProviderInstallCatalogEntry("moonshot-api-key")).toBeUndefined(); }); it("keeps missing provider-index entries visible when only some provider plugins are installed", () => { loadPluginRegistrySnapshot.mockReturnValue({ version: 1, hostContractVersion: "test", compatRegistryVersion: "test", migrationVersion: 1, policyHash: "test", generatedAtMs: 0, installRecords: {}, plugins: [ { pluginId: "moonshot", origin: "bundled", manifestPath: "/repo/extensions/moonshot/openclaw.plugin.json", manifestHash: "hash", rootDir: "/repo/extensions/moonshot", enabled: true, startup: { sidecar: false, memory: false, deferConfiguredChannelFullLoadUntilAfterListen: false, agentHarnesses: [], }, compat: [], }, ], diagnostics: [], }); loadOpenClawProviderIndex.mockReturnValue({ version: 1, providers: { groq: { id: "groq", name: "Groq", plugin: { id: "groq", package: "@openclaw/plugin-groq", install: { npmSpec: "@openclaw/plugin-groq@1.0.0", defaultChoice: "npm", }, }, authChoices: [ { method: "api-key", choiceId: "groq-api-key", choiceLabel: "Groq API key", }, ], }, moonshot: { id: "moonshot", name: "Moonshot AI", plugin: { id: "moonshot", package: "@openclaw/plugin-moonshot", install: { clawhubSpec: "clawhub:openclaw/moonshot@2026.5.2", npmSpec: "@openclaw/plugin-moonshot@2026.5.2", defaultChoice: "clawhub", }, }, authChoices: [ { method: "api-key", choiceId: "moonshot-api-key", choiceLabel: "Moonshot API key", }, ], }, vllm: { id: "vllm", name: "vLLM", plugin: { id: "vllm", package: "@openclaw/plugin-vllm", install: { clawhubSpec: "clawhub:openclaw/vllm@2026.5.2", npmSpec: "@openclaw/plugin-vllm@2026.5.2", defaultChoice: "clawhub", }, }, authChoices: [ { method: "server", choiceId: "vllm-server", choiceLabel: "vLLM server", }, ], }, }, }); const entries = resolveProviderInstallCatalogEntries(); expect(entries.map((entry) => entry.choiceId)).toEqual(["groq-api-key", "vllm-server"]); expect(resolveProviderInstallCatalogEntry("moonshot-api-key")).toBeUndefined(); expect(resolveProviderInstallCatalogEntry("vllm-server")).toMatchObject({ pluginId: "vllm", install: { clawhubSpec: "clawhub:openclaw/vllm@2026.5.2", npmSpec: "@openclaw/plugin-vllm@2026.5.2", defaultChoice: "clawhub", }, installSource: { defaultChoice: "clawhub", clawhub: { spec: "clawhub:openclaw/vllm@2026.5.2", }, npm: { spec: "@openclaw/plugin-vllm@2026.5.2", }, }, }); }); });