mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:30:44 +00:00
refactor: simplify plugin cache boundaries
This commit is contained in:
@@ -5,15 +5,15 @@ import {
|
||||
withBundledPluginEnablementCompat,
|
||||
withBundledPluginVitestCompat,
|
||||
} from "./bundled-compat.js";
|
||||
import {
|
||||
resolveConfigScopedRuntimeCacheValue,
|
||||
type ConfigScopedRuntimeCache,
|
||||
} from "./config-scoped-runtime-cache.js";
|
||||
import {
|
||||
resolvePluginRegistryLoadCacheKey,
|
||||
resolveRuntimePluginRegistry,
|
||||
type PluginLoadOptions,
|
||||
} from "./loader.js";
|
||||
import {
|
||||
resolveConfigScopedRuntimeCacheValue,
|
||||
type ConfigScopedRuntimeCache,
|
||||
} from "./plugin-cache-primitives.js";
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
|
||||
import type { PluginRegistry } from "./registry-types.js";
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export {
|
||||
resolveConfigScopedRuntimeCacheValue,
|
||||
type ConfigScopedRuntimeCache,
|
||||
} from "./plugin-cache-primitives.js";
|
||||
@@ -5,9 +5,21 @@ import {
|
||||
setCurrentPluginMetadataSnapshotState,
|
||||
} from "./current-plugin-metadata-state.js";
|
||||
import { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js";
|
||||
import { resolvePluginMetadataSnapshotConfigFingerprint } from "./plugin-metadata-config-fingerprint.js";
|
||||
import {
|
||||
resolvePluginControlPlaneFingerprint,
|
||||
type ResolvePluginControlPlaneContextParams,
|
||||
} from "./plugin-control-plane-context.js";
|
||||
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js";
|
||||
export { resolvePluginMetadataSnapshotConfigFingerprint } from "./plugin-metadata-config-fingerprint.js";
|
||||
|
||||
export function resolvePluginMetadataSnapshotConfigFingerprint(
|
||||
config?: OpenClawConfig,
|
||||
options: Omit<ResolvePluginControlPlaneContextParams, "config"> = {},
|
||||
): string {
|
||||
return resolvePluginControlPlaneFingerprint({
|
||||
config,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
// Single-slot Gateway-owned handoff. Replace or clear it at lifecycle boundaries;
|
||||
// never accumulate historical metadata snapshots here.
|
||||
|
||||
@@ -1,9 +1,50 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import {
|
||||
PluginLruCache,
|
||||
resolveConfigScopedRuntimeCacheValue,
|
||||
type ConfigScopedRuntimeCache,
|
||||
} from "./config-scoped-runtime-cache.js";
|
||||
} from "./plugin-cache-primitives.js";
|
||||
|
||||
describe("PluginLruCache", () => {
|
||||
it("evicts the least recently used entry", () => {
|
||||
const cache = new PluginLruCache<string>(2);
|
||||
|
||||
cache.set("", "empty");
|
||||
cache.set("a", "alpha");
|
||||
cache.set("b", "bravo");
|
||||
expect(cache.get("a")).toBe("alpha");
|
||||
|
||||
cache.set("c", "charlie");
|
||||
|
||||
expect(cache.get("b")).toBeUndefined();
|
||||
expect(cache.get("a")).toBe("alpha");
|
||||
expect(cache.get("c")).toBe("charlie");
|
||||
});
|
||||
|
||||
it("returns hit state for cached null values", () => {
|
||||
const cache = new PluginLruCache<string | null>(2);
|
||||
|
||||
cache.set("missing", null);
|
||||
|
||||
expect(cache.getResult("missing")).toEqual({ hit: true, value: null });
|
||||
expect(cache.getResult("unknown")).toEqual({ hit: false });
|
||||
});
|
||||
|
||||
it("resizes and falls back to the default max entry count", () => {
|
||||
const cache = new PluginLruCache<string>(2);
|
||||
|
||||
cache.setMaxEntriesForTest(1.9);
|
||||
cache.set("a", "alpha");
|
||||
cache.set("b", "bravo");
|
||||
expect(cache.maxEntries).toBe(1);
|
||||
expect(cache.size).toBe(1);
|
||||
expect(cache.get("a")).toBeUndefined();
|
||||
|
||||
cache.setMaxEntriesForTest();
|
||||
expect(cache.maxEntries).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveConfigScopedRuntimeCacheValue", () => {
|
||||
it("caches values by config object and key", () => {
|
||||
@@ -1,42 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { PluginLruCache } from "./plugin-cache-primitives.js";
|
||||
|
||||
describe("PluginLruCache", () => {
|
||||
it("evicts the least recently used entry", () => {
|
||||
const cache = new PluginLruCache<string>(2);
|
||||
|
||||
cache.set("", "empty");
|
||||
cache.set("a", "alpha");
|
||||
cache.set("b", "bravo");
|
||||
expect(cache.get("a")).toBe("alpha");
|
||||
|
||||
cache.set("c", "charlie");
|
||||
|
||||
expect(cache.get("b")).toBeUndefined();
|
||||
expect(cache.get("a")).toBe("alpha");
|
||||
expect(cache.get("c")).toBe("charlie");
|
||||
});
|
||||
|
||||
it("returns hit state for cached null values", () => {
|
||||
const cache = new PluginLruCache<string | null>(2);
|
||||
|
||||
cache.set("missing", null);
|
||||
|
||||
expect(cache.getResult("missing")).toEqual({ hit: true, value: null });
|
||||
expect(cache.getResult("unknown")).toEqual({ hit: false });
|
||||
});
|
||||
|
||||
it("resizes and falls back to the default max entry count", () => {
|
||||
const cache = new PluginLruCache<string>(2);
|
||||
|
||||
cache.setMaxEntriesForTest(1.9);
|
||||
cache.set("a", "alpha");
|
||||
cache.set("b", "bravo");
|
||||
expect(cache.maxEntries).toBe(1);
|
||||
expect(cache.size).toBe(1);
|
||||
expect(cache.get("a")).toBeUndefined();
|
||||
|
||||
cache.setMaxEntriesForTest();
|
||||
expect(cache.maxEntries).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { InstalledPluginIndex } from "./installed-plugin-index.js";
|
||||
import { resolvePluginControlPlaneFingerprint } from "./plugin-control-plane-context.js";
|
||||
|
||||
export {
|
||||
fingerprintPluginControlPlaneContext,
|
||||
fingerprintPluginDiscoveryContext,
|
||||
resolvePluginControlPlaneContext,
|
||||
resolvePluginControlPlaneFingerprint,
|
||||
resolvePluginDiscoveryContext,
|
||||
resolvePluginDiscoveryFingerprint,
|
||||
} from "./plugin-control-plane-context.js";
|
||||
|
||||
export function resolvePluginMetadataSnapshotConfigFingerprint(
|
||||
config: OpenClawConfig | undefined,
|
||||
options: {
|
||||
activationFingerprint?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
index?: InstalledPluginIndex;
|
||||
inventoryFingerprint?: string;
|
||||
policyHash?: string;
|
||||
workspaceDir?: string;
|
||||
} = {},
|
||||
): string {
|
||||
return resolvePluginControlPlaneFingerprint({
|
||||
config,
|
||||
activationFingerprint: options.activationFingerprint,
|
||||
env: options.env,
|
||||
index: options.index,
|
||||
inventoryFingerprint: options.inventoryFingerprint,
|
||||
policyHash: options.policyHash,
|
||||
workspaceDir: options.workspaceDir,
|
||||
});
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
resolveInstalledManifestRegistryIndexFingerprint,
|
||||
} from "./manifest-registry-installed.js";
|
||||
import type { PluginManifestRecord } from "./manifest-registry.js";
|
||||
import { resolvePluginMetadataSnapshotConfigFingerprint } from "./plugin-metadata-config-fingerprint.js";
|
||||
import { resolvePluginControlPlaneFingerprint } from "./plugin-control-plane-context.js";
|
||||
import type {
|
||||
LoadPluginMetadataSnapshotParams,
|
||||
PluginMetadataSnapshot,
|
||||
@@ -23,6 +23,15 @@ export type {
|
||||
PluginMetadataSnapshotRegistryDiagnostic,
|
||||
} from "./plugin-metadata-snapshot.types.js";
|
||||
|
||||
function resolvePluginMetadataSnapshotConfigFingerprint(
|
||||
params: Pick<LoadPluginMetadataSnapshotParams, "config" | "env" | "workspaceDir"> & {
|
||||
index?: InstalledPluginIndex;
|
||||
policyHash?: string;
|
||||
},
|
||||
): string {
|
||||
return resolvePluginControlPlaneFingerprint(params);
|
||||
}
|
||||
|
||||
function indexesMatch(
|
||||
left: InstalledPluginIndex | undefined,
|
||||
right: InstalledPluginIndex | undefined,
|
||||
@@ -51,7 +60,8 @@ export function isPluginMetadataSnapshotCompatible(params: {
|
||||
params.snapshot.policyHash === resolveInstalledPluginIndexPolicyHash(params.config) &&
|
||||
(!params.snapshot.configFingerprint ||
|
||||
params.snapshot.configFingerprint ===
|
||||
resolvePluginMetadataSnapshotConfigFingerprint(params.config, {
|
||||
resolvePluginMetadataSnapshotConfigFingerprint({
|
||||
config: params.config,
|
||||
env,
|
||||
index: params.index ?? params.snapshot.index,
|
||||
policyHash: params.snapshot.policyHash,
|
||||
@@ -185,7 +195,8 @@ function loadPluginMetadataSnapshotImpl(
|
||||
|
||||
return {
|
||||
policyHash: index.policyHash,
|
||||
configFingerprint: resolvePluginMetadataSnapshotConfigFingerprint(params.config, {
|
||||
configFingerprint: resolvePluginMetadataSnapshotConfigFingerprint({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
index,
|
||||
policyHash: index.policyHash,
|
||||
|
||||
@@ -26,6 +26,63 @@ async function loadCachedPluginModuleLoader(scope: string) {
|
||||
}
|
||||
|
||||
describe("getCachedPluginModuleLoader", () => {
|
||||
it("resolves deterministic cache entries for equivalent alias maps", async () => {
|
||||
const { resolvePluginModuleLoaderCacheEntry } = await importFreshModule<
|
||||
typeof import("./plugin-module-loader-cache.js")
|
||||
>(import.meta.url, "./plugin-module-loader-cache.js?scope=cache-entry-alias-order");
|
||||
|
||||
const first = resolvePluginModuleLoaderCacheEntry({
|
||||
modulePath: "/repo/extensions/demo/index.ts",
|
||||
importerUrl: "file:///repo/src/plugins/loader.ts",
|
||||
loaderFilename: "/repo/src/plugins/loader.ts",
|
||||
aliasMap: {
|
||||
alpha: "/repo/alpha.js",
|
||||
zeta: "/repo/zeta.js",
|
||||
},
|
||||
tryNative: false,
|
||||
});
|
||||
const second = resolvePluginModuleLoaderCacheEntry({
|
||||
modulePath: "/repo/extensions/demo/index.ts",
|
||||
importerUrl: "file:///repo/src/plugins/loader.ts",
|
||||
loaderFilename: "/repo/src/plugins/loader.ts",
|
||||
aliasMap: {
|
||||
zeta: "/repo/zeta.js",
|
||||
alpha: "/repo/alpha.js",
|
||||
},
|
||||
tryNative: false,
|
||||
});
|
||||
|
||||
expect(second.cacheKey).toBe(first.cacheKey);
|
||||
expect(second.scopedCacheKey).toBe(first.scopedCacheKey);
|
||||
expect(first.loaderFilename).toBe("/repo/src/plugins/loader.ts");
|
||||
});
|
||||
|
||||
it("keeps explicit shared cache scope keys independent of loader options", async () => {
|
||||
const { resolvePluginModuleLoaderCacheEntry } = await importFreshModule<
|
||||
typeof import("./plugin-module-loader-cache.js")
|
||||
>(import.meta.url, "./plugin-module-loader-cache.js?scope=cache-entry-shared-scope");
|
||||
|
||||
const first = resolvePluginModuleLoaderCacheEntry({
|
||||
modulePath: "/repo/dist/extensions/demo-a/api.js",
|
||||
importerUrl: "file:///repo/src/plugins/public-surface-loader.ts",
|
||||
loaderFilename: "/repo/src/plugins/public-surface-loader.ts",
|
||||
aliasMap: { demo: "/repo/demo-a.js" },
|
||||
tryNative: true,
|
||||
sharedCacheScopeKey: "bundled:native",
|
||||
});
|
||||
const second = resolvePluginModuleLoaderCacheEntry({
|
||||
modulePath: "/repo/dist/extensions/demo-b/api.js",
|
||||
importerUrl: "file:///repo/src/plugins/public-surface-loader.ts",
|
||||
loaderFilename: "/repo/src/plugins/public-surface-loader.ts",
|
||||
aliasMap: { demo: "/repo/demo-b.js" },
|
||||
tryNative: false,
|
||||
sharedCacheScopeKey: "bundled:native",
|
||||
});
|
||||
|
||||
expect(first.cacheKey).not.toBe(second.cacheKey);
|
||||
expect(first.scopedCacheKey).toBe(second.scopedCacheKey);
|
||||
});
|
||||
|
||||
it("reuses cached loaders for the same module config and filename", async () => {
|
||||
const { createJiti, getCachedPluginModuleLoader } =
|
||||
await loadCachedPluginModuleLoader("cached-loader");
|
||||
|
||||
@@ -15,6 +15,25 @@ export type PluginModuleLoaderCache = Pick<
|
||||
PluginLruCache<PluginModuleLoader>,
|
||||
"clear" | "get" | "set" | "size"
|
||||
>;
|
||||
export type ResolvePluginModuleLoaderCacheEntryParams = {
|
||||
modulePath: string;
|
||||
importerUrl: string;
|
||||
argvEntry?: string;
|
||||
preferBuiltDist?: boolean;
|
||||
loaderFilename?: string;
|
||||
aliasMap?: Record<string, string>;
|
||||
tryNative?: boolean;
|
||||
pluginSdkResolution?: PluginSdkResolutionPreference;
|
||||
cacheScopeKey?: string;
|
||||
sharedCacheScopeKey?: string;
|
||||
};
|
||||
export type PluginModuleLoaderCacheEntry = {
|
||||
loaderFilename: string;
|
||||
aliasMap: Record<string, string>;
|
||||
tryNative: boolean;
|
||||
cacheKey: string;
|
||||
scopedCacheKey: string;
|
||||
};
|
||||
|
||||
const DEFAULT_PLUGIN_MODULE_LOADER_CACHE_ENTRIES = 128;
|
||||
|
||||
@@ -24,34 +43,27 @@ export function createPluginModuleLoaderCache(
|
||||
return new PluginLruCache<PluginModuleLoader>(maxEntries);
|
||||
}
|
||||
|
||||
export function getCachedPluginModuleLoader(params: {
|
||||
cache: PluginModuleLoaderCache;
|
||||
modulePath: string;
|
||||
importerUrl: string;
|
||||
argvEntry?: string;
|
||||
preferBuiltDist?: boolean;
|
||||
loaderFilename?: string;
|
||||
createLoader?: PluginModuleLoaderFactory;
|
||||
aliasMap?: Record<string, string>;
|
||||
tryNative?: boolean;
|
||||
pluginSdkResolution?: PluginSdkResolutionPreference;
|
||||
cacheScopeKey?: string;
|
||||
sharedCacheScopeKey?: string;
|
||||
}): PluginModuleLoader {
|
||||
function resolveDefaultPluginModuleLoaderConfig(
|
||||
params: ResolvePluginModuleLoaderCacheEntryParams,
|
||||
): ReturnType<typeof resolvePluginLoaderModuleConfig> {
|
||||
return resolvePluginLoaderModuleConfig({
|
||||
modulePath: params.modulePath,
|
||||
argv1: params.argvEntry ?? process.argv[1],
|
||||
moduleUrl: params.importerUrl,
|
||||
...(params.preferBuiltDist ? { preferBuiltDist: true } : {}),
|
||||
...(params.pluginSdkResolution ? { pluginSdkResolution: params.pluginSdkResolution } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
export function resolvePluginModuleLoaderCacheEntry(
|
||||
params: ResolvePluginModuleLoaderCacheEntryParams,
|
||||
): PluginModuleLoaderCacheEntry {
|
||||
const loaderFilename = toSafeImportPath(params.loaderFilename ?? params.modulePath);
|
||||
const hasAliasOverride = Boolean(params.aliasMap);
|
||||
const hasTryNativeOverride = typeof params.tryNative === "boolean";
|
||||
const defaultConfig =
|
||||
hasAliasOverride || hasTryNativeOverride
|
||||
? resolvePluginLoaderModuleConfig({
|
||||
modulePath: params.modulePath,
|
||||
argv1: params.argvEntry ?? process.argv[1],
|
||||
moduleUrl: params.importerUrl,
|
||||
...(params.preferBuiltDist ? { preferBuiltDist: true } : {}),
|
||||
...(params.pluginSdkResolution
|
||||
? { pluginSdkResolution: params.pluginSdkResolution }
|
||||
: {}),
|
||||
})
|
||||
? resolveDefaultPluginModuleLoaderConfig(params)
|
||||
: null;
|
||||
const canReuseDefaultCacheKey =
|
||||
defaultConfig !== null &&
|
||||
@@ -63,13 +75,7 @@ export function getCachedPluginModuleLoader(params: {
|
||||
aliasMap: params.aliasMap ?? defaultConfig.aliasMap,
|
||||
cacheKey: canReuseDefaultCacheKey ? defaultConfig.cacheKey : undefined,
|
||||
}
|
||||
: resolvePluginLoaderModuleConfig({
|
||||
modulePath: params.modulePath,
|
||||
argv1: params.argvEntry ?? process.argv[1],
|
||||
moduleUrl: params.importerUrl,
|
||||
...(params.preferBuiltDist ? { preferBuiltDist: true } : {}),
|
||||
...(params.pluginSdkResolution ? { pluginSdkResolution: params.pluginSdkResolution } : {}),
|
||||
});
|
||||
: resolveDefaultPluginModuleLoaderConfig(params);
|
||||
const { tryNative, aliasMap } = resolved;
|
||||
const cacheKey =
|
||||
resolved.cacheKey ??
|
||||
@@ -81,18 +87,29 @@ export function getCachedPluginModuleLoader(params: {
|
||||
params.sharedCacheScopeKey ??
|
||||
(params.cacheScopeKey ? `${params.cacheScopeKey}::${cacheKey}` : cacheKey)
|
||||
}`;
|
||||
const cached = params.cache.get(scopedCacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
return {
|
||||
loaderFilename,
|
||||
aliasMap,
|
||||
tryNative,
|
||||
cacheKey,
|
||||
scopedCacheKey,
|
||||
};
|
||||
}
|
||||
|
||||
function createLazySourceTransformLoader(params: {
|
||||
loaderFilename: string;
|
||||
aliasMap: Record<string, string>;
|
||||
tryNative: boolean;
|
||||
createLoader?: PluginModuleLoaderFactory;
|
||||
}): () => PluginModuleLoader {
|
||||
let loadWithSourceTransform: PluginModuleLoader | undefined;
|
||||
const getLoadWithSourceTransform = (): PluginModuleLoader => {
|
||||
return () => {
|
||||
if (loadWithSourceTransform) {
|
||||
return loadWithSourceTransform;
|
||||
}
|
||||
const jitiLoader = (params.createLoader ?? createJiti)(loaderFilename, {
|
||||
...buildPluginLoaderJitiOptions(aliasMap),
|
||||
tryNative,
|
||||
const jitiLoader = (params.createLoader ?? createJiti)(params.loaderFilename, {
|
||||
...buildPluginLoaderJitiOptions(params.aliasMap),
|
||||
tryNative: params.tryNative,
|
||||
});
|
||||
loadWithSourceTransform = new Proxy(jitiLoader, {
|
||||
apply(target, thisArg, argArray) {
|
||||
@@ -108,18 +125,25 @@ export function getCachedPluginModuleLoader(params: {
|
||||
});
|
||||
return loadWithSourceTransform;
|
||||
};
|
||||
}
|
||||
|
||||
function createPluginModuleLoader(params: {
|
||||
loaderFilename: string;
|
||||
aliasMap: Record<string, string>;
|
||||
tryNative: boolean;
|
||||
createLoader?: PluginModuleLoaderFactory;
|
||||
}): PluginModuleLoader {
|
||||
const getLoadWithSourceTransform = createLazySourceTransformLoader(params);
|
||||
// When the caller has explicitly opted out of native loading (for example
|
||||
// `bundled-capability-runtime` in Vitest+dist mode, which depends on
|
||||
// jiti's alias rewriting to surface a narrow SDK slice), route every
|
||||
// target through jiti so those alias rewrites still apply.
|
||||
if (!tryNative) {
|
||||
const loader = ((target: string, ...rest: unknown[]) =>
|
||||
if (!params.tryNative) {
|
||||
return ((target: string, ...rest: unknown[]) =>
|
||||
(getLoadWithSourceTransform() as (t: string, ...a: unknown[]) => unknown)(
|
||||
target,
|
||||
...rest,
|
||||
)) as PluginModuleLoader;
|
||||
params.cache.set(scopedCacheKey, loader);
|
||||
return loader;
|
||||
}
|
||||
// Otherwise prefer native require() for already-compiled JS artifacts
|
||||
// (the bundled plugin public surfaces shipped in dist/). jiti's transform
|
||||
@@ -128,7 +152,7 @@ export function getCachedPluginModuleLoader(params: {
|
||||
// for TS / TSX sources and for the small set of require(esm) /
|
||||
// async-module fallbacks `tryNativeRequireJavaScriptModule` declines to
|
||||
// handle.
|
||||
const loader = ((target: string, ...rest: unknown[]) => {
|
||||
return ((target: string, ...rest: unknown[]) => {
|
||||
const native = tryNativeRequireJavaScriptModule(target, { allowWindows: true });
|
||||
if (native.ok) {
|
||||
return native.moduleExport;
|
||||
@@ -138,7 +162,26 @@ export function getCachedPluginModuleLoader(params: {
|
||||
...rest,
|
||||
);
|
||||
}) as PluginModuleLoader;
|
||||
params.cache.set(scopedCacheKey, loader);
|
||||
}
|
||||
|
||||
export function getCachedPluginModuleLoader(
|
||||
params: ResolvePluginModuleLoaderCacheEntryParams & {
|
||||
cache: PluginModuleLoaderCache;
|
||||
createLoader?: PluginModuleLoaderFactory;
|
||||
},
|
||||
): PluginModuleLoader {
|
||||
const cacheEntry = resolvePluginModuleLoaderCacheEntry(params);
|
||||
const cached = params.cache.get(cacheEntry.scopedCacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const loader = createPluginModuleLoader({
|
||||
loaderFilename: cacheEntry.loaderFilename,
|
||||
aliasMap: cacheEntry.aliasMap,
|
||||
tryNative: cacheEntry.tryNative,
|
||||
...(params.createLoader ? { createLoader: params.createLoader } : {}),
|
||||
});
|
||||
params.cache.set(cacheEntry.scopedCacheKey, loader);
|
||||
return loader;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import {
|
||||
resolveConfigScopedRuntimeCacheValue,
|
||||
type ConfigScopedRuntimeCache,
|
||||
} from "./config-scoped-runtime-cache.js";
|
||||
} from "./plugin-cache-primitives.js";
|
||||
import { resolvePluginControlPlaneFingerprint } from "./plugin-control-plane-context.js";
|
||||
import { resolveProviderConfigApiOwnerHint } from "./provider-config-owner.js";
|
||||
import { isPluginProvidersLoadInFlight, resolvePluginProviders } from "./providers.runtime.js";
|
||||
|
||||
Reference in New Issue
Block a user