perf(agents): reuse provider policy manifest registry (#119305)

This commit is contained in:
Vincent Koc
2026-08-05 02:25:11 +08:00
committed by GitHub
parent 941e18d4d1
commit 832459d2ae
11 changed files with 352 additions and 18 deletions
@@ -1,5 +1,5 @@
// Verifies models.json planning applies config env vars and discovery scope.
import { beforeAll, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { createConfigRuntimeEnv } from "../config/env-vars.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
@@ -17,6 +17,15 @@ import {
import type { ProviderConfig } from "./models-config.providers.secrets.js";
import { encodePluginModelCatalogRelativePath } from "./plugin-model-catalog.js";
const providerRuntimeMocks = vi.hoisted(() => ({
normalizeProviderConfigWithPlugin: vi.fn<
typeof import("../plugins/provider-runtime.js").normalizeProviderConfigWithPlugin
>(() => undefined),
resolveProviderConfigApiKeyWithPlugin: vi.fn<
typeof import("../plugins/provider-runtime.js").resolveProviderConfigApiKeyWithPlugin
>(() => undefined),
}));
vi.mock("./provider-auth-aliases.js", () => ({
resolveProviderAuthAliasMap: () => Object.create(null) as Record<string, string>,
resolveProviderIdForAuth: (provider: string) => provider.trim().toLowerCase(),
@@ -26,8 +35,8 @@ vi.mock("./provider-auth-aliases.js", () => ({
// provider markers local instead of loading the bundled plugin/runtime catalog.
vi.mock("../plugins/provider-runtime.js", () => ({
applyProviderNativeStreamingUsageCompatWithPlugin: () => undefined,
normalizeProviderConfigWithPlugin: () => undefined,
resolveProviderConfigApiKeyWithPlugin: () => undefined,
normalizeProviderConfigWithPlugin: providerRuntimeMocks.normalizeProviderConfigWithPlugin,
resolveProviderConfigApiKeyWithPlugin: providerRuntimeMocks.resolveProviderConfigApiKeyWithPlugin,
resolveExternalAuthProfilesWithPlugins: () => [],
resolveProviderSyntheticAuthWithPlugin: () => undefined,
}));
@@ -51,6 +60,13 @@ vi.mock("./model-auth-env-vars.js", () => ({
const TEST_ENV_VAR = "OPENCLAW_MODELS_CONFIG_TEST_ENV";
afterEach(() => {
providerRuntimeMocks.normalizeProviderConfigWithPlugin.mockReset();
providerRuntimeMocks.normalizeProviderConfigWithPlugin.mockReturnValue(undefined);
providerRuntimeMocks.resolveProviderConfigApiKeyWithPlugin.mockReset();
providerRuntimeMocks.resolveProviderConfigApiKeyWithPlugin.mockReturnValue(undefined);
});
function createImplicitOpenRouterProvider(): ProviderConfig {
return {
baseUrl: "https://openrouter.ai/api/v1",
@@ -393,6 +409,64 @@ describe("models-config", () => {
expect(observedSnapshot).toBe(pluginMetadataSnapshot);
});
it.each([
{ label: "full", pluginIds: undefined, expectedRegistry: true },
{ label: "scoped", pluginIds: ["owner"], expectedRegistry: false },
])(
"threads provider policy metadata only from a $label plugin snapshot",
async ({ pluginIds, expectedRegistry }) => {
const manifestRegistry = { plugins: [], diagnostics: [] };
const pluginMetadataSnapshot = {
index: { plugins: [] },
manifestRegistry,
owners: {
providers: new Map(),
modelCatalogProviders: new Map(),
setupProviders: new Map(),
},
...(pluginIds ? { pluginIds } : {}),
} as unknown as Pick<
PluginMetadataSnapshot,
"index" | "manifestRegistry" | "owners" | "pluginIds"
>;
providerRuntimeMocks.resolveProviderConfigApiKeyWithPlugin.mockReturnValue(
"POLICY_ALIAS_API_KEY",
);
await planOpenClawModelsJsonWithDeps(
{
cfg: { models: { providers: {} } },
agentDir: "/tmp/openclaw-models-config-policy-registry-test",
env: {},
existingRaw: "",
existingParsed: null,
pluginMetadataSnapshot,
},
{
resolveImplicitProviders: async () => ({
"policy-alias": createImplicitOpenAiProvider({
baseUrl: "https://policy.example/v1",
apiKey: undefined,
}),
}),
},
);
const normalizeParams =
providerRuntimeMocks.normalizeProviderConfigWithPlugin.mock.calls.find(
([params]) => params.provider === "policy-alias",
)?.[0];
const apiKeyParams =
providerRuntimeMocks.resolveProviderConfigApiKeyWithPlugin.mock.calls.find(
([params]) => params.provider === "policy-alias",
)?.[0];
expect(normalizeParams?.manifestRegistry).toBe(
expectedRegistry ? manifestRegistry : undefined,
);
expect(apiKeyParams?.manifestRegistry).toBe(expectedRegistry ? manifestRegistry : undefined);
},
);
it("does not write unauthenticated model providers that would invalidate models.json", async () => {
expect(unauthenticatedProviderWritePlan.action).toBe("write");
expect(unauthenticatedProviderParsed.providers?.openai).toBeUndefined();
+11 -1
View File
@@ -222,7 +222,10 @@ async function planOpenClawModelsJsonWithDeps(
workspaceDir?: string;
existingRaw: string;
existingParsed: unknown;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
pluginMetadataSnapshot?: Pick<
PluginMetadataSnapshot,
"index" | "manifestRegistry" | "owners" | "pluginIds"
>;
preparedStaticProviderCatalog?: PreparedProviderStaticCatalog;
providerDiscoveryProviderIds?: readonly string[];
providerDiscoveryTimeoutMs?: number;
@@ -273,6 +276,10 @@ async function planOpenClawModelsJsonWithDeps(
const mode = cfg.models?.mode ?? "merge";
const secretRefManagedProviders = new Set<string>();
const manifestPlugins = params.pluginMetadataSnapshot?.manifestRegistry.plugins;
const providerPolicyManifestRegistry =
params.pluginMetadataSnapshot?.pluginIds === undefined
? params.pluginMetadataSnapshot?.manifestRegistry
: undefined;
const normalizedProviders =
normalizeProviders({
providers,
@@ -283,6 +290,9 @@ async function planOpenClawModelsJsonWithDeps(
sourceSecretDefaults: params.sourceConfigForSecrets?.secrets?.defaults,
secretRefManagedProviders,
manifestPlugins,
...(providerPolicyManifestRegistry
? { manifestRegistry: providerPolicyManifestRegistry }
: {}),
}) ?? providers;
const mergedProviders = resolveProvidersForMode({
mode,
@@ -0,0 +1,145 @@
// Verifies models.json planning reuses prepared plugin metadata for provider aliases.
import {
afterAll,
afterEach,
beforeAll,
describe,
expect,
it,
vi,
type MockInstance,
} from "vitest";
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
import type { ProviderConfig } from "./models-config.providers.secrets.js";
const manifestRegistry = {
diagnostics: [],
plugins: [
{
id: "xai",
channels: [],
cliBackends: [],
hooks: [],
origin: "bundled",
manifestPath: "/tmp/xai/openclaw.plugin.json",
providers: ["xai"],
providerAuthAliases: { "x-ai": "xai" },
rootDir: "/tmp/xai",
skills: [],
source: "/tmp/xai/index.js",
},
],
};
vi.mock("./model-auth-env-vars.js", () => ({
listKnownProviderEnvApiKeyNames: () => ["OPENAI_API_KEY"],
resolveProviderEnvAuthLookupMaps: () => ({
aliasMap: {},
envCandidateMap: {},
authEvidenceMap: {},
}),
}));
let planOpenClawModelsJsonWithDeps: typeof import("./models-config.plan.test-support.js").planOpenClawModelsJsonWithDeps;
let loadPluginManifestRegistrySpy: MockInstance | undefined;
let loadBundledPluginPublicArtifactModuleSyncSpy: MockInstance | undefined;
let bundledPluginsDir: string;
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const originalTrustBundledPluginsDir = process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR;
beforeAll(async () => {
bundledPluginsDir = tempDirs.make("openclaw-provider-policy-registry-");
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledPluginsDir;
process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR = "1";
const manifestRegistryModule = await import("../plugins/manifest-registry.js");
loadPluginManifestRegistrySpy = vi
.spyOn(manifestRegistryModule, "loadPluginManifestRegistry")
.mockReturnValue(manifestRegistry as never);
const publicSurfaceLoader = await import("../plugins/public-surface-loader.js");
loadBundledPluginPublicArtifactModuleSyncSpy = vi
.spyOn(publicSurfaceLoader, "loadBundledPluginPublicArtifactModuleSync")
.mockImplementation(({ dirName }: { dirName: string }) => {
if (dirName !== "xai") {
throw new Error(`Unable to resolve bundled plugin public surface ${dirName}`);
}
return {
normalizeConfig: ({
providerConfig,
}: {
providerConfig: ProviderConfig;
}): ProviderConfig => ({
...providerConfig,
baseUrl: "https://normalized.example/v1",
}),
};
});
({ planOpenClawModelsJsonWithDeps } = await import("./models-config.plan.test-support.js"));
});
afterAll(() => {
loadPluginManifestRegistrySpy?.mockRestore();
loadBundledPluginPublicArtifactModuleSyncSpy?.mockRestore();
if (originalBundledPluginsDir === undefined) {
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
} else {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = originalBundledPluginsDir;
}
if (originalTrustBundledPluginsDir === undefined) {
delete process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR;
} else {
process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR = originalTrustBundledPluginsDir;
}
});
describe("models-config provider policy registry", () => {
it("does not reload manifests while resolving an alias-owned provider policy", async () => {
loadPluginManifestRegistrySpy?.mockClear();
loadBundledPluginPublicArtifactModuleSyncSpy?.mockClear();
const pluginMetadataSnapshot = {
index: { plugins: [] },
manifestRegistry,
owners: {
providers: new Map(),
modelCatalogProviders: new Map(),
setupProviders: new Map(),
},
} as unknown as Pick<
PluginMetadataSnapshot,
"index" | "manifestRegistry" | "owners" | "pluginIds"
>;
const plan = await planOpenClawModelsJsonWithDeps(
{
cfg: { models: { providers: {} } },
agentDir: "/tmp/openclaw-provider-policy-registry-test/agent",
env: {},
existingRaw: "",
existingParsed: null,
pluginMetadataSnapshot,
},
{
resolveImplicitProviders: async () => ({
"x-ai": {
baseUrl: "https://mock.example/v1",
api: "openai-responses",
apiKey: "OPENAI_API_KEY",
models: [],
},
}),
},
);
expect(plan.action).toBe("write");
expect(
plan.action === "write" ? JSON.parse(plan.contents).providers["x-ai"].baseUrl : null,
).toBe("https://normalized.example/v1");
expect(loadBundledPluginPublicArtifactModuleSyncSpy).toHaveBeenCalledWith({
dirName: "xai",
artifactBasename: "provider-policy-api.js",
});
expect(loadPluginManifestRegistrySpy).not.toHaveBeenCalled();
});
});
@@ -2,7 +2,7 @@
* Normalizes configured provider model rows for runtime/discovery use.
*/
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
import type { PluginManifestRecord, PluginManifestRegistry } from "../plugins/manifest-registry.js";
import { ensureAuthProfileStore } from "./auth-profiles/store.js";
import { normalizeConfiguredProviderCatalogModelId } from "./model-ref-shared.js";
import {
@@ -124,6 +124,7 @@ export function normalizeProviders(params: {
sourceSecretDefaults?: SecretDefaults;
secretRefManagedProviders?: Set<string>;
manifestPlugins?: ProviderModelNormalizationOptions["manifestPlugins"];
manifestRegistry?: Pick<PluginManifestRegistry, "plugins">;
}): ModelsConfig["providers"] {
const { providers } = params;
if (!providers) {
@@ -198,7 +199,7 @@ export function normalizeProviders(params: {
);
const profileApiKey = needsProfileApiKey ? resolveProfileApiKey(normalizedKey) : undefined;
const providerApiKeyResolver = needsProfileApiKey
? resolveProviderConfigApiKeyResolver(normalizedKey)
? resolveProviderConfigApiKeyResolver(normalizedKey, undefined, params.manifestRegistry)
: undefined;
const providerWithApiKey = resolveMissingProviderApiKey({
providerKey: normalizedKey,
@@ -216,6 +217,7 @@ export function normalizeProviders(params: {
const providerSpecificNormalized = normalizeProviderSpecificConfig(
normalizedKey,
normalizedProvider,
params.manifestRegistry,
);
if (providerSpecificNormalized !== normalizedProvider) {
mutated = true;
@@ -1,3 +1,4 @@
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
/**
* Runtime-policy bridge for provider config normalization. These helpers call
* plugin hooks without triggering runtime plugin loading from config assembly.
@@ -10,6 +11,8 @@ import {
import { resolveProviderPluginLookupKey } from "./models-config.providers.policy.lookup.js";
import type { ProviderConfig } from "./models-config.providers.secrets.js";
export type ProviderPolicyManifestRegistry = Pick<PluginManifestRegistry, "plugins">;
/** Apply provider native-streaming usage compatibility policy. */
export function applyProviderNativeStreamingUsagePolicy(
providerKey: string,
@@ -32,12 +35,14 @@ export function applyProviderNativeStreamingUsagePolicy(
export function normalizeProviderConfigPolicy(
providerKey: string,
provider: ProviderConfig,
manifestRegistry?: ProviderPolicyManifestRegistry,
): ProviderConfig {
const runtimeProviderKey = resolveProviderPluginLookupKey(providerKey, provider);
return (
normalizeProviderConfigWithPlugin({
provider: runtimeProviderKey,
allowRuntimePluginLoad: false,
...(manifestRegistry ? { manifestRegistry } : {}),
context: {
provider: providerKey,
providerConfig: provider,
@@ -50,12 +55,14 @@ export function normalizeProviderConfigPolicy(
export function resolveProviderConfigApiKeyPolicy(
providerKey: string,
provider?: ProviderConfig,
manifestRegistry?: ProviderPolicyManifestRegistry,
): ((env: NodeJS.ProcessEnv) => string | undefined) | undefined {
const runtimeProviderKey = resolveProviderPluginLookupKey(providerKey, provider).trim();
return (env) =>
resolveProviderConfigApiKeyWithPlugin({
provider: runtimeProviderKey,
allowRuntimePluginLoad: false,
...(manifestRegistry ? { manifestRegistry } : {}),
context: {
provider: providerKey,
env,
+5 -2
View File
@@ -5,6 +5,7 @@ import {
applyProviderNativeStreamingUsagePolicy,
normalizeProviderConfigPolicy,
resolveProviderConfigApiKeyPolicy,
type ProviderPolicyManifestRegistry,
} from "./models-config.providers.policy.runtime.js";
import type { ProviderConfig } from "./models-config.providers.secrets.js";
@@ -34,8 +35,9 @@ export function applyNativeStreamingUsageCompat(
export function normalizeProviderSpecificConfig(
providerKey: string,
provider: ProviderConfig,
manifestRegistry?: ProviderPolicyManifestRegistry,
): ProviderConfig {
const normalized = normalizeProviderConfigPolicy(providerKey, provider);
const normalized = normalizeProviderConfigPolicy(providerKey, provider, manifestRegistry);
if (normalized && normalized !== provider) {
return normalized;
}
@@ -46,6 +48,7 @@ export function normalizeProviderSpecificConfig(
export function resolveProviderConfigApiKeyResolver(
providerKey: string,
provider?: ProviderConfig,
manifestRegistry?: ProviderPolicyManifestRegistry,
): ((env: NodeJS.ProcessEnv) => string | undefined) | undefined {
return resolveProviderConfigApiKeyPolicy(providerKey, provider);
return resolveProviderConfigApiKeyPolicy(providerKey, provider, manifestRegistry);
}
+12 -3
View File
@@ -51,9 +51,14 @@ type PreparedOpenClawModelsJsonSource = ModelsJsonReadyResult & {
workspaceDir?: string;
};
type ModelsConfigPluginMetadataSnapshot = Pick<
PluginMetadataSnapshot,
"index" | "manifestRegistry" | "owners" | "pluginIds"
>;
type EnsureOpenClawModelsJsonOptions = {
env?: NodeJS.ProcessEnv;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
pluginMetadataSnapshot?: ModelsConfigPluginMetadataSnapshot;
preparedStaticProviderCatalog?: PreparedProviderStaticCatalog;
workspaceDir?: string;
providerDiscoveryProviderIds?: readonly string[];
@@ -92,7 +97,7 @@ async function buildModelsJsonFingerprint(params: {
sourceConfigForSecrets: OpenClawConfig;
agentDir: string;
workspaceDir?: string;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index">;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "pluginIds">;
providerDiscoveryProviderIds?: readonly string[];
providerDiscoveryTimeoutMs?: number;
providerDiscoveryEntriesOnly?: boolean;
@@ -120,6 +125,10 @@ async function buildModelsJsonFingerprint(params: {
pluginCatalogFingerprint,
workspaceDir: params.workspaceDir,
pluginMetadataSnapshotIndexFingerprint,
pluginMetadataSnapshotPluginIds:
params.pluginMetadataSnapshot?.pluginIds === undefined
? null
: params.pluginMetadataSnapshot.pluginIds.toSorted(),
providerDiscoveryProviderIds: params.providerDiscoveryProviderIds,
providerDiscoveryTimeoutMs: params.providerDiscoveryTimeoutMs,
providerDiscoveryEntriesOnly: params.providerDiscoveryEntriesOnly === true,
@@ -287,7 +296,7 @@ async function buildModelsJsonSourceFingerprint(
agentDirOverride?: string,
options: {
env?: NodeJS.ProcessEnv;
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
pluginMetadataSnapshot?: ModelsConfigPluginMetadataSnapshot;
workspaceDir?: string;
providerDiscoveryProviderIds?: readonly string[];
providerDiscoveryTimeoutMs?: number;
@@ -624,6 +624,34 @@ describe("models-config write serialization", () => {
});
});
it("keeps full and scoped plugin metadata snapshots in distinct cache entries", async () => {
await withModelsTempHome(async (home) => {
planOpenClawModelsJsonMock.mockImplementation(async () => ({ action: "noop" }));
const workspaceDir = path.join(home, "workspace");
const agentDir = path.join(home, "agent");
const fullSnapshot = createPluginMetadataSnapshot(workspaceDir);
const scopedSnapshot = {
...createPluginMetadataSnapshot(workspaceDir),
pluginIds: ["owner"],
};
await ensureOpenClawModelsJson({}, agentDir, {
workspaceDir,
pluginMetadataSnapshot: fullSnapshot,
});
await ensureOpenClawModelsJson({}, agentDir, {
workspaceDir,
pluginMetadataSnapshot: scopedSnapshot,
});
await ensureOpenClawModelsJson({}, agentDir, {
workspaceDir,
pluginMetadataSnapshot: fullSnapshot,
});
expect(planOpenClawModelsJsonMock).toHaveBeenCalledTimes(2);
});
});
it("serializes concurrent models.json writes to avoid overlap", async () => {
await withModelsTempHome(async () => {
const first = structuredClone(CUSTOM_PROXY_MODELS_CONFIG);
@@ -648,7 +648,7 @@ describe("provider public artifacts", () => {
expect(loadPluginManifestRegistry).not.toHaveBeenCalled();
});
it("loads provider policy surfaces without package-manager repair", async () => {
it("keeps canonical provider policy lookup on the direct artifact path", async () => {
const loadBundledPluginPublicArtifactModuleSync = vi.fn(() => ({
normalizeConfig: (ctx: { providerConfig: ModelProviderConfig }) => ctx.providerConfig,
}));
@@ -660,7 +660,12 @@ describe("provider public artifacts", () => {
typeof import("./provider-public-artifacts.js")
>(import.meta.url, "./provider-public-artifacts.js?scope=no-runtime-deps");
const surface = resolvePolicySurface("openai");
const manifestRegistry = {
get plugins(): never {
throw new Error("direct provider policy lookup must not inspect manifest metadata");
},
};
const surface = resolvePolicySurface("openai", { manifestRegistry });
expect(surface?.normalizeConfig).toBeTypeOf("function");
expect(loadBundledPluginPublicArtifactModuleSync).toHaveBeenCalledWith({
dirName: "openai",
+47 -2
View File
@@ -279,8 +279,9 @@ describe("provider-runtime", () => {
beforeAll(async () => {
vi.resetModules();
vi.doMock("./provider-public-artifacts.js", () => ({
resolveBundledProviderPolicySurface: (provider: string) =>
resolveBundledProviderPolicySurfaceMock(provider),
resolveBundledProviderPolicySurface: (
...args: Parameters<ResolveBundledProviderPolicySurface>
) => resolveBundledProviderPolicySurfaceMock(...args),
}));
vi.doMock("./providers.js", () => ({
resolveCatalogHookProviderPluginIds: (params: unknown) =>
@@ -1613,6 +1614,50 @@ describe("provider-runtime", () => {
expect(resolvePluginProvidersMock).not.toHaveBeenCalled();
});
it("forwards prepared manifest metadata to bundled config policy resolution", () => {
const manifestRegistry = { plugins: [] };
const providerConfig: ModelProviderConfig = {
baseUrl: "https://api.example.com/v1",
api: "openai-completions",
models: [],
};
resolveBundledProviderPolicySurfaceMock.mockReturnValue({
normalizeConfig: ({ providerConfig: candidateConfig }) => ({
...candidateConfig,
baseUrl: "https://normalized.example.com/v1",
}),
resolveConfigApiKey: () => "EXAMPLE_API_KEY",
});
expect(
normalizeProviderConfigWithPlugin({
provider: "example-alias",
manifestRegistry,
context: {
provider: "example-alias",
providerConfig,
},
})?.baseUrl,
).toBe("https://normalized.example.com/v1");
expect(
resolveProviderConfigApiKeyWithPlugin({
provider: "example-alias",
manifestRegistry,
context: {
provider: "example-alias",
env: {},
},
}),
).toBe("EXAMPLE_API_KEY");
expect(resolveBundledProviderPolicySurfaceMock).toHaveBeenNthCalledWith(1, "example-alias", {
manifestRegistry,
});
expect(resolveBundledProviderPolicySurfaceMock).toHaveBeenNthCalledWith(2, "example-alias", {
manifestRegistry,
});
});
it("resolves thinking profiles from bundled policy surface before runtime plugins", () => {
const resolveThinkingProfile = vi.fn(() => ({
levels: [{ id: "off" as const }],
+9 -3
View File
@@ -19,7 +19,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { UsageProviderId } from "../infra/provider-usage.types.js";
import { getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot.js";
import { normalizeProviderModelIdWithManifest } from "./manifest-model-id-normalization.js";
import type { PluginManifestRecord } from "./manifest-registry.js";
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
import { resolvePluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
import type {
PluginMetadataRegistryView,
@@ -446,12 +446,15 @@ export function normalizeProviderConfigWithPlugin(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
manifestRegistry?: Pick<PluginManifestRegistry, "plugins">;
context: ProviderNormalizeConfigContext;
allowRuntimePluginLoad?: boolean;
}): ModelProviderConfig | undefined {
const hasConfigChange = (normalized: ModelProviderConfig) =>
normalized !== params.context.providerConfig;
const bundledSurface = resolveBundledProviderPolicySurface(params.provider);
const bundledSurface = resolveBundledProviderPolicySurface(params.provider, {
manifestRegistry: params.manifestRegistry,
});
if (bundledSurface?.normalizeConfig) {
const normalized = bundledSurface.normalizeConfig(params.context);
return normalized && hasConfigChange(normalized) ? normalized : undefined;
@@ -489,10 +492,13 @@ export function resolveProviderConfigApiKeyWithPlugin(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
manifestRegistry?: Pick<PluginManifestRegistry, "plugins">;
context: ProviderResolveConfigApiKeyContext;
allowRuntimePluginLoad?: boolean;
}): string | undefined {
const bundledSurface = resolveBundledProviderPolicySurface(params.provider);
const bundledSurface = resolveBundledProviderPolicySurface(params.provider, {
manifestRegistry: params.manifestRegistry,
});
if (bundledSurface?.resolveConfigApiKey) {
return normalizeOptionalString(bundledSurface.resolveConfigApiKey(params.context));
}