mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:30:43 +00:00
fix(providers): cache targeted runtime hook resolution
This commit is contained in:
@@ -14,6 +14,22 @@ import type {
|
||||
ProviderWrapStreamFnContext,
|
||||
} from "./types.js";
|
||||
|
||||
const providerRuntimePluginCache = new WeakMap<
|
||||
OpenClawConfig,
|
||||
Map<string, ProviderPlugin | null>
|
||||
>();
|
||||
|
||||
type ProviderRuntimePluginLookupParams = {
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
applyAutoEnable?: boolean;
|
||||
bundledProviderAllowlistCompat?: boolean;
|
||||
bundledProviderVitestCompat?: boolean;
|
||||
installBundledRuntimeDeps?: boolean;
|
||||
};
|
||||
|
||||
function matchesProviderId(provider: ProviderPlugin, providerId: string): boolean {
|
||||
const normalized = normalizeProviderId(providerId);
|
||||
if (!normalized) {
|
||||
@@ -27,6 +43,33 @@ function matchesProviderId(provider: ProviderPlugin, providerId: string): boolea
|
||||
);
|
||||
}
|
||||
|
||||
function resolveProviderRuntimePluginCacheKey(params: ProviderRuntimePluginLookupParams): string {
|
||||
return JSON.stringify({
|
||||
provider: normalizeLowercaseStringOrEmpty(params.provider),
|
||||
plugins: params.config?.plugins,
|
||||
models: params.config?.models?.providers,
|
||||
workspaceDir: params.workspaceDir ?? "",
|
||||
applyAutoEnable: params.applyAutoEnable ?? null,
|
||||
bundledProviderAllowlistCompat: params.bundledProviderAllowlistCompat ?? null,
|
||||
bundledProviderVitestCompat: params.bundledProviderVitestCompat ?? null,
|
||||
installBundledRuntimeDeps: params.installBundledRuntimeDeps ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveProviderRuntimePluginCache(
|
||||
params: ProviderRuntimePluginLookupParams,
|
||||
): Map<string, ProviderPlugin | null> | undefined {
|
||||
if (!params.config || (params.env && params.env !== process.env)) {
|
||||
return undefined;
|
||||
}
|
||||
let cache = providerRuntimePluginCache.get(params.config);
|
||||
if (!cache) {
|
||||
cache = new Map();
|
||||
providerRuntimePluginCache.set(params.config, cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
function matchesProviderLiteralId(provider: ProviderPlugin, providerId: string): boolean {
|
||||
const normalized = normalizeLowercaseStringOrEmpty(providerId);
|
||||
return !!normalized && normalizeLowercaseStringOrEmpty(provider.id) === normalized;
|
||||
@@ -51,7 +94,6 @@ export function resolveProviderPluginsForHooks(params: {
|
||||
workspaceDir,
|
||||
env,
|
||||
activate: false,
|
||||
cache: false,
|
||||
applyAutoEnable: params.applyAutoEnable,
|
||||
bundledProviderAllowlistCompat: params.bundledProviderAllowlistCompat ?? true,
|
||||
bundledProviderVitestCompat: params.bundledProviderVitestCompat ?? true,
|
||||
@@ -65,7 +107,6 @@ export function resolveProviderPluginsForHooks(params: {
|
||||
workspaceDir,
|
||||
env,
|
||||
activate: false,
|
||||
cache: false,
|
||||
applyAutoEnable: params.applyAutoEnable,
|
||||
bundledProviderAllowlistCompat: params.bundledProviderAllowlistCompat ?? true,
|
||||
bundledProviderVitestCompat: params.bundledProviderVitestCompat ?? true,
|
||||
@@ -74,21 +115,19 @@ export function resolveProviderPluginsForHooks(params: {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
export function resolveProviderRuntimePlugin(params: {
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
applyAutoEnable?: boolean;
|
||||
bundledProviderAllowlistCompat?: boolean;
|
||||
bundledProviderVitestCompat?: boolean;
|
||||
installBundledRuntimeDeps?: boolean;
|
||||
}): ProviderPlugin | undefined {
|
||||
export function resolveProviderRuntimePlugin(
|
||||
params: ProviderRuntimePluginLookupParams,
|
||||
): ProviderPlugin | undefined {
|
||||
const cache = resolveProviderRuntimePluginCache(params);
|
||||
const cacheKey = cache ? resolveProviderRuntimePluginCacheKey(params) : "";
|
||||
if (cache?.has(cacheKey)) {
|
||||
return cache.get(cacheKey) ?? undefined;
|
||||
}
|
||||
const apiOwnerHint = resolveProviderConfigApiOwnerHint({
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
});
|
||||
return resolveProviderPluginsForHooks({
|
||||
const plugin = resolveProviderPluginsForHooks({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState(),
|
||||
env: params.env,
|
||||
@@ -105,6 +144,8 @@ export function resolveProviderRuntimePlugin(params: {
|
||||
}
|
||||
return matchesProviderId(plugin, params.provider);
|
||||
});
|
||||
cache?.set(cacheKey, plugin ?? null);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
export function resolveProviderHookPlugin(params: {
|
||||
@@ -140,7 +181,9 @@ export function resolveProviderExtraParamsForTransport(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderExtraParamsForTransportContext;
|
||||
}) {
|
||||
return resolveProviderHookPlugin(params)?.extraParamsForTransport?.(params.context) ?? undefined;
|
||||
return (
|
||||
resolveProviderRuntimePlugin(params)?.extraParamsForTransport?.(params.context) ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveProviderAuthProfileId(params: {
|
||||
@@ -150,7 +193,7 @@ export function resolveProviderAuthProfileId(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderResolveAuthProfileIdContext;
|
||||
}): string | undefined {
|
||||
const resolved = resolveProviderHookPlugin(params)?.resolveAuthProfileId?.(params.context);
|
||||
const resolved = resolveProviderRuntimePlugin(params)?.resolveAuthProfileId?.(params.context);
|
||||
return typeof resolved === "string" && resolved.trim() ? resolved.trim() : undefined;
|
||||
}
|
||||
|
||||
@@ -171,5 +214,5 @@ export function wrapProviderStreamFn(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderWrapStreamFnContext;
|
||||
}) {
|
||||
return resolveProviderHookPlugin(params)?.wrapStreamFn?.(params.context) ?? undefined;
|
||||
return resolveProviderRuntimePlugin(params)?.wrapStreamFn?.(params.context) ?? undefined;
|
||||
}
|
||||
|
||||
@@ -1114,6 +1114,69 @@ describe("provider-runtime", () => {
|
||||
).toBe(wrappedStreamFn);
|
||||
});
|
||||
|
||||
it("does not run broad provider-hook scans for reasoning output mode", () => {
|
||||
resolvePluginProvidersMock.mockImplementation((params) => {
|
||||
if (params.providerRefs?.includes("mock-openai")) {
|
||||
return [];
|
||||
}
|
||||
throw new Error("unexpected broad provider hook scan");
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveProviderReasoningOutputModeWithPlugin({
|
||||
provider: "mock-openai",
|
||||
context: createDemoResolvedModelContext({
|
||||
provider: "mock-openai",
|
||||
modelId: "gpt-5.5",
|
||||
}),
|
||||
}),
|
||||
).toBeUndefined();
|
||||
expect(resolvePluginProvidersMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("does not run broad provider-hook scans for auth profile selection", () => {
|
||||
resolvePluginProvidersMock.mockImplementation((params) => {
|
||||
if (params.providerRefs?.includes("mock-openai")) {
|
||||
return [];
|
||||
}
|
||||
throw new Error("unexpected broad provider hook scan");
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveProviderAuthProfileId({
|
||||
provider: "mock-openai",
|
||||
context: createDemoRuntimeContext({
|
||||
provider: "mock-openai",
|
||||
modelId: "gpt-5.5",
|
||||
profileOrder: [],
|
||||
authStore: { version: 1, profiles: {}, order: {} },
|
||||
}),
|
||||
}),
|
||||
).toBeUndefined();
|
||||
expect(resolvePluginProvidersMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("does not run broad provider-hook scans for transport extra params", () => {
|
||||
resolvePluginProvidersMock.mockImplementation((params) => {
|
||||
if (params.providerRefs?.includes("mock-openai")) {
|
||||
return [];
|
||||
}
|
||||
throw new Error("unexpected broad provider hook scan");
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveProviderExtraParamsForTransport({
|
||||
provider: "mock-openai",
|
||||
context: createDemoRuntimeContext({
|
||||
provider: "mock-openai",
|
||||
modelId: "gpt-5.5",
|
||||
extraParams: {},
|
||||
}),
|
||||
}),
|
||||
).toBeUndefined();
|
||||
expect(resolvePluginProvidersMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("normalizes transport hooks without needing provider ownership", () => {
|
||||
resolvePluginProvidersMock.mockReturnValue([
|
||||
{
|
||||
@@ -1969,7 +2032,6 @@ describe("provider-runtime", () => {
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["openai"],
|
||||
activate: false,
|
||||
cache: false,
|
||||
}),
|
||||
);
|
||||
expect(resolveCatalogHookProviderPluginIdsMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -525,7 +525,7 @@ export function resolveProviderReplayPolicyWithPlugin(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderReplayPolicyContext;
|
||||
}): ProviderReplayPolicy | undefined {
|
||||
return resolveProviderHookPlugin(params)?.buildReplayPolicy?.(params.context) ?? undefined;
|
||||
return resolveProviderRuntimePlugin(params)?.buildReplayPolicy?.(params.context) ?? undefined;
|
||||
}
|
||||
|
||||
export async function sanitizeProviderReplayHistoryWithPlugin(params: {
|
||||
@@ -535,7 +535,7 @@ export async function sanitizeProviderReplayHistoryWithPlugin(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderSanitizeReplayHistoryContext;
|
||||
}) {
|
||||
return await resolveProviderHookPlugin(params)?.sanitizeReplayHistory?.(params.context);
|
||||
return await resolveProviderRuntimePlugin(params)?.sanitizeReplayHistory?.(params.context);
|
||||
}
|
||||
|
||||
export async function validateProviderReplayTurnsWithPlugin(params: {
|
||||
@@ -545,7 +545,7 @@ export async function validateProviderReplayTurnsWithPlugin(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderValidateReplayTurnsContext;
|
||||
}) {
|
||||
return await resolveProviderHookPlugin(params)?.validateReplayTurns?.(params.context);
|
||||
return await resolveProviderRuntimePlugin(params)?.validateReplayTurns?.(params.context);
|
||||
}
|
||||
|
||||
export function normalizeProviderToolSchemasWithPlugin(params: {
|
||||
@@ -555,7 +555,7 @@ export function normalizeProviderToolSchemasWithPlugin(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderNormalizeToolSchemasContext;
|
||||
}) {
|
||||
return resolveProviderHookPlugin(params)?.normalizeToolSchemas?.(params.context) ?? undefined;
|
||||
return resolveProviderRuntimePlugin(params)?.normalizeToolSchemas?.(params.context) ?? undefined;
|
||||
}
|
||||
|
||||
export function inspectProviderToolSchemasWithPlugin(params: {
|
||||
@@ -565,7 +565,7 @@ export function inspectProviderToolSchemasWithPlugin(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderNormalizeToolSchemasContext;
|
||||
}) {
|
||||
return resolveProviderHookPlugin(params)?.inspectToolSchemas?.(params.context) ?? undefined;
|
||||
return resolveProviderRuntimePlugin(params)?.inspectToolSchemas?.(params.context) ?? undefined;
|
||||
}
|
||||
|
||||
export function resolveProviderReasoningOutputModeWithPlugin(params: {
|
||||
@@ -575,7 +575,7 @@ export function resolveProviderReasoningOutputModeWithPlugin(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderReasoningOutputModeContext;
|
||||
}): ProviderReasoningOutputMode | undefined {
|
||||
const mode = resolveProviderHookPlugin(params)?.resolveReasoningOutputMode?.(params.context);
|
||||
const mode = resolveProviderRuntimePlugin(params)?.resolveReasoningOutputMode?.(params.context);
|
||||
return mode === "native" || mode === "tagged" ? mode : undefined;
|
||||
}
|
||||
|
||||
@@ -597,7 +597,7 @@ export function resolveProviderTransportTurnStateWithPlugin(params: {
|
||||
context: ProviderResolveTransportTurnStateContext;
|
||||
}): ProviderTransportTurnState | undefined {
|
||||
return (
|
||||
resolveProviderHookPlugin(params)?.resolveTransportTurnState?.(params.context) ?? undefined
|
||||
resolveProviderRuntimePlugin(params)?.resolveTransportTurnState?.(params.context) ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
@@ -609,7 +609,8 @@ export function resolveProviderWebSocketSessionPolicyWithPlugin(params: {
|
||||
context: ProviderResolveWebSocketSessionPolicyContext;
|
||||
}): ProviderWebSocketSessionPolicy | undefined {
|
||||
return (
|
||||
resolveProviderHookPlugin(params)?.resolveWebSocketSessionPolicy?.(params.context) ?? undefined
|
||||
resolveProviderRuntimePlugin(params)?.resolveWebSocketSessionPolicy?.(params.context) ??
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user