mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
perf(agents): reuse provider policy manifest registry (#119305)
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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,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);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 }],
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user