perf(secrets): hint bundled web provider owners

This commit is contained in:
Vincent Koc
2026-04-07 12:57:10 +01:00
parent c084630f9e
commit ead634812e
3 changed files with 84 additions and 9 deletions

View File

@@ -141,6 +141,7 @@ export type ResolveRuntimeWebProviderSurfaceParams<
invalidAutoDetectCode: RuntimeWebWarningCode;
sourceConfig: OpenClawConfig;
context: ResolverContext;
configuredBundledPluginIdHint?: string;
resolveProviders: (params: { configuredBundledPluginId?: string }) => Promise<TProvider[]>;
sortProviders: (providers: TProvider[]) => TProvider[];
readConfiguredCredential: (params: {
@@ -162,19 +163,43 @@ export async function resolveRuntimeWebProviderSurface<
>(
params: ResolveRuntimeWebProviderSurfaceParams<TProvider, TToolConfig>,
): Promise<RuntimeWebProviderSurface<TProvider>> {
const configuredBundledPluginId = resolveManifestContractOwnerPluginId({
contract: params.contract,
value: params.rawProvider,
origin: "bundled",
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
});
const allProviders = params.sortProviders(
let configuredBundledPluginId = params.configuredBundledPluginIdHint;
if (!configuredBundledPluginId && params.rawProvider) {
configuredBundledPluginId = resolveManifestContractOwnerPluginId({
contract: params.contract,
value: params.rawProvider,
origin: "bundled",
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
});
}
let allProviders = params.sortProviders(
await params.resolveProviders({
configuredBundledPluginId,
}),
);
if (
params.rawProvider &&
params.configuredBundledPluginIdHint &&
configuredBundledPluginId &&
!allProviders.some((provider) => provider.id === params.rawProvider)
) {
configuredBundledPluginId = undefined;
}
if (params.rawProvider && !configuredBundledPluginId) {
configuredBundledPluginId = resolveManifestContractOwnerPluginId({
contract: params.contract,
value: params.rawProvider,
origin: "bundled",
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
});
allProviders = params.sortProviders(
await params.resolveProviders({
configuredBundledPluginId,
}),
);
}
const hasConfiguredSurface =
Boolean(params.toolConfig) ||
allProviders.some((provider) => {

View File

@@ -37,6 +37,14 @@ const {
const { resolveManifestContractPluginIdsByCompatibilityRuntimePathMock } = vi.hoisted(() => ({
resolveManifestContractPluginIdsByCompatibilityRuntimePathMock: vi.fn(() => ["brave"]),
}));
const { resolveManifestContractOwnerPluginIdMock, manifestRegistryActual } = vi.hoisted(() => ({
resolveManifestContractOwnerPluginIdMock: vi.fn(),
manifestRegistryActual: {
resolveManifestContractOwnerPluginId: undefined as
| typeof import("../plugins/manifest-registry.js").resolveManifestContractOwnerPluginId
| undefined,
},
}));
let secretResolve: typeof import("./resolve.js");
let createResolverContext: typeof import("./runtime-shared.js").createResolverContext;
let resolveRuntimeWebTools: typeof import("./runtime-web-tools.js").resolveRuntimeWebTools;
@@ -73,8 +81,14 @@ vi.mock("../plugins/manifest-registry.js", async () => {
const actual = await vi.importActual<typeof import("../plugins/manifest-registry.js")>(
"../plugins/manifest-registry.js",
);
manifestRegistryActual.resolveManifestContractOwnerPluginId =
actual.resolveManifestContractOwnerPluginId;
resolveManifestContractOwnerPluginIdMock.mockImplementation(
actual.resolveManifestContractOwnerPluginId,
);
return {
...actual,
resolveManifestContractOwnerPluginId: resolveManifestContractOwnerPluginIdMock,
resolveManifestContractPluginIdsByCompatibilityRuntimePath:
resolveManifestContractPluginIdsByCompatibilityRuntimePathMock,
};
@@ -297,6 +311,11 @@ describe("runtime web tools resolution", () => {
resolveBundledExplicitWebFetchProvidersFromPublicArtifactsMock.mockClear();
resolveBundledWebSearchProvidersFromPublicArtifactsMock.mockClear();
resolveBundledWebFetchProvidersFromPublicArtifactsMock.mockClear();
resolveManifestContractOwnerPluginIdMock.mockReset();
resolveManifestContractOwnerPluginIdMock.mockImplementation(
manifestRegistryActual.resolveManifestContractOwnerPluginId!,
);
resolveManifestContractOwnerPluginIdMock.mockClear();
resolveManifestContractPluginIdsByCompatibilityRuntimePathMock.mockClear();
});
@@ -798,6 +817,7 @@ describe("runtime web tools resolution", () => {
expect(resolveBundledExplicitWebSearchProvidersFromPublicArtifactsMock).toHaveBeenCalledWith({
onlyPluginIds: ["google"],
});
expect(resolveManifestContractOwnerPluginIdMock).not.toHaveBeenCalled();
expect(resolveBundledWebSearchProvidersFromPublicArtifactsMock).not.toHaveBeenCalled();
expect(resolvePluginWebSearchProvidersMock).not.toHaveBeenCalled();
});

View File

@@ -82,6 +82,31 @@ function hasPluginScopedWebToolConfig(
});
}
function inferSingleBundledPluginScopedWebToolConfigOwner(
config: OpenClawConfig,
key: "webSearch" | "webFetch",
): string | undefined {
const entries = config.plugins?.entries;
if (!entries) {
return undefined;
}
const matches: string[] = [];
for (const [pluginId, entry] of Object.entries(entries)) {
if (!isRecord(entry) || entry.enabled === false) {
continue;
}
const pluginConfig = isRecord(entry.config) ? entry.config : undefined;
if (!isRecord(pluginConfig?.[key])) {
continue;
}
matches.push(pluginId);
if (matches.length > 1) {
return undefined;
}
}
return matches[0];
}
function hasCustomWebSearchPluginRisk(config: OpenClawConfig): boolean {
const plugins = config.plugins;
if (!plugins) {
@@ -490,6 +515,10 @@ export async function resolveRuntimeWebTools(params: {
};
}
const rawProvider = normalizeLowercaseStringOrEmpty(search?.provider);
const configuredBundledWebSearchPluginIdHint =
rawProvider && hasPluginWebSearchConfig && !hasCustomWebSearchPluginRisk(params.sourceConfig)
? inferSingleBundledPluginScopedWebToolConfigOwner(params.sourceConfig, "webSearch")
: undefined;
const searchMetadata: RuntimeWebSearchMetadata = {
providerSource: "none",
diagnostics: [],
@@ -518,6 +547,7 @@ export async function resolveRuntimeWebTools(params: {
invalidAutoDetectCode: "WEB_SEARCH_PROVIDER_INVALID_AUTODETECT",
sourceConfig: params.sourceConfig,
context: params.context,
configuredBundledPluginIdHint: configuredBundledWebSearchPluginIdHint,
resolveProviders: ({ configuredBundledPluginId }) =>
resolveBundledWebSearchProviders({
sourceConfig: params.sourceConfig,