mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
test(plugins): trim contract helper runtime boot
This commit is contained in:
@@ -1,28 +1 @@
|
||||
import {
|
||||
createWebSearchProviderContractFields,
|
||||
type WebSearchProviderPlugin,
|
||||
} from "openclaw/plugin-sdk/provider-web-search-config-contract";
|
||||
|
||||
export function createGeminiWebSearchProvider(): WebSearchProviderPlugin {
|
||||
const credentialPath = "plugins.entries.google.config.webSearch.apiKey";
|
||||
|
||||
return {
|
||||
id: "gemini",
|
||||
label: "Gemini (Google Search)",
|
||||
hint: "Requires Google Gemini API key · Google Search grounding",
|
||||
onboardingScopes: ["text-inference"],
|
||||
credentialLabel: "Google Gemini API key",
|
||||
envVars: ["GEMINI_API_KEY"],
|
||||
placeholder: "AIza...",
|
||||
signupUrl: "https://aistudio.google.com/apikey",
|
||||
docsUrl: "https://docs.openclaw.ai/tools/web",
|
||||
autoDetectOrder: 20,
|
||||
credentialPath,
|
||||
...createWebSearchProviderContractFields({
|
||||
credentialPath,
|
||||
searchCredential: { type: "scoped", scopeId: "gemini" },
|
||||
configuredCredential: { pluginId: "google" },
|
||||
}),
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
export { createGeminiWebSearchProvider } from "./src/gemini-web-search-provider.js";
|
||||
|
||||
@@ -228,6 +228,42 @@ describe("plugin contract registry scoped retries", () => {
|
||||
expect(loadBundledCapabilityRuntimeRegistry).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses web search public artifacts before falling back to the bundled runtime registry", async () => {
|
||||
const loadBundledCapabilityRuntimeRegistry = vi.fn(() => {
|
||||
throw new Error(
|
||||
"web search contract vitest fast path should not hit bundled runtime registry",
|
||||
);
|
||||
});
|
||||
const loadVitestWebSearchProviderContractRegistry = vi.fn(() => [
|
||||
{
|
||||
pluginId: "google",
|
||||
provider: {
|
||||
id: "gemini",
|
||||
label: "Gemini",
|
||||
credentialPath: "plugins.entries.google.config.webSearch.apiKey",
|
||||
} as WebSearchProviderPlugin,
|
||||
credentialValue: "AIzaSyDUMMY",
|
||||
},
|
||||
]);
|
||||
|
||||
vi.doMock("../bundled-capability-runtime.js", () => ({
|
||||
loadBundledCapabilityRuntimeRegistry,
|
||||
}));
|
||||
vi.doMock("./web-provider-vitest-registry.js", () => ({
|
||||
loadVitestWebSearchProviderContractRegistry,
|
||||
}));
|
||||
|
||||
const { resolveWebSearchProviderContractEntriesForPluginId } = await import("./registry.js");
|
||||
|
||||
expect(
|
||||
resolveWebSearchProviderContractEntriesForPluginId("google").map(
|
||||
(entry) => entry.provider.id,
|
||||
),
|
||||
).toEqual(["gemini"]);
|
||||
expect(loadVitestWebSearchProviderContractRegistry).toHaveBeenCalledTimes(1);
|
||||
expect(loadBundledCapabilityRuntimeRegistry).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("retries web fetch provider loads after a transient plugin-scoped runtime error", async () => {
|
||||
const loadBundledCapabilityRuntimeRegistry = vi
|
||||
.fn()
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
loadVitestSpeechProviderContractRegistry,
|
||||
loadVitestVideoGenerationProviderContractRegistry,
|
||||
} from "./speech-vitest-registry.js";
|
||||
import { loadVitestWebSearchProviderContractRegistry } from "./web-provider-vitest-registry.js";
|
||||
|
||||
type BundledCapabilityRuntimeRegistry = ReturnType<typeof loadBundledCapabilityRuntimeRegistry>;
|
||||
type CapabilityContractEntry<T> = {
|
||||
@@ -474,15 +475,23 @@ export function resolveWebFetchProviderContractEntriesForPluginId(
|
||||
|
||||
function loadWebSearchProviderContractRegistry(): WebSearchProviderContractEntry[] {
|
||||
if (!webSearchProviderContractRegistryCache) {
|
||||
const registry = loadBundledCapabilityRuntimeRegistry({
|
||||
pluginIds: resolveBundledManifestContractPluginIds("webSearchProviders"),
|
||||
pluginSdkResolution: "dist",
|
||||
});
|
||||
webSearchProviderContractRegistryCache = registry.webSearchProviders.map((entry) => ({
|
||||
pluginId: entry.pluginId,
|
||||
provider: entry.provider,
|
||||
credentialValue: resolveWebSearchCredentialValue(entry.provider),
|
||||
}));
|
||||
const vitestEntries = process.env.VITEST ? loadVitestWebSearchProviderContractRegistry() : [];
|
||||
const coveredPluginIds = new Set(vitestEntries.map((entry) => entry.pluginId));
|
||||
const remainingPluginIds = resolveBundledManifestContractPluginIds("webSearchProviders").filter(
|
||||
(pluginId) => !coveredPluginIds.has(pluginId),
|
||||
);
|
||||
const runtimeEntries =
|
||||
remainingPluginIds.length > 0
|
||||
? loadBundledCapabilityRuntimeRegistry({
|
||||
pluginIds: remainingPluginIds,
|
||||
pluginSdkResolution: "dist",
|
||||
}).webSearchProviders.map((entry) => ({
|
||||
pluginId: entry.pluginId,
|
||||
provider: entry.provider,
|
||||
credentialValue: resolveWebSearchCredentialValue(entry.provider),
|
||||
}))
|
||||
: [];
|
||||
webSearchProviderContractRegistryCache = [...vitestEntries, ...runtimeEntries];
|
||||
}
|
||||
return webSearchProviderContractRegistryCache;
|
||||
}
|
||||
@@ -503,6 +512,16 @@ export function resolveWebSearchProviderContractEntriesForPluginId(
|
||||
return cached;
|
||||
}
|
||||
|
||||
if (process.env.VITEST) {
|
||||
const vitestEntries = loadVitestWebSearchProviderContractRegistry().filter(
|
||||
(entry) => entry.pluginId === pluginId,
|
||||
);
|
||||
if (vitestEntries.length > 0) {
|
||||
cache.set(pluginId, vitestEntries);
|
||||
return vitestEntries;
|
||||
}
|
||||
}
|
||||
|
||||
const entries = loadScopedCapabilityRuntimeRegistryEntries({
|
||||
pluginId,
|
||||
capabilityLabel: "web search provider",
|
||||
|
||||
21
src/plugins/contracts/web-provider-vitest-registry.ts
Normal file
21
src/plugins/contracts/web-provider-vitest-registry.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { createGeminiWebSearchProvider } from "../../../extensions/google/web-search-contract-api.js";
|
||||
import type { WebSearchProviderPlugin } from "../types.js";
|
||||
|
||||
export type WebSearchProviderContractEntry = {
|
||||
pluginId: string;
|
||||
provider: WebSearchProviderPlugin;
|
||||
credentialValue: unknown;
|
||||
};
|
||||
|
||||
let webSearchProviderContractRegistryCache: WebSearchProviderContractEntry[] | null = null;
|
||||
|
||||
export function loadVitestWebSearchProviderContractRegistry(): WebSearchProviderContractEntry[] {
|
||||
webSearchProviderContractRegistryCache ??= [
|
||||
{
|
||||
pluginId: "google",
|
||||
provider: createGeminiWebSearchProvider(),
|
||||
credentialValue: "AIzaSyDUMMY",
|
||||
},
|
||||
];
|
||||
return webSearchProviderContractRegistryCache;
|
||||
}
|
||||
@@ -150,3 +150,18 @@ export function resolveRelativeBundledPluginPublicModuleId(params: {
|
||||
.replaceAll(path.sep, "/");
|
||||
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
||||
}
|
||||
|
||||
export function resolveRelativeExtensionPublicModuleId(params: {
|
||||
fromModuleUrl: string;
|
||||
dirName: string;
|
||||
artifactBasename: string;
|
||||
}): string {
|
||||
const fromFilePath = fileURLToPath(params.fromModuleUrl);
|
||||
const targetPath = resolveVitestSourceModulePath(
|
||||
path.resolve(OPENCLAW_PACKAGE_ROOT, "extensions", params.dirName, params.artifactBasename),
|
||||
);
|
||||
const relativePath = path
|
||||
.relative(path.dirname(fromFilePath), targetPath)
|
||||
.replaceAll(path.sep, "/");
|
||||
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "../../../src/plugin-sdk/facade-loader.js";
|
||||
import { __testing as pluginLoaderTesting } from "../../../src/plugins/loader.js";
|
||||
import { createEmptyPluginRegistry } from "../../../src/plugins/registry-empty.js";
|
||||
import { setActivePluginRegistry } from "../../../src/plugins/runtime.js";
|
||||
import type { SpeechProviderPlugin } from "../../../src/plugins/types.js";
|
||||
import { resolveRelativeExtensionPublicModuleId } from "../../../src/test-utils/bundled-plugin-public-surface.js";
|
||||
import { withEnv } from "../../../src/test-utils/env.js";
|
||||
import { summarizeText as summarizeTextCore } from "../../../src/tts/tts-core.js";
|
||||
import type { ResolvedTtsConfig } from "../../../src/tts/tts-types.js";
|
||||
|
||||
type TtsRuntimeModule = typeof import("../../../src/tts/tts.js");
|
||||
|
||||
const speechCoreRuntimeApiModuleId = resolveRelativeExtensionPublicModuleId({
|
||||
fromModuleUrl: import.meta.url,
|
||||
dirName: "speech-core",
|
||||
artifactBasename: "runtime-api.js",
|
||||
});
|
||||
|
||||
let ttsRuntime: TtsRuntimeModule;
|
||||
let ttsRuntimePromise: Promise<TtsRuntimeModule> | null = null;
|
||||
let ttsRuntimeInitialized = false;
|
||||
@@ -389,12 +395,7 @@ function buildTestGoogleSpeechProvider(): SpeechProviderPlugin {
|
||||
}
|
||||
|
||||
async function loadTtsRuntime(): Promise<TtsRuntimeModule> {
|
||||
ttsRuntimePromise ??= Promise.resolve(
|
||||
loadBundledPluginPublicSurfaceModuleSync<TtsRuntimeModule>({
|
||||
dirName: "speech-core",
|
||||
artifactBasename: "runtime-api.js",
|
||||
}),
|
||||
);
|
||||
ttsRuntimePromise ??= import(speechCoreRuntimeApiModuleId) as Promise<TtsRuntimeModule>;
|
||||
return await ttsRuntimePromise;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user