mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 19:10:58 +00:00
feat(plugins): split cold provider contributions
This commit is contained in:
@@ -1,13 +1,66 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import type { ModelProviderConfig } from "../config/types.js";
|
import type { ModelProviderConfig } from "../config/types.js";
|
||||||
|
import type { PluginCandidate } from "./discovery.js";
|
||||||
import {
|
import {
|
||||||
groupPluginDiscoveryProvidersByOrder,
|
groupPluginDiscoveryProvidersByOrder,
|
||||||
normalizePluginDiscoveryResult,
|
normalizePluginDiscoveryResult,
|
||||||
|
resolveInstalledPluginProviderContributionIds,
|
||||||
runProviderCatalog,
|
runProviderCatalog,
|
||||||
runProviderStaticCatalog,
|
runProviderStaticCatalog,
|
||||||
} from "./provider-discovery.js";
|
} from "./provider-discovery.js";
|
||||||
|
import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.js";
|
||||||
import type { ProviderCatalogResult, ProviderDiscoveryOrder, ProviderPlugin } from "./types.js";
|
import type { ProviderCatalogResult, ProviderDiscoveryOrder, ProviderPlugin } from "./types.js";
|
||||||
|
|
||||||
|
const tempDirs: string[] = [];
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupTrackedTempDirs(tempDirs);
|
||||||
|
});
|
||||||
|
|
||||||
|
function makeTempDir() {
|
||||||
|
return makeTrackedTempDir("openclaw-provider-discovery", tempDirs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hermeticEnv(overrides: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv {
|
||||||
|
return {
|
||||||
|
OPENCLAW_BUNDLED_PLUGINS_DIR: undefined,
|
||||||
|
OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1",
|
||||||
|
OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE: "1",
|
||||||
|
OPENCLAW_VERSION: "2026.4.25",
|
||||||
|
VITEST: "true",
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createProviderContributionCandidate(params: {
|
||||||
|
pluginId?: string;
|
||||||
|
providerIds?: readonly string[];
|
||||||
|
}): PluginCandidate {
|
||||||
|
const rootDir = makeTempDir();
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(rootDir, "index.ts"),
|
||||||
|
"throw new Error('runtime provider entry should not load for cold contribution ids');\n",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(rootDir, "openclaw.plugin.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
id: params.pluginId ?? "demo",
|
||||||
|
configSchema: { type: "object" },
|
||||||
|
providers: params.providerIds ?? ["demo"],
|
||||||
|
}),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
idHint: params.pluginId ?? "demo",
|
||||||
|
source: path.join(rootDir, "index.ts"),
|
||||||
|
rootDir,
|
||||||
|
origin: "global",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function makeProvider(params: {
|
function makeProvider(params: {
|
||||||
id: string;
|
id: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
@@ -112,6 +165,50 @@ async function expectProviderCatalogResult(params: {
|
|||||||
).resolves.toEqual(params.expected);
|
).resolves.toEqual(params.expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe("resolveInstalledPluginProviderContributionIds", () => {
|
||||||
|
it("reads provider ids from the installed plugin index without importing runtime entries", () => {
|
||||||
|
const candidate = createProviderContributionCandidate({
|
||||||
|
pluginId: "demo",
|
||||||
|
providerIds: ["demo", "demo-alias"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveInstalledPluginProviderContributionIds({
|
||||||
|
candidates: [candidate],
|
||||||
|
env: hermeticEnv(),
|
||||||
|
}),
|
||||||
|
).toEqual(["demo", "demo-alias"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits disabled plugin provider ids unless explicitly requested", () => {
|
||||||
|
const candidate = createProviderContributionCandidate({
|
||||||
|
pluginId: "demo",
|
||||||
|
providerIds: ["demo"],
|
||||||
|
});
|
||||||
|
const params = {
|
||||||
|
candidates: [candidate],
|
||||||
|
config: {
|
||||||
|
plugins: {
|
||||||
|
entries: {
|
||||||
|
demo: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
env: hermeticEnv(),
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(resolveInstalledPluginProviderContributionIds(params)).toEqual([]);
|
||||||
|
expect(
|
||||||
|
resolveInstalledPluginProviderContributionIds({
|
||||||
|
...params,
|
||||||
|
includeDisabled: true,
|
||||||
|
}),
|
||||||
|
).toEqual(["demo"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("groupPluginDiscoveryProvidersByOrder", () => {
|
describe("groupPluginDiscoveryProvidersByOrder", () => {
|
||||||
it.each([
|
it.each([
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||||
import type { ModelProviderConfig } from "../config/types.js";
|
import type { ModelProviderConfig } from "../config/types.js";
|
||||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||||
|
import {
|
||||||
|
loadInstalledPluginIndex,
|
||||||
|
type InstalledPluginIndex,
|
||||||
|
type LoadInstalledPluginIndexParams,
|
||||||
|
} from "./installed-plugin-index.js";
|
||||||
import type { ProviderDiscoveryOrder, ProviderPlugin } from "./types.js";
|
import type { ProviderDiscoveryOrder, ProviderPlugin } from "./types.js";
|
||||||
|
|
||||||
const DISCOVERY_ORDER: readonly ProviderDiscoveryOrder[] = ["simple", "profile", "paired", "late"];
|
const DISCOVERY_ORDER: readonly ProviderDiscoveryOrder[] = ["simple", "profile", "paired", "late"];
|
||||||
@@ -28,7 +33,7 @@ function isSafeProviderConfigKey(value: string): boolean {
|
|||||||
return value !== "" && !DANGEROUS_PROVIDER_KEYS.has(value);
|
return value !== "" && !DANGEROUS_PROVIDER_KEYS.has(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolvePluginDiscoveryProviders(params: {
|
export type ResolveRuntimePluginDiscoveryProvidersParams = {
|
||||||
config?: OpenClawConfig;
|
config?: OpenClawConfig;
|
||||||
workspaceDir?: string;
|
workspaceDir?: string;
|
||||||
env?: NodeJS.ProcessEnv;
|
env?: NodeJS.ProcessEnv;
|
||||||
@@ -36,12 +41,45 @@ export async function resolvePluginDiscoveryProviders(params: {
|
|||||||
includeUntrustedWorkspacePlugins?: boolean;
|
includeUntrustedWorkspacePlugins?: boolean;
|
||||||
requireCompleteDiscoveryEntryCoverage?: boolean;
|
requireCompleteDiscoveryEntryCoverage?: boolean;
|
||||||
discoveryEntriesOnly?: boolean;
|
discoveryEntriesOnly?: boolean;
|
||||||
}): Promise<ProviderPlugin[]> {
|
};
|
||||||
|
|
||||||
|
export type ResolveInstalledPluginProviderContributionIdsParams = LoadInstalledPluginIndexParams & {
|
||||||
|
index?: InstalledPluginIndex;
|
||||||
|
includeDisabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function sortedValues(values: Iterable<string>): string[] {
|
||||||
|
return [...new Set(values)].toSorted((left, right) => left.localeCompare(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveInstalledPluginProviderContributionIds(
|
||||||
|
params: ResolveInstalledPluginProviderContributionIdsParams = {},
|
||||||
|
): string[] {
|
||||||
|
const index = params.index ?? loadInstalledPluginIndex(params);
|
||||||
|
const providerIds: string[] = [];
|
||||||
|
for (const plugin of index.plugins) {
|
||||||
|
if (!params.includeDisabled && !plugin.enabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
providerIds.push(...plugin.contributions.providers);
|
||||||
|
}
|
||||||
|
return sortedValues(providerIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveRuntimePluginDiscoveryProviders(
|
||||||
|
params: ResolveRuntimePluginDiscoveryProvidersParams,
|
||||||
|
): Promise<ProviderPlugin[]> {
|
||||||
return (await loadProviderRuntime())
|
return (await loadProviderRuntime())
|
||||||
.resolvePluginDiscoveryProvidersRuntime(params)
|
.resolvePluginDiscoveryProvidersRuntime(params)
|
||||||
.filter((provider) => resolveProviderCatalogOrderHook(provider));
|
.filter((provider) => resolveProviderCatalogOrderHook(provider));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function resolvePluginDiscoveryProviders(
|
||||||
|
params: ResolveRuntimePluginDiscoveryProvidersParams,
|
||||||
|
): Promise<ProviderPlugin[]> {
|
||||||
|
return resolveRuntimePluginDiscoveryProviders(params);
|
||||||
|
}
|
||||||
|
|
||||||
export function groupPluginDiscoveryProvidersByOrder(
|
export function groupPluginDiscoveryProvidersByOrder(
|
||||||
providers: ProviderPlugin[],
|
providers: ProviderPlugin[],
|
||||||
): Record<ProviderDiscoveryOrder, ProviderPlugin[]> {
|
): Record<ProviderDiscoveryOrder, ProviderPlugin[]> {
|
||||||
|
|||||||
Reference in New Issue
Block a user