test(plugins): fast-path bundled provider contract loads

This commit is contained in:
Vincent Koc
2026-04-17 14:05:35 -07:00
parent 420b1da82f
commit 48c4a026dd
9 changed files with 128 additions and 18 deletions

View File

@@ -1,4 +1,5 @@
export { CLAUDE_CLI_BACKEND_ID, isClaudeCliProvider } from "./cli-shared.js";
export { buildAnthropicProvider } from "./register.runtime.js";
export {
createAnthropicBetaHeadersWrapper,
createAnthropicFastModeWrapper,

View File

@@ -18,7 +18,10 @@ import {
upsertAuthProfile,
validateAnthropicSetupToken,
} from "openclaw/plugin-sdk/provider-auth";
import { cloneFirstTemplateModel } from "openclaw/plugin-sdk/provider-model-shared";
import {
cloneFirstTemplateModel,
type ProviderPlugin,
} from "openclaw/plugin-sdk/provider-model-shared";
import { fetchClaudeUsage } from "openclaw/plugin-sdk/provider-usage";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import * as claudeCliAuth from "./cli-auth-seam.js";
@@ -395,11 +398,10 @@ async function runAnthropicCliMigrationNonInteractive(ctx: {
};
}
export function registerAnthropicPlugin(api: OpenClawPluginApi): void {
export function buildAnthropicProvider(): ProviderPlugin {
const providerId = "anthropic";
const defaultAnthropicModel = DEFAULT_ANTHROPIC_MODEL;
api.registerCliBackend(buildAnthropicCliBackend());
api.registerProvider({
return {
id: providerId,
label: "Anthropic",
docsPath: "/providers/models",
@@ -505,6 +507,11 @@ export function registerAnthropicPlugin(api: OpenClawPluginApi): void {
store: ctx.store,
profileId: ctx.profileId,
}),
});
};
}
export function registerAnthropicPlugin(api: OpenClawPluginApi): void {
api.registerCliBackend(buildAnthropicCliBackend());
api.registerProvider(buildAnthropicProvider());
api.registerMediaUnderstandingProvider(anthropicMediaUnderstandingProvider);
}

View File

@@ -24,6 +24,8 @@ export {
shouldNormalizeGoogleGenerativeAiProviderConfig,
shouldNormalizeGoogleProviderConfig,
} from "./provider-policy.js";
export { buildGoogleGeminiCliProvider } from "./gemini-cli-provider.js";
export { buildGoogleProvider } from "./provider-registration.js";
export function parseGeminiAuth(apiKey: string): { headers: Record<string, string> } {
const parsed = apiKey.startsWith("{") ? parseGoogleOauthApiKey(apiKey) : null;

View File

@@ -4,6 +4,7 @@ import type {
ProviderFetchUsageSnapshotContext,
} from "openclaw/plugin-sdk/plugin-entry";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth-result";
import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderToolCompatFamilyHooks } from "openclaw/plugin-sdk/provider-tools";
import { fetchGeminiUsage } from "openclaw/plugin-sdk/provider-usage";
import { formatGoogleOauthApiKey, parseGoogleUsageToken } from "./oauth-token-shared.js";
@@ -29,8 +30,8 @@ async function fetchGeminiCliUsage(ctx: ProviderFetchUsageSnapshotContext) {
return await fetchGeminiUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn, PROVIDER_ID);
}
export function registerGoogleGeminiCliProvider(api: OpenClawPluginApi) {
api.registerProvider({
export function buildGoogleGeminiCliProvider(): ProviderPlugin {
return {
id: PROVIDER_ID,
label: PROVIDER_LABEL,
docsPath: "/providers/models",
@@ -128,5 +129,9 @@ export function registerGoogleGeminiCliProvider(api: OpenClawPluginApi) {
};
},
fetchUsageSnapshot: async (ctx) => await fetchGeminiCliUsage(ctx),
});
};
}
export function registerGoogleGeminiCliProvider(api: OpenClawPluginApi) {
api.registerProvider(buildGoogleGeminiCliProvider());
}

View File

