mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:40:44 +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 { PluginCandidate } from "./discovery.js";
|
||||
import {
|
||||
groupPluginDiscoveryProvidersByOrder,
|
||||
normalizePluginDiscoveryResult,
|
||||
resolveInstalledPluginProviderContributionIds,
|
||||
runProviderCatalog,
|
||||
runProviderStaticCatalog,
|
||||
} from "./provider-discovery.js";
|
||||
import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.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: {
|
||||
id: string;
|
||||
label?: string;
|
||||
@@ -112,6 +165,50 @@ async function expectProviderCatalogResult(params: {
|
||||
).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", () => {
|
||||
it.each([
|
||||
{
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import type { ModelProviderConfig } from "../config/types.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";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
export async function resolvePluginDiscoveryProviders(params: {
|
||||
export type ResolveRuntimePluginDiscoveryProvidersParams = {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
@@ -36,12 +41,45 @@ export async function resolvePluginDiscoveryProviders(params: {
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
requireCompleteDiscoveryEntryCoverage?: 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())
|
||||
.resolvePluginDiscoveryProvidersRuntime(params)
|
||||
.filter((provider) => resolveProviderCatalogOrderHook(provider));
|
||||
}
|
||||
|
||||
export async function resolvePluginDiscoveryProviders(
|
||||
params: ResolveRuntimePluginDiscoveryProvidersParams,
|
||||
): Promise<ProviderPlugin[]> {
|
||||
return resolveRuntimePluginDiscoveryProviders(params);
|
||||
}
|
||||
|
||||
export function groupPluginDiscoveryProvidersByOrder(
|
||||
providers: ProviderPlugin[],
|
||||
): Record<ProviderDiscoveryOrder, ProviderPlugin[]> {
|
||||
|
||||
Reference in New Issue
Block a user