perf(secrets): lazy-load runtime web-tools manifest lookups

This commit is contained in:
Vincent Koc
2026-04-12 09:23:34 +01:00
parent 1be43777e0
commit d742db1ea4
3 changed files with 63 additions and 49 deletions

View File

@@ -1 +1,5 @@
export { resolveManifestContractOwnerPluginId } from "../plugins/manifest-registry.js";
export {
resolveManifestContractOwnerPluginId,
resolveManifestContractPluginIds,
resolveManifestContractPluginIdsByCompatibilityRuntimePath,
} from "../plugins/manifest-registry.js";

View File

@@ -34,10 +34,12 @@ const {
),
resolveBundledWebFetchProvidersFromPublicArtifactsMock: vi.fn(() => buildTestWebFetchProviders()),
}));
const { resolveManifestContractPluginIdsByCompatibilityRuntimePathMock } = vi.hoisted(() => ({
const {
resolveManifestContractPluginIdsByCompatibilityRuntimePathMock,
resolveManifestContractOwnerPluginIdMock,
runtimeManifestActual,
} = vi.hoisted(() => ({
resolveManifestContractPluginIdsByCompatibilityRuntimePathMock: vi.fn(() => ["brave"]),
}));
const { resolveManifestContractOwnerPluginIdMock, runtimeManifestActual } = vi.hoisted(() => ({
resolveManifestContractOwnerPluginIdMock: vi.fn(),
runtimeManifestActual: {
resolveManifestContractOwnerPluginId: undefined as
@@ -77,17 +79,6 @@ vi.mock("./runtime-web-tools-public-artifacts.runtime.js", () => ({
resolveBundledWebFetchProvidersFromPublicArtifactsMock,
}));
vi.mock("../plugins/manifest-registry.js", async () => {
const actual = await vi.importActual<typeof import("../plugins/manifest-registry.js")>(
"../plugins/manifest-registry.js",
);
return {
...actual,
resolveManifestContractPluginIdsByCompatibilityRuntimePath:
resolveManifestContractPluginIdsByCompatibilityRuntimePathMock,
};
});
vi.mock("./runtime-web-tools-manifest.runtime.js", async () => {
const actual = await vi.importActual<typeof import("./runtime-web-tools-manifest.runtime.js")>(
"./runtime-web-tools-manifest.runtime.js",
@@ -100,6 +91,8 @@ vi.mock("./runtime-web-tools-manifest.runtime.js", async () => {
return {
...actual,
resolveManifestContractOwnerPluginId: resolveManifestContractOwnerPluginIdMock,
resolveManifestContractPluginIdsByCompatibilityRuntimePath:
resolveManifestContractPluginIdsByCompatibilityRuntimePathMock,
};
});

View File

@@ -1,9 +1,5 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveSecretInputRef } from "../config/types.secrets.js";
import {
resolveManifestContractPluginIds,
resolveManifestContractPluginIdsByCompatibilityRuntimePath,
} from "../plugins/manifest-registry.js";
import type {
PluginWebFetchProviderEntry,
PluginWebSearchProviderEntry,
@@ -54,6 +50,10 @@ const loadRuntimeWebToolsPublicArtifacts = createLazyRuntimeSurface(
() => import("./runtime-web-tools-public-artifacts.runtime.js"),
(mod) => mod,
);
const loadRuntimeWebToolsManifest = createLazyRuntimeSurface(
() => import("./runtime-web-tools-manifest.runtime.js"),
(mod) => mod,
);
type FetchConfig = NonNullable<OpenClawConfig["tools"]>["web"] extends infer Web
? Web extends { fetch?: infer Fetch }
@@ -120,8 +120,11 @@ function inferExactBundledPluginScopedWebToolConfigOwner(params: {
return isRecord(pluginConfig?.[params.key]) ? params.pluginId : undefined;
}
function hasCustomWebSearchPluginRisk(config: OpenClawConfig): boolean {
const plugins = config.plugins;
async function hasCustomWebSearchPluginRisk(params: {
config: OpenClawConfig;
env: NodeJS.ProcessEnv;
}): Promise<boolean> {
const plugins = params.config.plugins;
if (!plugins) {
return false;
}
@@ -132,12 +135,13 @@ function hasCustomWebSearchPluginRisk(config: OpenClawConfig): boolean {
return true;
}
const { resolveManifestContractPluginIds } = await loadRuntimeWebToolsManifest();
const bundledPluginIds = new Set<string>(
resolveManifestContractPluginIds({
contract: "webSearchProviders",
origin: "bundled",
config,
env: process.env,
config: params.config,
env: params.env,
}),
);
const hasNonBundledPluginId = (pluginId: string) => !bundledPluginIds.has(pluginId.trim());
@@ -465,6 +469,7 @@ export async function resolveRuntimeWebTools(params: {
}): Promise<RuntimeWebToolsMetadata> {
const defaults = params.sourceConfig.secrets?.defaults;
const diagnostics: RuntimeWebDiagnostic[] = [];
const env = { ...process.env, ...params.context.env };
const sourceTools = isRecord(params.sourceConfig.tools) ? params.sourceConfig.tools : undefined;
const sourceWeb = isRecord(sourceTools?.web) ? sourceTools.web : undefined;
@@ -472,9 +477,12 @@ export async function resolveRuntimeWebTools(params: {
? params.resolvedConfig.tools
: undefined;
const resolvedWeb = isRecord(resolvedTools?.web) ? resolvedTools.web : undefined;
let hasCustomWebSearchRisk: boolean | undefined;
const getHasCustomWebSearchRisk = (): boolean => {
hasCustomWebSearchRisk ??= hasCustomWebSearchPluginRisk(params.sourceConfig);
let hasCustomWebSearchRisk: Promise<boolean> | undefined;
const getHasCustomWebSearchRisk = (): Promise<boolean> => {
hasCustomWebSearchRisk ??= hasCustomWebSearchPluginRisk({
config: params.sourceConfig,
env,
});
return hasCustomWebSearchRisk;
};
const legacyXSearchSource = isRecord(sourceWeb?.x_search) ? sourceWeb.x_search : undefined;
@@ -533,35 +541,44 @@ export async function resolveRuntimeWebTools(params: {
};
}
const rawProvider = normalizeLowercaseStringOrEmpty(search?.provider);
const configuredBundledWebSearchPluginIdHint =
rawProvider && hasPluginWebSearchConfig
? (inferExactBundledPluginScopedWebToolConfigOwner({
config: params.sourceConfig,
key: "webSearch",
pluginId: rawProvider,
}) ??
(!getHasCustomWebSearchRisk()
? inferSingleBundledPluginScopedWebToolConfigOwner(params.sourceConfig, "webSearch")
: undefined))
: undefined;
let configuredBundledWebSearchPluginIdHint: string | undefined;
if (rawProvider && hasPluginWebSearchConfig) {
configuredBundledWebSearchPluginIdHint = inferExactBundledPluginScopedWebToolConfigOwner({
config: params.sourceConfig,
key: "webSearch",
pluginId: rawProvider,
});
if (!configuredBundledWebSearchPluginIdHint && !(await getHasCustomWebSearchRisk())) {
configuredBundledWebSearchPluginIdHint = inferSingleBundledPluginScopedWebToolConfigOwner(
params.sourceConfig,
"webSearch",
);
}
}
const searchMetadata: RuntimeWebSearchMetadata = {
providerSource: "none",
diagnostics: [],
};
if (search || hasPluginWebSearchConfig) {
const searchCompatibilityOnlyPluginIds =
let searchCompatibilityOnlyPluginIds: string[] = [];
if (
!rawProvider &&
!hasPluginWebSearchConfig &&
isRecord(search) &&
Object.prototype.hasOwnProperty.call(search, "apiKey")
? resolveManifestContractPluginIdsByCompatibilityRuntimePath({
contract: "webSearchProviders",
path: "tools.web.search.apiKey",
origin: "bundled",
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
})
: [];
) {
const { resolveManifestContractPluginIdsByCompatibilityRuntimePath } =
await loadRuntimeWebToolsManifest();
searchCompatibilityOnlyPluginIds = resolveManifestContractPluginIdsByCompatibilityRuntimePath(
{
contract: "webSearchProviders",
path: "tools.web.search.apiKey",
origin: "bundled",
config: params.sourceConfig,
env,
},
);
}
const searchSurface = await resolveRuntimeWebProviderSurface({
contract: "webSearchProviders",
rawProvider,
@@ -573,7 +590,7 @@ export async function resolveRuntimeWebTools(params: {
sourceConfig: params.sourceConfig,
context: params.context,
configuredBundledPluginIdHint: configuredBundledWebSearchPluginIdHint,
resolveProviders: ({ configuredBundledPluginId }) =>
resolveProviders: async ({ configuredBundledPluginId }) =>
resolveBundledWebSearchProviders({
sourceConfig: params.sourceConfig,
context: params.context,
@@ -581,10 +598,10 @@ export async function resolveRuntimeWebTools(params: {
onlyPluginIds:
configuredBundledPluginId === undefined &&
searchCompatibilityOnlyPluginIds.length > 0 &&
!getHasCustomWebSearchRisk()
!(await getHasCustomWebSearchRisk())
? searchCompatibilityOnlyPluginIds
: undefined,
hasCustomWebSearchPluginRisk: getHasCustomWebSearchRisk(),
hasCustomWebSearchPluginRisk: await getHasCustomWebSearchRisk(),
}),
sortProviders: sortWebSearchProvidersForAutoDetect,
readConfiguredCredential: ({ provider, config, toolConfig }) =>