@@ -1,5 +1,6 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-model-shared";
import {
GOOGLE_GEMINI_DEFAULT_MODEL,
applyGoogleGeminiModelDefault,
@@ -10,8 +11,8 @@ import {
import { GOOGLE_GEMINI_PROVIDER_HOOKS } from "./provider-hooks.js";
import { isModernGoogleModel, resolveGoogleGeminiForwardCompatModel } from "./provider-models.js";
export function registerGoogleProvider(api: OpenClawPluginApi) {
api.registerProvider({
export function buildGoogleProvider(): ProviderPlugin {
return {
id: "google",
label: "Google AI Studio",
docsPath: "/providers/models",
@@ -50,5 +51,9 @@ export function registerGoogleProvider(api: OpenClawPluginApi) {
}),
...GOOGLE_GEMINI_PROVIDER_HOOKS,
isModernModelRef: ({ modelId }) => isModernGoogleModel(modelId),
});
};
}
export function registerGoogleProvider(api: OpenClawPluginApi) {
api.registerProvider(buildGoogleProvider());
}

View File

@@ -10,6 +10,7 @@ export {
OPENAI_DEFAULT_TTS_VOICE,
} from "./default-models.js";
export { buildOpenAICodexProvider } from "./openai-codex-catalog.js";
export { buildOpenAICodexProviderPlugin } from "./openai-codex-provider.js";
export { buildOpenAIProvider } from "./openai-provider.js";
export { buildOpenAIRealtimeTranscriptionProvider } from "./realtime-transcription-provider.js";
export { buildOpenAIRealtimeVoiceProvider } from "./realtime-voice-provider.js";

View File

@@ -0,0 +1,28 @@
import { buildAnthropicProvider } from "../../../extensions/anthropic/api.js";
import {
buildGoogleGeminiCliProvider,
buildGoogleProvider,
} from "../../../extensions/google/api.js";
import {
buildOpenAICodexProviderPlugin,
buildOpenAIProvider,
} from "../../../extensions/openai/api.js";
import type { ProviderPlugin } from "../types.js";
export type ProviderContractEntry = {
pluginId: string;
provider: ProviderPlugin;
};
let providerContractRegistryCache: ProviderContractEntry[] | null = null;
export function loadVitestProviderContractRegistry(): ProviderContractEntry[] {
providerContractRegistryCache ??= [
{ pluginId: "anthropic", provider: buildAnthropicProvider() },
{ pluginId: "google", provider: buildGoogleProvider() },
{ pluginId: "google", provider: buildGoogleGeminiCliProvider() },
{ pluginId: "openai", provider: buildOpenAIProvider() },
{ pluginId: "openai", provider: buildOpenAICodexProviderPlugin() },
];
return providerContractRegistryCache;
}

View File

@@ -187,6 +187,47 @@ describe("plugin contract registry scoped retries", () => {
expect(loadBundledCapabilityRuntimeRegistry).toHaveBeenCalledTimes(1);
});
it("uses provider public artifacts before falling back to the bundled runtime registry", async () => {
const loadBundledCapabilityRuntimeRegistry = vi.fn(() => {
throw new Error("provider contract vitest fast path should not hit bundled runtime registry");
});
const loadVitestProviderContractRegistry = vi.fn(() => [
{
pluginId: "openai",
provider: {
id: "openai",
label: "OpenAI",
docsPath: "/providers/openai",
auth: [{ id: "api-key", label: "API key", run: async () => ({ profiles: [] }) }],
} as ProviderPlugin,
},
{
pluginId: "openai",
provider: {
id: "openai-codex",
label: "OpenAI Codex",
docsPath: "/providers/openai",
auth: [{ id: "oauth", label: "OAuth", run: async () => ({ profiles: [] }) }],
} as ProviderPlugin,
},
]);
vi.doMock("../bundled-capability-runtime.js", () => ({
loadBundledCapabilityRuntimeRegistry,
}));
vi.doMock("./provider-vitest-registry.js", () => ({
loadVitestProviderContractRegistry,
}));
const { resolveProviderContractProvidersForPluginIds } = await import("./registry.js");
expect(
resolveProviderContractProvidersForPluginIds(["openai"]).map((provider) => provider.id),
).toEqual(["openai", "openai-codex"]);
expect(loadVitestProviderContractRegistry).toHaveBeenCalledTimes(1);
expect(loadBundledCapabilityRuntimeRegistry).not.toHaveBeenCalled();
});
it("retries web fetch provider loads after a transient plugin-scoped runtime error", async () => {
const loadBundledCapabilityRuntimeRegistry = vi
.fn()

View File

@@ -17,6 +17,7 @@ import type {
WebSearchProviderPlugin,
} from "../types.js";
import { BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS } from "./inventory/bundled-capability-metadata.js";
import { loadVitestProviderContractRegistry } from "./provider-vitest-registry.js";
import { uniqueStrings } from "./shared.js";
import {
loadVitestImageGenerationProviderContractRegistry,
@@ -314,6 +315,16 @@ function loadProviderContractEntriesForPluginId(pluginId: string): ProviderContr
return cached;
}
if (process.env.VITEST) {
const vitestEntries = loadVitestProviderContractRegistry().filter(
(entry) => entry.pluginId === pluginId,
);
if (vitestEntries.length > 0) {
cache.set(pluginId, vitestEntries);
return vitestEntries;
}
}
try {
providerContractLoadError = undefined;
const entries = loadScopedCapabilityRuntimeRegistryEntries({
@@ -344,13 +355,22 @@ function loadProviderContractRegistry(): ProviderContractEntry[] {
if (!providerContractRegistryCache) {
try {
providerContractLoadError = undefined;
providerContractRegistryCache = loadBundledCapabilityRuntimeRegistry({
pluginIds: resolveBundledProviderContractPluginIds(),
pluginSdkResolution: "dist",
}).providers.map((entry) => ({
pluginId: entry.pluginId,
provider: entry.provider,
}));
const vitestEntries = process.env.VITEST ? loadVitestProviderContractRegistry() : [];
const coveredPluginIds = new Set(vitestEntries.map((entry) => entry.pluginId));
const remainingPluginIds = resolveBundledProviderContractPluginIds().filter(
(pluginId) => !coveredPluginIds.has(pluginId),
);
const runtimeEntries =
remainingPluginIds.length > 0
? loadBundledCapabilityRuntimeRegistry({
pluginIds: remainingPluginIds,
pluginSdkResolution: "dist",
}).providers.map((entry) => ({
pluginId: entry.pluginId,
provider: entry.provider,
}))
: [];
providerContractRegistryCache = [...vitestEntries, ...runtimeEntries];
} catch (error) {
providerContractLoadError = error instanceof Error ? error : new Error(String(error));
providerContractRegistryCache = [];