Files
openclaw/src/plugins/provider-install-catalog.test.ts

808 lines
23 KiB
TypeScript

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<LoadOpenClawProviderIndex>(() => ({ version: 1, providers: {} })),
);
vi.mock("../model-catalog/index.js", async () => {
const actual = await vi.importActual<typeof import("../model-catalog/index.js")>(
"../model-catalog/index.js",
);
return {
...actual,
loadOpenClawProviderIndex,
};
});
const loadPluginRegistrySnapshot = vi.hoisted(() =>
vi.fn<LoadPluginRegistrySnapshot>(() => ({
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<ResolveManifestProviderAuthChoices>(() => []),
);
vi.mock("./provider-auth-choices.js", () => ({
resolveManifestProviderAuthChoices,
}));
const listOfficialExternalProviderCatalogEntries = vi.hoisted(() =>
vi.fn<ListOfficialExternalProviderCatalogEntries>(() => []),
);
vi.mock("./official-external-plugin-catalog.js", async () => {
const actual = await vi.importActual<typeof import("./official-external-plugin-catalog.js")>(
"./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",
},
},
});
});
});