Enhance web search provider config validation and compatibility handling
- Added a test to ensure no warnings for legacy Brave config when bundled web search allowlist compatibility is applied. - Updated validation logic to incorporate compatibility configuration for bundled web search plugins. - Refactored the ensureRegistry function to utilize the new compatibility handling.
This commit is contained in:
parent
c3be293dd5
commit
b71686ab44
@ -136,6 +136,35 @@ function pluginWebSearchApiKey(
|
||||
}
|
||||
|
||||
describe("web search provider config", () => {
|
||||
it("does not warn for legacy brave config when bundled web search allowlist compat applies", () => {
|
||||
const res = validateConfigObjectWithPlugins({
|
||||
plugins: {
|
||||
allow: ["bluebubbles", "memory-core"],
|
||||
},
|
||||
tools: {
|
||||
web: {
|
||||
search: {
|
||||
enabled: true,
|
||||
apiKey: "test-brave-key", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok).toBe(true);
|
||||
if (!res.ok) {
|
||||
return;
|
||||
}
|
||||
expect(res.warnings).not.toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: "plugins.entries.brave",
|
||||
message: expect.stringContaining(
|
||||
"plugin disabled (not in allowlist) but config is present",
|
||||
),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts perplexity provider and config", () => {
|
||||
const res = validateConfigObjectWithPlugins(
|
||||
buildWebSearchProviderConfig({
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import path from "node:path";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { CHANNEL_IDS, normalizeChatChannelId } from "../channels/registry.js";
|
||||
import { withBundledPluginAllowlistCompat } from "../plugins/bundled-compat.js";
|
||||
import { resolveBundledWebSearchPluginIds } from "../plugins/bundled-web-search.js";
|
||||
import {
|
||||
normalizePluginsConfig,
|
||||
resolveEffectiveEnableState,
|
||||
@ -351,15 +353,38 @@ function validateConfigObjectWithPluginsBase(
|
||||
};
|
||||
|
||||
let registryInfo: RegistryInfo | null = null;
|
||||
let compatConfig: OpenClawConfig | null | undefined;
|
||||
|
||||
const ensureCompatConfig = (): OpenClawConfig => {
|
||||
if (compatConfig !== undefined) {
|
||||
return compatConfig ?? config;
|
||||
}
|
||||
|
||||
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
|
||||
const bundledWebSearchPluginIds = resolveBundledWebSearchPluginIds({
|
||||
config,
|
||||
workspaceDir: workspaceDir ?? undefined,
|
||||
env: opts.env,
|
||||
});
|
||||
compatConfig = withBundledPluginAllowlistCompat({
|
||||
config,
|
||||
pluginIds: bundledWebSearchPluginIds,
|
||||
});
|
||||
return compatConfig ?? config;
|
||||
};
|
||||
|
||||
const ensureRegistry = (): RegistryInfo => {
|
||||
if (registryInfo) {
|
||||
return registryInfo;
|
||||
}
|
||||
|
||||
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
|
||||
const effectiveConfig = ensureCompatConfig();
|
||||
const workspaceDir = resolveAgentWorkspaceDir(
|
||||
effectiveConfig,
|
||||
resolveDefaultAgentId(effectiveConfig),
|
||||
);
|
||||
const registry = loadPluginManifestRegistry({
|
||||
config,
|
||||
config: effectiveConfig,
|
||||
workspaceDir: workspaceDir ?? undefined,
|
||||
env: opts.env,
|
||||
});
|
||||
@ -393,7 +418,7 @@ function validateConfigObjectWithPluginsBase(
|
||||
const ensureNormalizedPlugins = (): ReturnType<typeof normalizePluginsConfig> => {
|
||||
const info = ensureRegistry();
|
||||
if (!info.normalizedPlugins) {
|
||||
info.normalizedPlugins = normalizePluginsConfig(config.plugins);
|
||||
info.normalizedPlugins = normalizePluginsConfig(ensureCompatConfig().plugins);
|
||||
}
|
||||
return info.normalizedPlugins;
|
||||
};
|
||||
|
||||
129
src/plugins/runtime/runtime-matrix-boundary.ts
Normal file
129
src/plugins/runtime/runtime-matrix-boundary.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { createJiti } from "jiti";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { loadPluginManifestRegistry } from "../manifest-registry.js";
|
||||
import {
|
||||
buildPluginLoaderJitiOptions,
|
||||
resolvePluginSdkAliasFile,
|
||||
resolvePluginSdkScopedAliasMap,
|
||||
shouldPreferNativeJiti,
|
||||
} from "../sdk-alias.js";
|
||||
|
||||
const MATRIX_PLUGIN_ID = "matrix";
|
||||
|
||||
type MatrixModule = typeof import("../../../extensions/matrix/runtime-api.js");
|
||||
|
||||
type MatrixPluginRecord = {
|
||||
rootDir?: string;
|
||||
source: string;
|
||||
};
|
||||
|
||||
let cachedModulePath: string | null = null;
|
||||
let cachedModule: MatrixModule | null = null;
|
||||
|
||||
const jitiLoaders = new Map<boolean, ReturnType<typeof createJiti>>();
|
||||
|
||||
function readConfigSafely() {
|
||||
try {
|
||||
return loadConfig();
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function resolveMatrixPluginRecord(): MatrixPluginRecord | null {
|
||||
const manifestRegistry = loadPluginManifestRegistry({
|
||||
config: readConfigSafely(),
|
||||
cache: true,
|
||||
});
|
||||
const record = manifestRegistry.plugins.find((plugin) => plugin.id === MATRIX_PLUGIN_ID);
|
||||
if (!record?.source) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
rootDir: record.rootDir,
|
||||
source: record.source,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveMatrixRuntimeModulePath(record: MatrixPluginRecord): string | null {
|
||||
const candidates = [
|
||||
path.join(path.dirname(record.source), "runtime-api.js"),
|
||||
path.join(path.dirname(record.source), "runtime-api.ts"),
|
||||
...(record.rootDir
|
||||
? [path.join(record.rootDir, "runtime-api.js"), path.join(record.rootDir, "runtime-api.ts")]
|
||||
: []),
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
const tryNative = shouldPreferNativeJiti(modulePath);
|
||||
const cached = jitiLoaders.get(tryNative);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const pluginSdkAlias = resolvePluginSdkAliasFile({
|
||||
srcFile: "root-alias.cjs",
|
||||
distFile: "root-alias.cjs",
|
||||
modulePath,
|
||||
});
|
||||
const aliasMap = {
|
||||
...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}),
|
||||
...resolvePluginSdkScopedAliasMap({ modulePath }),
|
||||
};
|
||||
const loader = createJiti(import.meta.url, {
|
||||
...buildPluginLoaderJitiOptions(aliasMap),
|
||||
tryNative,
|
||||
});
|
||||
jitiLoaders.set(tryNative, loader);
|
||||
return loader;
|
||||
}
|
||||
|
||||
function loadWithJiti<TModule>(modulePath: string): TModule {
|
||||
return getJiti(modulePath)(modulePath) as TModule;
|
||||
}
|
||||
|
||||
function loadMatrixModule(): MatrixModule | null {
|
||||
const record = resolveMatrixPluginRecord();
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
const modulePath = resolveMatrixRuntimeModulePath(record);
|
||||
if (!modulePath) {
|
||||
return null;
|
||||
}
|
||||
if (cachedModule && cachedModulePath === modulePath) {
|
||||
return cachedModule;
|
||||
}
|
||||
const loaded = loadWithJiti<MatrixModule>(modulePath);
|
||||
cachedModulePath = modulePath;
|
||||
cachedModule = loaded;
|
||||
return loaded;
|
||||
}
|
||||
|
||||
export function setMatrixThreadBindingIdleTimeoutBySessionKey(
|
||||
...args: Parameters<MatrixModule["setMatrixThreadBindingIdleTimeoutBySessionKey"]>
|
||||
): ReturnType<MatrixModule["setMatrixThreadBindingIdleTimeoutBySessionKey"]> {
|
||||
const fn = loadMatrixModule()?.setMatrixThreadBindingIdleTimeoutBySessionKey;
|
||||
if (typeof fn !== "function") {
|
||||
return [];
|
||||
}
|
||||
return fn(...args);
|
||||
}
|
||||
|
||||
export function setMatrixThreadBindingMaxAgeBySessionKey(
|
||||
...args: Parameters<MatrixModule["setMatrixThreadBindingMaxAgeBySessionKey"]>
|
||||
): ReturnType<MatrixModule["setMatrixThreadBindingMaxAgeBySessionKey"]> {
|
||||
const fn = loadMatrixModule()?.setMatrixThreadBindingMaxAgeBySessionKey;
|
||||
if (typeof fn !== "function") {
|
||||
return [];
|
||||
}
|
||||
return fn(...args);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
setMatrixThreadBindingIdleTimeoutBySessionKey,
|
||||
setMatrixThreadBindingMaxAgeBySessionKey,
|
||||
} from "../../../extensions/matrix/runtime-api.js";
|
||||
} from "./runtime-matrix-boundary.js";
|
||||
import type { PluginRuntimeChannel } from "./types-channel.js";
|
||||
|
||||
export function createRuntimeMatrix(): PluginRuntimeChannel["matrix"] {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user