mirror of https://github.com/openclaw/openclaw.git
fix(regression): restore bundled capability provider compat
This commit is contained in:
parent
d0cd645b4a
commit
3dbd81e610
|
|
@ -0,0 +1,129 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { createEmptyPluginRegistry } from "./registry.js";
|
||||
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "./runtime.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
loadOpenClawPlugins: vi.fn(() => createEmptyPluginRegistry()),
|
||||
loadPluginManifestRegistry: vi.fn(() => ({ plugins: [], diagnostics: [] })),
|
||||
withBundledPluginAllowlistCompat: vi.fn(({ config }) => config),
|
||||
withBundledPluginEnablementCompat: vi.fn(({ config }) => config),
|
||||
withBundledPluginVitestCompat: vi.fn(({ config }) => config),
|
||||
}));
|
||||
|
||||
vi.mock("./loader.js", () => ({
|
||||
loadOpenClawPlugins: mocks.loadOpenClawPlugins,
|
||||
}));
|
||||
|
||||
vi.mock("./manifest-registry.js", () => ({
|
||||
loadPluginManifestRegistry: mocks.loadPluginManifestRegistry,
|
||||
}));
|
||||
|
||||
vi.mock("./bundled-compat.js", () => ({
|
||||
withBundledPluginAllowlistCompat: mocks.withBundledPluginAllowlistCompat,
|
||||
withBundledPluginEnablementCompat: mocks.withBundledPluginEnablementCompat,
|
||||
withBundledPluginVitestCompat: mocks.withBundledPluginVitestCompat,
|
||||
}));
|
||||
|
||||
let resolvePluginCapabilityProviders: typeof import("./capability-provider-runtime.js").resolvePluginCapabilityProviders;
|
||||
|
||||
describe("resolvePluginCapabilityProviders", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
resetPluginRuntimeStateForTest();
|
||||
mocks.loadOpenClawPlugins.mockReset();
|
||||
mocks.loadOpenClawPlugins.mockReturnValue(createEmptyPluginRegistry());
|
||||
mocks.loadPluginManifestRegistry.mockReset();
|
||||
mocks.loadPluginManifestRegistry.mockReturnValue({ plugins: [], diagnostics: [] });
|
||||
mocks.withBundledPluginAllowlistCompat.mockReset();
|
||||
mocks.withBundledPluginAllowlistCompat.mockImplementation(({ config }) => config);
|
||||
mocks.withBundledPluginEnablementCompat.mockReset();
|
||||
mocks.withBundledPluginEnablementCompat.mockImplementation(({ config }) => config);
|
||||
mocks.withBundledPluginVitestCompat.mockReset();
|
||||
mocks.withBundledPluginVitestCompat.mockImplementation(({ config }) => config);
|
||||
({ resolvePluginCapabilityProviders } = await import("./capability-provider-runtime.js"));
|
||||
});
|
||||
|
||||
it("uses the active registry when capability providers are already loaded", () => {
|
||||
const active = createEmptyPluginRegistry();
|
||||
active.speechProviders.push({
|
||||
pluginId: "openai",
|
||||
pluginName: "OpenAI",
|
||||
source: "test",
|
||||
provider: {
|
||||
id: "openai",
|
||||
label: "OpenAI",
|
||||
isConfigured: () => true,
|
||||
synthesize: async () => ({
|
||||
audioBuffer: Buffer.from("x"),
|
||||
outputFormat: "mp3",
|
||||
voiceCompatible: false,
|
||||
fileExtension: ".mp3",
|
||||
}),
|
||||
},
|
||||
});
|
||||
setActivePluginRegistry(active);
|
||||
|
||||
const providers = resolvePluginCapabilityProviders({ key: "speechProviders" });
|
||||
|
||||
expect(providers.map((provider) => provider.id)).toEqual(["openai"]);
|
||||
expect(mocks.loadPluginManifestRegistry).not.toHaveBeenCalled();
|
||||
expect(mocks.loadOpenClawPlugins).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["speechProviders", "speechProviders"],
|
||||
["mediaUnderstandingProviders", "mediaUnderstandingProviders"],
|
||||
["imageGenerationProviders", "imageGenerationProviders"],
|
||||
] as const)("applies bundled compat before fallback loading for %s", (key, contractKey) => {
|
||||
const cfg = { plugins: { allow: ["custom-plugin"] } } as OpenClawConfig;
|
||||
const allowlistCompat = { plugins: { allow: ["custom-plugin", "openai"] } };
|
||||
const enablementCompat = {
|
||||
plugins: {
|
||||
allow: ["custom-plugin", "openai"],
|
||||
entries: { openai: { enabled: true } },
|
||||
},
|
||||
};
|
||||
mocks.loadPluginManifestRegistry.mockReturnValue({
|
||||
plugins: [
|
||||
{
|
||||
id: "openai",
|
||||
origin: "bundled",
|
||||
contracts: { [contractKey]: ["openai"] },
|
||||
},
|
||||
{
|
||||
id: "custom-plugin",
|
||||
origin: "workspace",
|
||||
contracts: {},
|
||||
},
|
||||
] as Array<Record<string, unknown>>,
|
||||
diagnostics: [],
|
||||
});
|
||||
mocks.withBundledPluginAllowlistCompat.mockReturnValue(allowlistCompat);
|
||||
mocks.withBundledPluginEnablementCompat.mockReturnValue(enablementCompat);
|
||||
mocks.withBundledPluginVitestCompat.mockReturnValue(enablementCompat);
|
||||
|
||||
resolvePluginCapabilityProviders({ key, cfg });
|
||||
|
||||
expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledWith({
|
||||
config: cfg,
|
||||
env: process.env,
|
||||
});
|
||||
expect(mocks.withBundledPluginAllowlistCompat).toHaveBeenCalledWith({
|
||||
config: cfg,
|
||||
pluginIds: ["openai"],
|
||||
});
|
||||
expect(mocks.withBundledPluginEnablementCompat).toHaveBeenCalledWith({
|
||||
config: allowlistCompat,
|
||||
pluginIds: ["openai"],
|
||||
});
|
||||
expect(mocks.withBundledPluginVitestCompat).toHaveBeenCalledWith({
|
||||
config: enablementCompat,
|
||||
pluginIds: ["openai"],
|
||||
env: process.env,
|
||||
});
|
||||
expect(mocks.loadOpenClawPlugins).toHaveBeenCalledWith({
|
||||
config: enablementCompat,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import {
|
||||
withBundledPluginAllowlistCompat,
|
||||
withBundledPluginEnablementCompat,
|
||||
withBundledPluginVitestCompat,
|
||||
} from "./bundled-compat.js";
|
||||
import { loadOpenClawPlugins } from "./loader.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import type { PluginRegistry } from "./registry.js";
|
||||
import { getActivePluginRegistry } from "./runtime.js";
|
||||
|
||||
|
|
@ -8,9 +14,56 @@ type CapabilityProviderRegistryKey =
|
|||
| "mediaUnderstandingProviders"
|
||||
| "imageGenerationProviders";
|
||||
|
||||
type CapabilityContractKey =
|
||||
| "speechProviders"
|
||||
| "mediaUnderstandingProviders"
|
||||
| "imageGenerationProviders";
|
||||
|
||||
type CapabilityProviderForKey<K extends CapabilityProviderRegistryKey> =
|
||||
PluginRegistry[K][number] extends { provider: infer T } ? T : never;
|
||||
|
||||
const CAPABILITY_CONTRACT_KEY: Record<CapabilityProviderRegistryKey, CapabilityContractKey> = {
|
||||
speechProviders: "speechProviders",
|
||||
mediaUnderstandingProviders: "mediaUnderstandingProviders",
|
||||
imageGenerationProviders: "imageGenerationProviders",
|
||||
};
|
||||
|
||||
function resolveBundledCapabilityCompatPluginIds(params: {
|
||||
key: CapabilityProviderRegistryKey;
|
||||
cfg?: OpenClawConfig;
|
||||
}): string[] {
|
||||
const contractKey = CAPABILITY_CONTRACT_KEY[params.key];
|
||||
return loadPluginManifestRegistry({
|
||||
config: params.cfg,
|
||||
env: process.env,
|
||||
})
|
||||
.plugins.filter(
|
||||
(plugin) => plugin.origin === "bundled" && (plugin.contracts?.[contractKey]?.length ?? 0) > 0,
|
||||
)
|
||||
.map((plugin) => plugin.id)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function resolveCapabilityProviderConfig(params: {
|
||||
key: CapabilityProviderRegistryKey;
|
||||
cfg?: OpenClawConfig;
|
||||
}) {
|
||||
const pluginIds = resolveBundledCapabilityCompatPluginIds(params);
|
||||
const allowlistCompat = withBundledPluginAllowlistCompat({
|
||||
config: params.cfg,
|
||||
pluginIds,
|
||||
});
|
||||
const enablementCompat = withBundledPluginEnablementCompat({
|
||||
config: allowlistCompat,
|
||||
pluginIds,
|
||||
});
|
||||
return withBundledPluginVitestCompat({
|
||||
config: enablementCompat,
|
||||
pluginIds,
|
||||
env: process.env,
|
||||
});
|
||||
}
|
||||
|
||||
export function resolvePluginCapabilityProviders<K extends CapabilityProviderRegistryKey>(params: {
|
||||
key: K;
|
||||
cfg?: OpenClawConfig;
|
||||
|
|
@ -20,7 +73,11 @@ export function resolvePluginCapabilityProviders<K extends CapabilityProviderReg
|
|||
const shouldUseActive =
|
||||
params.useActiveRegistryWhen?.(active) ?? (active?.[params.key].length ?? 0) > 0;
|
||||
const registry =
|
||||
shouldUseActive || !params.cfg ? active : loadOpenClawPlugins({ config: params.cfg });
|
||||
shouldUseActive || !params.cfg
|
||||
? active
|
||||
: loadOpenClawPlugins({
|
||||
config: resolveCapabilityProviderConfig({ key: params.key, cfg: params.cfg }),
|
||||
});
|
||||
return (registry?.[params.key] ?? []).map(
|
||||
(entry) => entry.provider,
|
||||
) as CapabilityProviderForKey<K>[];
|
||||
|
|
|
|||
|
|
@ -83,7 +83,17 @@ describe("speech provider registry", () => {
|
|||
|
||||
expect(listSpeechProviders(cfg).map((provider) => provider.id)).toEqual(["microsoft"]);
|
||||
expect(getSpeechProvider("edge", cfg)?.id).toBe("microsoft");
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith({ config: cfg });
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith({
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
elevenlabs: { enabled: true },
|
||||
microsoft: { enabled: true },
|
||||
openai: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("returns no providers when neither plugins nor active registry provide speech support", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue