mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
test(plugins): fast-path bundled provider contract loads
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
export { CLAUDE_CLI_BACKEND_ID, isClaudeCliProvider } from "./cli-shared.js";
|
||||
export { buildAnthropicProvider } from "./register.runtime.js";
|
||||
export {
|
||||
createAnthropicBetaHeadersWrapper,
|
||||
createAnthropicFastModeWrapper,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
28
src/plugins/contracts/provider-vitest-registry.ts
Normal file
28
src/plugins/contracts/provider-vitest-registry.ts
Normal 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;
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
Reference in New Issue
Block a user