fix(providers): cache targeted runtime hook resolution

This commit is contained in:
Ayaan Zaidi
2026-05-01 07:34:08 +05:30
parent 5a69832833
commit 354084b1b3
3 changed files with 131 additions and 25 deletions

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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
);
}