fix(plugins): guard provider discovery credential metadata

This commit is contained in:
Vincent Koc
2026-06-04 02:42:09 +02:00
parent 69c8097dd1
commit 8f36231e58
2 changed files with 65 additions and 9 deletions

View File

@@ -170,6 +170,19 @@ function createManifestPluginWithoutDiscovery(params: {
};
}
function createPoisonedCredentialMetadataPlugin(params: {
id: string;
poison: "setup" | "providerAuthEnvVars";
}): PluginManifestRecord {
const plugin = createManifestPluginWithoutDiscovery({ id: params.id });
return Object.defineProperty(plugin, params.poison, {
configurable: true,
get() {
throw new Error(`provider discovery ${params.poison} metadata exploded`);
},
});
}
function createProvider(params: { id: string; mode: "static" | "catalog" }): ProviderPlugin {
const hook = {
run: async () => ({
@@ -548,6 +561,45 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
expect(params.onlyPluginIds).toEqual(["kilocode"]);
});
it("skips unreadable credential metadata while selecting full-load fallbacks", () => {
const codexEntryProvider = createProvider({ id: "codex", mode: "catalog" });
const fullProviders = [createProvider({ id: "kilocode", mode: "catalog" })];
mocks.resolveDiscoveredProviderPluginIds.mockReturnValue([
"codex",
"broken-setup",
"broken-auth-vars",
"kilocode",
]);
mocks.loadPluginMetadataSnapshot.mockReturnValue({
index: { plugins: [] },
manifestRegistry: {
plugins: [
createManifestPlugin("codex"),
createPoisonedCredentialMetadataPlugin({ id: "broken-setup", poison: "setup" }),
createPoisonedCredentialMetadataPlugin({
id: "broken-auth-vars",
poison: "providerAuthEnvVars",
}),
createManifestPluginWithoutDiscovery({
id: "kilocode",
setupProviders: [{ id: "kilocode", envVars: ["KILOCODE_API_KEY"] }],
}),
],
diagnostics: [],
},
});
mocks.loadSource.mockReturnValue(codexEntryProvider);
mocks.resolvePluginProviders.mockReturnValue(fullProviders);
expect(
resolvePluginDiscoveryProvidersRuntime({
env: { KILOCODE_API_KEY: "sk-test" } as NodeJS.ProcessEnv,
}),
).toEqual([{ ...codexEntryProvider, pluginId: "codex" }, ...fullProviders]);
expect(mocks.resolvePluginProviders).toHaveBeenCalledTimes(1);
expect(requireResolvePluginProvidersParams().onlyPluginIds).toEqual(["kilocode"]);
});
it("enables bundled provider Vitest compat when falling back from discovery entries", () => {
const fullProviders = [createProvider({ id: "deepseek", mode: "catalog" })];
mocks.resolveDiscoveredProviderPluginIds.mockReturnValue([]);

View File

@@ -1,10 +1,10 @@
import path from "node:path";
import type { NormalizedModelCatalogRow } from "@openclaw/model-catalog-core/model-catalog-types";
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { sortUniqueStrings } from "../../packages/normalization-core/src/string-normalization.js";
import type { ModelDefinitionConfig, ModelProviderConfig } from "../config/types.models.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { planManifestModelCatalogRows } from "../model-catalog/manifest-planner.js";
import { sortUniqueStrings } from "../../packages/normalization-core/src/string-normalization.js";
import { loadManifestMetadataSnapshot } from "./manifest-contract-eligibility.js";
import type { PluginManifestRecord } from "./manifest-registry.js";
import { clearNativeRequireJavaScriptModuleCache } from "./native-module-require.js";
@@ -123,14 +123,18 @@ function hasProviderAuthEnvCredential(
plugin: PluginManifestRecord,
env: NodeJS.ProcessEnv,
): boolean {
const envVars = [
...(plugin.setup?.providers ?? []).flatMap((provider) => provider.envVars ?? []),
...Object.values(plugin.providerAuthEnvVars ?? {}).flat(),
];
return envVars.some((name) => {
const value = env[name]?.trim();
return value !== undefined && value !== "";
});
try {
const envVars = [
...(plugin.setup?.providers ?? []).flatMap((provider) => provider.envVars ?? []),
...Object.values(plugin.providerAuthEnvVars ?? {}).flat(),
];
return envVars.some((name) => {
const value = env[name]?.trim();
return value !== undefined && value !== "";
});
} catch {
return false;
}
}
function modelDefinitionCostFromManifestRow(