mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
ffb5b99114
* plugins: include resolved workspaceDir in provider hook cache keys resolveProviderPluginsForHooks, resolveProviderPluginsForCatalogHooks, and resolveProviderRuntimePlugin used the raw params.workspaceDir for cache keys and plugin-id discovery while resolvePluginProviders already fell back to the active registry workspace. Resolve workspaceDir once at the top of each function so cache keys, candidate filtering, and loading all use the same workspace root. * fix(plugins): inherit runtime workspace for snapshot loads * test(gateway): stub runtime registry seam * fix(plugins): restore workspace fallback after rebase --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
723 lines
21 KiB
TypeScript
723 lines
21 KiB
TypeScript
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
import type { PluginAutoEnableResult } from "../config/plugin-auto-enable.js";
|
|
import type { PluginManifestRecord } from "./manifest-registry.js";
|
|
import { createEmptyPluginRegistry } from "./registry-empty.js";
|
|
import type { ProviderPlugin } from "./types.js";
|
|
|
|
type ResolveRuntimePluginRegistry = typeof import("./loader.js").resolveRuntimePluginRegistry;
|
|
type LoadOpenClawPlugins = typeof import("./loader.js").loadOpenClawPlugins;
|
|
type LoadPluginManifestRegistry =
|
|
typeof import("./manifest-registry.js").loadPluginManifestRegistry;
|
|
type ApplyPluginAutoEnable = typeof import("../config/plugin-auto-enable.js").applyPluginAutoEnable;
|
|
type SetActivePluginRegistry = typeof import("./runtime.js").setActivePluginRegistry;
|
|
|
|
const resolveRuntimePluginRegistryMock = vi.fn<ResolveRuntimePluginRegistry>();
|
|
const loadOpenClawPluginsMock = vi.fn<LoadOpenClawPlugins>();
|
|
const loadPluginManifestRegistryMock = vi.fn<LoadPluginManifestRegistry>();
|
|
const applyPluginAutoEnableMock = vi.fn<ApplyPluginAutoEnable>();
|
|
|
|
let resolveOwningPluginIdsForProvider: typeof import("./providers.js").resolveOwningPluginIdsForProvider;
|
|
let resolveOwningPluginIdsForModelRef: typeof import("./providers.js").resolveOwningPluginIdsForModelRef;
|
|
let resolveEnabledProviderPluginIds: typeof import("./providers.js").resolveEnabledProviderPluginIds;
|
|
let resolvePluginProviders: typeof import("./providers.runtime.js").resolvePluginProviders;
|
|
let setActivePluginRegistry: SetActivePluginRegistry;
|
|
|
|
function createManifestProviderPlugin(params: {
|
|
id: string;
|
|
providerIds: string[];
|
|
origin?: "bundled" | "workspace";
|
|
enabledByDefault?: boolean;
|
|
modelSupport?: { modelPrefixes?: string[]; modelPatterns?: string[] };
|
|
}): PluginManifestRecord {
|
|
return {
|
|
id: params.id,
|
|
enabledByDefault: params.enabledByDefault,
|
|
channels: [],
|
|
cliBackends: [],
|
|
providers: params.providerIds,
|
|
modelSupport: params.modelSupport,
|
|
skills: [],
|
|
hooks: [],
|
|
origin: params.origin ?? "bundled",
|
|
rootDir: `/tmp/${params.id}`,
|
|
source: params.origin ?? "bundled",
|
|
manifestPath: `/tmp/${params.id}/openclaw.plugin.json`,
|
|
};
|
|
}
|
|
|
|
function setManifestPlugins(plugins: PluginManifestRecord[]) {
|
|
loadPluginManifestRegistryMock.mockReturnValue({
|
|
plugins,
|
|
diagnostics: [],
|
|
});
|
|
}
|
|
|
|
function setOwningProviderManifestPlugins() {
|
|
setManifestPlugins([
|
|
createManifestProviderPlugin({
|
|
id: "minimax",
|
|
providerIds: ["minimax", "minimax-portal"],
|
|
}),
|
|
createManifestProviderPlugin({
|
|
id: "openai",
|
|
providerIds: ["openai", "openai-codex"],
|
|
modelSupport: {
|
|
modelPrefixes: ["gpt-", "o1", "o3", "o4"],
|
|
},
|
|
}),
|
|
createManifestProviderPlugin({
|
|
id: "anthropic",
|
|
providerIds: ["anthropic"],
|
|
modelSupport: {
|
|
modelPrefixes: ["claude-"],
|
|
},
|
|
}),
|
|
]);
|
|
}
|
|
|
|
function setOwningProviderManifestPluginsWithWorkspace() {
|
|
setManifestPlugins([
|
|
createManifestProviderPlugin({
|
|
id: "minimax",
|
|
providerIds: ["minimax", "minimax-portal"],
|
|
}),
|
|
createManifestProviderPlugin({
|
|
id: "openai",
|
|
providerIds: ["openai", "openai-codex"],
|
|
modelSupport: {
|
|
modelPrefixes: ["gpt-", "o1", "o3", "o4"],
|
|
},
|
|
}),
|
|
createManifestProviderPlugin({
|
|
id: "anthropic",
|
|
providerIds: ["anthropic"],
|
|
modelSupport: {
|
|
modelPrefixes: ["claude-"],
|
|
},
|
|
}),
|
|
createManifestProviderPlugin({
|
|
id: "workspace-provider",
|
|
providerIds: ["workspace-provider"],
|
|
origin: "workspace",
|
|
modelSupport: {
|
|
modelPrefixes: ["workspace-model-"],
|
|
},
|
|
}),
|
|
]);
|
|
}
|
|
|
|
function getLastRuntimeRegistryCall(): Record<string, unknown> {
|
|
const call = resolveRuntimePluginRegistryMock.mock.calls.at(-1)?.[0];
|
|
expect(call).toBeDefined();
|
|
return (call ?? {}) as Record<string, unknown>;
|
|
}
|
|
|
|
function cloneOptions<T>(value: T): T {
|
|
return structuredClone(value);
|
|
}
|
|
|
|
function expectResolvedProviders(providers: unknown, expected: unknown[]) {
|
|
expect(providers).toEqual(expected);
|
|
}
|
|
|
|
function expectLastRuntimeRegistryLoad(params?: {
|
|
env?: NodeJS.ProcessEnv;
|
|
onlyPluginIds?: readonly string[];
|
|
}) {
|
|
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
cache: false,
|
|
activate: false,
|
|
...(params?.env ? { env: params.env } : {}),
|
|
...(params?.onlyPluginIds ? { onlyPluginIds: params.onlyPluginIds } : {}),
|
|
}),
|
|
);
|
|
}
|
|
|
|
function expectLastSetupRegistryLoad(params?: {
|
|
env?: NodeJS.ProcessEnv;
|
|
onlyPluginIds?: readonly string[];
|
|
}) {
|
|
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
cache: false,
|
|
activate: false,
|
|
...(params?.env ? { env: params.env } : {}),
|
|
...(params?.onlyPluginIds ? { onlyPluginIds: params.onlyPluginIds } : {}),
|
|
}),
|
|
);
|
|
}
|
|
|
|
function getLastResolvedPluginConfig() {
|
|
return getLastRuntimeRegistryCall().config as
|
|
| {
|
|
plugins?: {
|
|
allow?: string[];
|
|
entries?: Record<string, { enabled?: boolean }>;
|
|
};
|
|
}
|
|
| undefined;
|
|
}
|
|
|
|
function getLastSetupLoadedPluginConfig() {
|
|
const call = loadOpenClawPluginsMock.mock.calls.at(-1)?.[0];
|
|
expect(call).toBeDefined();
|
|
return (call?.config ?? undefined) as
|
|
| {
|
|
plugins?: {
|
|
allow?: string[];
|
|
entries?: Record<string, { enabled?: boolean }>;
|
|
};
|
|
}
|
|
| undefined;
|
|
}
|
|
|
|
function createBundledProviderCompatOptions(params?: { onlyPluginIds?: readonly string[] }) {
|
|
return {
|
|
config: {
|
|
plugins: {
|
|
allow: ["openrouter"],
|
|
},
|
|
},
|
|
bundledProviderAllowlistCompat: true,
|
|
...(params?.onlyPluginIds ? { onlyPluginIds: params.onlyPluginIds } : {}),
|
|
};
|
|
}
|
|
|
|
function createAutoEnabledProviderConfig() {
|
|
const rawConfig: OpenClawConfig = {
|
|
plugins: {},
|
|
};
|
|
const autoEnabledConfig: OpenClawConfig = {
|
|
...rawConfig,
|
|
plugins: {
|
|
entries: {
|
|
google: { enabled: true },
|
|
},
|
|
},
|
|
};
|
|
return { rawConfig, autoEnabledConfig };
|
|
}
|
|
|
|
function expectAutoEnabledProviderLoad(params: { rawConfig: unknown; autoEnabledConfig: unknown }) {
|
|
expect(applyPluginAutoEnableMock).toHaveBeenCalledWith({
|
|
config: params.rawConfig,
|
|
env: process.env,
|
|
});
|
|
expectProviderRuntimeRegistryLoad({ config: params.autoEnabledConfig });
|
|
}
|
|
|
|
function expectResolvedAllowlistState(params?: {
|
|
expectedAllow?: readonly string[];
|
|
unexpectedAllow?: readonly string[];
|
|
expectedEntries?: Record<string, { enabled?: boolean }>;
|
|
expectedOnlyPluginIds?: readonly string[];
|
|
}) {
|
|
expectLastRuntimeRegistryLoad(
|
|
params?.expectedOnlyPluginIds ? { onlyPluginIds: params.expectedOnlyPluginIds } : undefined,
|
|
);
|
|
|
|
const config = getLastResolvedPluginConfig();
|
|
const allow = config?.plugins?.allow ?? [];
|
|
|
|
if (params?.expectedAllow) {
|
|
expect(allow).toEqual(expect.arrayContaining([...params.expectedAllow]));
|
|
}
|
|
if (params?.expectedEntries) {
|
|
expect(config?.plugins?.entries).toEqual(expect.objectContaining(params.expectedEntries));
|
|
}
|
|
params?.unexpectedAllow?.forEach((disallowedPluginId) => {
|
|
expect(allow).not.toContain(disallowedPluginId);
|
|
});
|
|
}
|
|
|
|
function expectOwningPluginIds(provider: string, expectedPluginIds?: readonly string[]) {
|
|
expect(resolveOwningPluginIdsForProvider({ provider })).toEqual(expectedPluginIds);
|
|
}
|
|
|
|
function expectModelOwningPluginIds(model: string, expectedPluginIds?: readonly string[]) {
|
|
expect(resolveOwningPluginIdsForModelRef({ model })).toEqual(expectedPluginIds);
|
|
}
|
|
|
|
function expectProviderRuntimeRegistryLoad(params?: { config?: unknown; env?: NodeJS.ProcessEnv }) {
|
|
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
...(params?.config ? { config: params.config } : {}),
|
|
...(params?.env ? { env: params.env } : {}),
|
|
}),
|
|
);
|
|
}
|
|
|
|
describe("resolvePluginProviders", () => {
|
|
beforeAll(async () => {
|
|
vi.resetModules();
|
|
vi.doMock("./loader.js", () => ({
|
|
loadOpenClawPlugins: (...args: Parameters<LoadOpenClawPlugins>) =>
|
|
loadOpenClawPluginsMock(...args),
|
|
resolveRuntimePluginRegistry: (...args: Parameters<ResolveRuntimePluginRegistry>) =>
|
|
resolveRuntimePluginRegistryMock(...args),
|
|
}));
|
|
vi.doMock("../config/plugin-auto-enable.js", () => ({
|
|
applyPluginAutoEnable: (...args: Parameters<ApplyPluginAutoEnable>) =>
|
|
applyPluginAutoEnableMock(...args),
|
|
}));
|
|
vi.doMock("./manifest-registry.js", () => ({
|
|
loadPluginManifestRegistry: (...args: Parameters<LoadPluginManifestRegistry>) =>
|
|
loadPluginManifestRegistryMock(...args),
|
|
}));
|
|
({
|
|
resolveOwningPluginIdsForProvider,
|
|
resolveOwningPluginIdsForModelRef,
|
|
resolveEnabledProviderPluginIds,
|
|
} = await import("./providers.js"));
|
|
({ resolvePluginProviders } = await import("./providers.runtime.js"));
|
|
({ setActivePluginRegistry } = await import("./runtime.js"));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
setActivePluginRegistry(createEmptyPluginRegistry());
|
|
resolveRuntimePluginRegistryMock.mockReset();
|
|
loadOpenClawPluginsMock.mockReset();
|
|
const provider: ProviderPlugin = {
|
|
id: "demo-provider",
|
|
label: "Demo Provider",
|
|
auth: [],
|
|
};
|
|
const registry = createEmptyPluginRegistry();
|
|
registry.providers.push({ pluginId: "google", provider, source: "bundled" });
|
|
resolveRuntimePluginRegistryMock.mockReturnValue(registry);
|
|
loadOpenClawPluginsMock.mockReturnValue(registry);
|
|
loadPluginManifestRegistryMock.mockReset();
|
|
applyPluginAutoEnableMock.mockReset();
|
|
applyPluginAutoEnableMock.mockImplementation(
|
|
(params): PluginAutoEnableResult => ({
|
|
config: params.config ?? ({} as OpenClawConfig),
|
|
changes: [],
|
|
autoEnabledReasons: {},
|
|
}),
|
|
);
|
|
setManifestPlugins([
|
|
createManifestProviderPlugin({
|
|
id: "google",
|
|
providerIds: ["google"],
|
|
enabledByDefault: true,
|
|
}),
|
|
createManifestProviderPlugin({ id: "browser", providerIds: [] }),
|
|
createManifestProviderPlugin({
|
|
id: "kilocode",
|
|
providerIds: ["kilocode"],
|
|
enabledByDefault: true,
|
|
}),
|
|
createManifestProviderPlugin({
|
|
id: "moonshot",
|
|
providerIds: ["moonshot"],
|
|
enabledByDefault: true,
|
|
}),
|
|
createManifestProviderPlugin({ id: "google-gemini-cli-auth", providerIds: [] }),
|
|
createManifestProviderPlugin({
|
|
id: "workspace-provider",
|
|
providerIds: ["workspace-provider"],
|
|
origin: "workspace",
|
|
modelSupport: {
|
|
modelPrefixes: ["workspace-model-"],
|
|
},
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("forwards an explicit env to plugin loading", () => {
|
|
const env = { OPENCLAW_HOME: "/srv/openclaw-home" } as NodeJS.ProcessEnv;
|
|
|
|
const providers = resolvePluginProviders({
|
|
workspaceDir: "/workspace/explicit",
|
|
env,
|
|
});
|
|
|
|
expectResolvedProviders(providers, [
|
|
{ id: "demo-provider", label: "Demo Provider", auth: [], pluginId: "google" },
|
|
]);
|
|
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspaceDir: "/workspace/explicit",
|
|
env,
|
|
cache: false,
|
|
activate: false,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("keeps bundled provider plugins enabled when they default on outside Vitest compat", () => {
|
|
expect(resolveEnabledProviderPluginIds({ config: {}, env: {} as NodeJS.ProcessEnv })).toEqual([
|
|
"google",
|
|
"kilocode",
|
|
"moonshot",
|
|
]);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: "can augment restrictive allowlists for bundled provider compatibility",
|
|
options: createBundledProviderCompatOptions(),
|
|
expectedAllow: ["openrouter", "google", "kilocode", "moonshot"],
|
|
expectedEntries: {
|
|
google: { enabled: true },
|
|
kilocode: { enabled: true },
|
|
moonshot: { enabled: true },
|
|
},
|
|
},
|
|
{
|
|
name: "does not reintroduce the retired google auth plugin id into compat allowlists",
|
|
options: createBundledProviderCompatOptions(),
|
|
expectedAllow: ["google"],
|
|
unexpectedAllow: ["google-gemini-cli-auth"],
|
|
},
|
|
{
|
|
name: "does not inject non-bundled provider plugin ids into compat allowlists",
|
|
options: createBundledProviderCompatOptions(),
|
|
unexpectedAllow: ["workspace-provider"],
|
|
},
|
|
{
|
|
name: "scopes bundled provider compat expansion to the requested plugin ids",
|
|
options: createBundledProviderCompatOptions({
|
|
onlyPluginIds: ["moonshot"],
|
|
}),
|
|
expectedAllow: ["openrouter", "moonshot"],
|
|
unexpectedAllow: ["google", "kilocode"],
|
|
expectedOnlyPluginIds: ["moonshot"],
|
|
},
|
|
] as const)(
|
|
"$name",
|
|
({ options, expectedAllow, expectedEntries, expectedOnlyPluginIds, unexpectedAllow }) => {
|
|
resolvePluginProviders(
|
|
cloneOptions(options) as unknown as Parameters<typeof resolvePluginProviders>[0],
|
|
);
|
|
|
|
expectResolvedAllowlistState({
|
|
expectedAllow,
|
|
expectedEntries,
|
|
expectedOnlyPluginIds,
|
|
unexpectedAllow,
|
|
});
|
|
},
|
|
);
|
|
|
|
it("can enable bundled provider plugins under Vitest when no explicit plugin config exists", () => {
|
|
resolvePluginProviders({
|
|
env: { VITEST: "1" } as NodeJS.ProcessEnv,
|
|
bundledProviderVitestCompat: true,
|
|
});
|
|
|
|
expectLastRuntimeRegistryLoad();
|
|
expect(getLastResolvedPluginConfig()).toEqual(
|
|
expect.objectContaining({
|
|
plugins: expect.objectContaining({
|
|
enabled: true,
|
|
allow: expect.arrayContaining(["google", "moonshot"]),
|
|
entries: expect.objectContaining({
|
|
google: { enabled: true },
|
|
moonshot: { enabled: true },
|
|
}),
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("uses process env for Vitest compat when no explicit env is passed", () => {
|
|
const previousVitest = process.env.VITEST;
|
|
process.env.VITEST = "1";
|
|
try {
|
|
resolvePluginProviders({
|
|
bundledProviderVitestCompat: true,
|
|
onlyPluginIds: ["google"],
|
|
});
|
|
|
|
expectLastRuntimeRegistryLoad({
|
|
onlyPluginIds: ["google"],
|
|
});
|
|
expect(getLastResolvedPluginConfig()).toEqual(
|
|
expect.objectContaining({
|
|
plugins: expect.objectContaining({
|
|
enabled: true,
|
|
allow: ["google"],
|
|
entries: {
|
|
google: { enabled: true },
|
|
},
|
|
}),
|
|
}),
|
|
);
|
|
} finally {
|
|
if (previousVitest === undefined) {
|
|
delete process.env.VITEST;
|
|
} else {
|
|
process.env.VITEST = previousVitest;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("does not leak host Vitest env into an explicit non-Vitest env", () => {
|
|
const previousVitest = process.env.VITEST;
|
|
process.env.VITEST = "1";
|
|
try {
|
|
resolvePluginProviders({
|
|
env: {} as NodeJS.ProcessEnv,
|
|
bundledProviderVitestCompat: true,
|
|
});
|
|
|
|
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
config: undefined,
|
|
env: {},
|
|
}),
|
|
);
|
|
} finally {
|
|
if (previousVitest === undefined) {
|
|
delete process.env.VITEST;
|
|
} else {
|
|
process.env.VITEST = previousVitest;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("loads only provider plugins on the provider runtime path", () => {
|
|
resolvePluginProviders({
|
|
bundledProviderAllowlistCompat: true,
|
|
});
|
|
|
|
expectLastRuntimeRegistryLoad({
|
|
onlyPluginIds: ["google", "kilocode", "moonshot"],
|
|
});
|
|
});
|
|
|
|
it("loads all discovered provider plugins in setup mode", () => {
|
|
resolvePluginProviders({
|
|
config: {
|
|
plugins: {
|
|
allow: ["openrouter"],
|
|
entries: {
|
|
google: { enabled: false },
|
|
},
|
|
},
|
|
},
|
|
mode: "setup",
|
|
});
|
|
|
|
expectLastSetupRegistryLoad({
|
|
onlyPluginIds: ["google", "kilocode", "moonshot", "workspace-provider"],
|
|
});
|
|
expect(getLastSetupLoadedPluginConfig()).toEqual(
|
|
expect.objectContaining({
|
|
plugins: expect.objectContaining({
|
|
allow: expect.arrayContaining([
|
|
"openrouter",
|
|
"google",
|
|
"kilocode",
|
|
"moonshot",
|
|
"workspace-provider",
|
|
]),
|
|
entries: expect.objectContaining({
|
|
google: { enabled: true },
|
|
kilocode: { enabled: true },
|
|
moonshot: { enabled: true },
|
|
"workspace-provider": { enabled: true },
|
|
}),
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("loads provider plugins from the auto-enabled config snapshot", () => {
|
|
const { rawConfig, autoEnabledConfig } = createAutoEnabledProviderConfig();
|
|
applyPluginAutoEnableMock.mockReturnValue({
|
|
config: autoEnabledConfig,
|
|
changes: [],
|
|
autoEnabledReasons: {
|
|
google: ["google auth configured"],
|
|
},
|
|
});
|
|
|
|
resolvePluginProviders({ config: rawConfig });
|
|
|
|
expectAutoEnabledProviderLoad({
|
|
rawConfig,
|
|
autoEnabledConfig,
|
|
});
|
|
});
|
|
|
|
it("routes provider runtime resolution through the compatible active-registry seam", () => {
|
|
resolvePluginProviders({
|
|
config: {
|
|
plugins: {
|
|
allow: ["google"],
|
|
},
|
|
},
|
|
onlyPluginIds: ["google"],
|
|
workspaceDir: "/workspace/runtime",
|
|
});
|
|
|
|
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspaceDir: "/workspace/runtime",
|
|
cache: false,
|
|
activate: false,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("inherits workspaceDir from the active registry when provider resolution omits it", () => {
|
|
setActivePluginRegistry(
|
|
createEmptyPluginRegistry(),
|
|
undefined,
|
|
"default",
|
|
"/workspace/runtime",
|
|
);
|
|
|
|
resolvePluginProviders({
|
|
config: {
|
|
plugins: {
|
|
allow: ["google"],
|
|
},
|
|
},
|
|
onlyPluginIds: ["google"],
|
|
});
|
|
|
|
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspaceDir: "/workspace/runtime",
|
|
cache: false,
|
|
activate: false,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
provider: "minimax-portal",
|
|
expectedPluginIds: ["minimax"],
|
|
},
|
|
{
|
|
provider: "openai-codex",
|
|
expectedPluginIds: ["openai"],
|
|
},
|
|
{
|
|
provider: "gemini-cli",
|
|
expectedPluginIds: undefined,
|
|
},
|
|
] as const)(
|
|
"maps $provider to owning plugin ids via manifests",
|
|
({ provider, expectedPluginIds }) => {
|
|
setOwningProviderManifestPlugins();
|
|
|
|
expectOwningPluginIds(provider, expectedPluginIds);
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
{
|
|
model: "gpt-5.4",
|
|
expectedPluginIds: ["openai"],
|
|
},
|
|
{
|
|
model: "claude-sonnet-4-6",
|
|
expectedPluginIds: ["anthropic"],
|
|
},
|
|
{
|
|
model: "openai/gpt-5.4",
|
|
expectedPluginIds: ["openai"],
|
|
},
|
|
{
|
|
model: "workspace-model-fast",
|
|
expectedPluginIds: ["workspace-provider"],
|
|
},
|
|
{
|
|
model: "unknown-model",
|
|
expectedPluginIds: undefined,
|
|
},
|
|
] as const)(
|
|
"maps $model to owning plugin ids via modelSupport",
|
|
({ model, expectedPluginIds }) => {
|
|
setOwningProviderManifestPluginsWithWorkspace();
|
|
|
|
expectModelOwningPluginIds(model, expectedPluginIds);
|
|
},
|
|
);
|
|
|
|
it("refuses ambiguous bundled shorthand model ownership", () => {
|
|
setManifestPlugins([
|
|
createManifestProviderPlugin({
|
|
id: "openai",
|
|
providerIds: ["openai"],
|
|
modelSupport: { modelPrefixes: ["gpt-"] },
|
|
}),
|
|
createManifestProviderPlugin({
|
|
id: "proxy-openai",
|
|
providerIds: ["proxy-openai"],
|
|
modelSupport: { modelPrefixes: ["gpt-"] },
|
|
}),
|
|
]);
|
|
|
|
expectModelOwningPluginIds("gpt-5.4", undefined);
|
|
});
|
|
|
|
it("prefers non-bundled shorthand model ownership over bundled matches", () => {
|
|
setManifestPlugins([
|
|
createManifestProviderPlugin({
|
|
id: "openai",
|
|
providerIds: ["openai"],
|
|
modelSupport: { modelPrefixes: ["gpt-"] },
|
|
}),
|
|
createManifestProviderPlugin({
|
|
id: "workspace-openai",
|
|
providerIds: ["workspace-openai"],
|
|
origin: "workspace",
|
|
modelSupport: { modelPrefixes: ["gpt-"] },
|
|
}),
|
|
]);
|
|
|
|
expectModelOwningPluginIds("gpt-5.4", ["workspace-openai"]);
|
|
});
|
|
|
|
it("auto-loads a model-owned provider plugin from shorthand model refs", () => {
|
|
setManifestPlugins([
|
|
createManifestProviderPlugin({
|
|
id: "openai",
|
|
providerIds: ["openai", "openai-codex"],
|
|
modelSupport: {
|
|
modelPrefixes: ["gpt-", "o1", "o3", "o4"],
|
|
},
|
|
}),
|
|
]);
|
|
const provider: ProviderPlugin = {
|
|
id: "openai",
|
|
label: "OpenAI",
|
|
auth: [],
|
|
};
|
|
const registry = createEmptyPluginRegistry();
|
|
registry.providers.push({ pluginId: "openai", provider, source: "bundled" });
|
|
resolveRuntimePluginRegistryMock.mockReturnValue(registry);
|
|
|
|
const providers = resolvePluginProviders({
|
|
config: {},
|
|
modelRefs: ["gpt-5.4"],
|
|
bundledProviderAllowlistCompat: true,
|
|
});
|
|
|
|
expectResolvedProviders(providers, [
|
|
{ id: "openai", label: "OpenAI", auth: [], pluginId: "openai" },
|
|
]);
|
|
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
onlyPluginIds: ["openai"],
|
|
config: expect.objectContaining({
|
|
plugins: expect.objectContaining({
|
|
allow: ["openai"],
|
|
entries: {
|
|
openai: { enabled: true },
|
|
},
|
|
}),
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
});
|