perf(plugins): split provider hook runtime seam

This commit is contained in:
Vincent Koc
2026-04-14 16:59:30 +01:00
parent 905b18530f
commit a7436c8b4a
9 changed files with 215 additions and 226 deletions

View File

@@ -140,7 +140,7 @@ import {
} from "./pi-embedded-runner/openai-stream-wrappers.js";
type WrapProviderStreamFnParams = Parameters<
typeof import("../plugins/provider-runtime.js").wrapProviderStreamFn
typeof import("../plugins/provider-hook-runtime.js").wrapProviderStreamFn
>[0];
function createTestOpenAIProviderWrapper(

View File

@@ -4,15 +4,6 @@ import { isOpenRouterAnthropicModelRef } from "./anthropic-family-cache-semantic
import { __testing as extraParamsTesting, applyExtraParamsToAgent } from "./extra-params.js";
import { resolveCacheRetention } from "./prompt-cache-retention.js";
vi.mock("../../plugins/provider-runtime.js", () => ({
prepareProviderExtraParams: ({
context,
}: {
context: { extraParams: Record<string, unknown> };
}) => context.extraParams,
wrapProviderStreamFn: () => undefined,
}));
function applyAndExpectWrapped(params: {
cfg?: Parameters<typeof applyExtraParamsToAgent>[1];
extraParamsOverride?: Parameters<typeof applyExtraParamsToAgent>[4];

View File

@@ -4,15 +4,6 @@ import { createPiAiStreamSimpleMock } from "../../../test/helpers/agents/pi-ai-s
import { __testing as extraParamsTesting } from "./extra-params.js";
import { runExtraParamsCase } from "./extra-params.test-support.js";
vi.mock("../../plugins/provider-runtime.js", () => ({
prepareProviderExtraParams: ({
context,
}: {
context: { extraParams: Record<string, unknown> };
}) => context.extraParams,
wrapProviderStreamFn: () => undefined,
}));
vi.mock("@mariozechner/pi-ai", async () =>
createPiAiStreamSimpleMock(() =>
vi.importActual<typeof import("@mariozechner/pi-ai")>("@mariozechner/pi-ai"),

View File

@@ -3,15 +3,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { __testing as extraParamsTesting } from "./extra-params.js";
import { runExtraParamsCase } from "./extra-params.test-support.js";
vi.mock("../../plugins/provider-runtime.js", () => ({
prepareProviderExtraParams: ({
context,
}: {
context: { extraParams: Record<string, unknown> };
}) => context.extraParams,
wrapProviderStreamFn: () => undefined,
}));
vi.mock("@mariozechner/pi-ai", async () => {
const original =
await vi.importActual<typeof import("@mariozechner/pi-ai")>("@mariozechner/pi-ai");

View File

@@ -1,19 +1,9 @@
import type { StreamFn } from "@mariozechner/pi-agent-core";
import type { Context, Model, SimpleStreamOptions } from "@mariozechner/pi-ai";
import { vi } from "vitest";
import type { ThinkLevel } from "../../auto-reply/thinking.shared.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { __testing as extraParamsTesting, applyExtraParamsToAgent } from "./extra-params.js";
vi.mock("../../plugins/provider-runtime.js", () => ({
prepareProviderExtraParams: ({
context,
}: {
context: { extraParams: Record<string, unknown> };
}) => context.extraParams,
wrapProviderStreamFn: () => undefined,
}));
export type ExtraParamsCapture<TPayload extends Record<string, unknown>> = {
headers?: Record<string, string>;
options?: SimpleStreamOptions;

View File

@@ -4,11 +4,11 @@ import { streamSimple } from "@mariozechner/pi-ai";
import type { SettingsManager } from "@mariozechner/pi-coding-agent";
import type { ThinkLevel } from "../../auto-reply/thinking.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
import {
prepareProviderExtraParams as prepareProviderExtraParamsRuntime,
wrapProviderStreamFn as wrapProviderStreamFnRuntime,
} from "../../plugins/provider-runtime.js";
} from "../../plugins/provider-hook-runtime.js";
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
import { createGoogleThinkingPayloadWrapper } from "./google-stream-wrappers.js";
import { log } from "./logger.js";
import { createMinimaxThinkingDisabledWrapper } from "./minimax-stream-wrappers.js";

View File

@@ -1,7 +1,7 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveProviderRuntimePlugin } from "../plugins/provider-hook-runtime.js";
import { shouldPreserveThinkingBlocks } from "../plugins/provider-replay-helpers.js";
import type { ProviderRuntimeModel } from "../plugins/provider-runtime-model.types.js";
import { resolveProviderRuntimePlugin } from "../plugins/provider-runtime.js";
import type { ProviderReplayPolicy } from "../plugins/types.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import { normalizeProviderId } from "./model-selection.js";

View File

@@ -0,0 +1,193 @@
import { normalizeProviderId } from "../agents/provider-id.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizePluginIdScope, serializePluginIdScope } from "./plugin-scope.js";
import { isPluginProvidersLoadInFlight, resolvePluginProviders } from "./providers.runtime.js";
import { resolvePluginCacheInputs } from "./roots.js";
import { getActivePluginRegistryWorkspaceDirFromState } from "./runtime-state.js";
import type {
ProviderPlugin,
ProviderPrepareExtraParamsContext,
ProviderWrapStreamFnContext,
} from "./types.js";
function matchesProviderId(provider: ProviderPlugin, providerId: string): boolean {
const normalized = normalizeProviderId(providerId);
if (!normalized) {
return false;
}
if (normalizeProviderId(provider.id) === normalized) {
return true;
}
return [...(provider.aliases ?? []), ...(provider.hookAliases ?? [])].some(
(alias) => normalizeProviderId(alias) === normalized,
);
}
let cachedHookProvidersWithoutConfig = new WeakMap<
NodeJS.ProcessEnv,
Map<string, ProviderPlugin[]>
>();
let cachedHookProvidersByConfig = new WeakMap<
OpenClawConfig,
WeakMap<NodeJS.ProcessEnv, Map<string, ProviderPlugin[]>>
>();
function resolveHookProviderCacheBucket(params: {
config?: OpenClawConfig;
env: NodeJS.ProcessEnv;
}) {
if (!params.config) {
let bucket = cachedHookProvidersWithoutConfig.get(params.env);
if (!bucket) {
bucket = new Map<string, ProviderPlugin[]>();
cachedHookProvidersWithoutConfig.set(params.env, bucket);
}
return bucket;
}
let envBuckets = cachedHookProvidersByConfig.get(params.config);
if (!envBuckets) {
envBuckets = new WeakMap<NodeJS.ProcessEnv, Map<string, ProviderPlugin[]>>();
cachedHookProvidersByConfig.set(params.config, envBuckets);
}
let bucket = envBuckets.get(params.env);
if (!bucket) {
bucket = new Map<string, ProviderPlugin[]>();
envBuckets.set(params.env, bucket);
}
return bucket;
}
function buildHookProviderCacheKey(params: {
config?: OpenClawConfig;
workspaceDir?: string;
onlyPluginIds?: string[];
providerRefs?: string[];
env?: NodeJS.ProcessEnv;
}) {
const { roots } = resolvePluginCacheInputs({
workspaceDir: params.workspaceDir,
env: params.env,
});
const onlyPluginIds = normalizePluginIdScope(params.onlyPluginIds);
return `${roots.workspace ?? ""}::${roots.global}::${roots.stock ?? ""}::${JSON.stringify(params.config ?? null)}::${serializePluginIdScope(onlyPluginIds)}::${JSON.stringify(params.providerRefs ?? [])}`;
}
export function clearProviderRuntimeHookCache(): void {
cachedHookProvidersWithoutConfig = new WeakMap<
NodeJS.ProcessEnv,
Map<string, ProviderPlugin[]>
>();
cachedHookProvidersByConfig = new WeakMap<
OpenClawConfig,
WeakMap<NodeJS.ProcessEnv, Map<string, ProviderPlugin[]>>
>();
}
export function resetProviderRuntimeHookCacheForTest(): void {
clearProviderRuntimeHookCache();
}
export const __testing = {
buildHookProviderCacheKey,
} as const;
export function resolveProviderPluginsForHooks(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
onlyPluginIds?: string[];
providerRefs?: string[];
}): ProviderPlugin[] {
const env = params.env ?? process.env;
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
const cacheBucket = resolveHookProviderCacheBucket({
config: params.config,
env,
});
const cacheKey = buildHookProviderCacheKey({
config: params.config,
workspaceDir,
onlyPluginIds: params.onlyPluginIds,
providerRefs: params.providerRefs,
env,
});
const cached = cacheBucket.get(cacheKey);
if (cached) {
return cached;
}
if (
isPluginProvidersLoadInFlight({
...params,
workspaceDir,
env,
activate: false,
cache: false,
bundledProviderAllowlistCompat: true,
bundledProviderVitestCompat: true,
})
) {
return [];
}
const resolved = resolvePluginProviders({
...params,
workspaceDir,
env,
activate: false,
cache: false,
bundledProviderAllowlistCompat: true,
bundledProviderVitestCompat: true,
});
cacheBucket.set(cacheKey, resolved);
return resolved;
}
export function resolveProviderRuntimePlugin(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): ProviderPlugin | undefined {
return resolveProviderPluginsForHooks({
config: params.config,
workspaceDir: params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState(),
env: params.env,
providerRefs: [params.provider],
}).find((plugin) => matchesProviderId(plugin, params.provider));
}
export function resolveProviderHookPlugin(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): ProviderPlugin | undefined {
return (
resolveProviderRuntimePlugin(params) ??
resolveProviderPluginsForHooks({
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env,
}).find((candidate) => matchesProviderId(candidate, params.provider))
);
}
export function prepareProviderExtraParams(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
context: ProviderPrepareExtraParamsContext;
}) {
return resolveProviderRuntimePlugin(params)?.prepareExtraParams?.(params.context) ?? undefined;
}
export function wrapProviderStreamFn(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
context: ProviderWrapStreamFnContext;
}) {
return resolveProviderHookPlugin(params)?.wrapStreamFn?.(params.context) ?? undefined;
}

View File

@@ -3,17 +3,23 @@ import {
applyPluginTextReplacements,
mergePluginTextTransforms,
} from "../agents/plugin-text-transforms.js";
import { normalizeProviderId } from "../agents/provider-id.js";
import type { ProviderSystemPromptContribution } from "../agents/system-prompt-contribution.js";
import type { ModelProviderConfig } from "../config/types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import { normalizePluginIdScope, serializePluginIdScope } from "./plugin-scope.js";
import {
__testing as providerHookRuntimeTesting,
clearProviderRuntimeHookCache,
prepareProviderExtraParams,
resetProviderRuntimeHookCacheForTest,
resolveProviderHookPlugin,
resolveProviderPluginsForHooks,
resolveProviderRuntimePlugin,
wrapProviderStreamFn,
} from "./provider-hook-runtime.js";
import { resolveBundledProviderPolicySurface } from "./provider-public-artifacts.js";
import type { ProviderRuntimeModel } from "./provider-runtime-model.types.js";
import { resolveCatalogHookProviderPluginIds } from "./providers.js";
import { isPluginProvidersLoadInFlight, resolvePluginProviders } from "./providers.runtime.js";
import { resolvePluginCacheInputs } from "./roots.js";
import { getActivePluginRegistryWorkspaceDirFromState } from "./runtime-state.js";
import { resolveRuntimeTextTransforms } from "./text-transforms.runtime.js";
import type {
@@ -41,7 +47,6 @@ import type {
ProviderNormalizeResolvedModelContext,
ProviderNormalizeTransportContext,
ProviderModernModelPolicyContext,
ProviderPrepareExtraParamsContext,
ProviderPrepareDynamicModelContext,
ProviderPreferRuntimeResolvedModelContext,
ProviderResolveExternalAuthProfilesContext,
@@ -61,142 +66,20 @@ import type {
ProviderTransportTurnState,
ProviderValidateReplayTurnsContext,
ProviderWebSocketSessionPolicy,
ProviderWrapStreamFnContext,
PluginTextTransforms,
} from "./types.js";
function matchesProviderId(provider: ProviderPlugin, providerId: string): boolean {
const normalized = normalizeProviderId(providerId);
if (!normalized) {
return false;
}
if (normalizeProviderId(provider.id) === normalized) {
return true;
}
return [...(provider.aliases ?? []), ...(provider.hookAliases ?? [])].some(
(alias) => normalizeProviderId(alias) === normalized,
);
}
let cachedHookProvidersWithoutConfig = new WeakMap<
NodeJS.ProcessEnv,
Map<string, ProviderPlugin[]>
>();
let cachedHookProvidersByConfig = new WeakMap<
OpenClawConfig,
WeakMap<NodeJS.ProcessEnv, Map<string, ProviderPlugin[]>>
>();
function resolveHookProviderCacheBucket(params: {
config?: OpenClawConfig;
env: NodeJS.ProcessEnv;
}) {
if (!params.config) {
let bucket = cachedHookProvidersWithoutConfig.get(params.env);
if (!bucket) {
bucket = new Map<string, ProviderPlugin[]>();
cachedHookProvidersWithoutConfig.set(params.env, bucket);
}
return bucket;
}
let envBuckets = cachedHookProvidersByConfig.get(params.config);
if (!envBuckets) {
envBuckets = new WeakMap<NodeJS.ProcessEnv, Map<string, ProviderPlugin[]>>();
cachedHookProvidersByConfig.set(params.config, envBuckets);
}
let bucket = envBuckets.get(params.env);
if (!bucket) {
bucket = new Map<string, ProviderPlugin[]>();
envBuckets.set(params.env, bucket);
}
return bucket;
}
function buildHookProviderCacheKey(params: {
config?: OpenClawConfig;
workspaceDir?: string;
onlyPluginIds?: string[];
providerRefs?: string[];
env?: NodeJS.ProcessEnv;
}) {
const { roots } = resolvePluginCacheInputs({
workspaceDir: params.workspaceDir,
env: params.env,
});
const onlyPluginIds = normalizePluginIdScope(params.onlyPluginIds);
return `${roots.workspace ?? ""}::${roots.global}::${roots.stock ?? ""}::${JSON.stringify(params.config ?? null)}::${serializePluginIdScope(onlyPluginIds)}::${JSON.stringify(params.providerRefs ?? [])}`;
}
export function clearProviderRuntimeHookCache(): void {
cachedHookProvidersWithoutConfig = new WeakMap<
NodeJS.ProcessEnv,
Map<string, ProviderPlugin[]>
>();
cachedHookProvidersByConfig = new WeakMap<
OpenClawConfig,
WeakMap<NodeJS.ProcessEnv, Map<string, ProviderPlugin[]>>
>();
}
export function resetProviderRuntimeHookCacheForTest(): void {
clearProviderRuntimeHookCache();
}
export {
clearProviderRuntimeHookCache,
prepareProviderExtraParams,
resetProviderRuntimeHookCacheForTest,
resolveProviderRuntimePlugin,
wrapProviderStreamFn,
};
export const __testing = {
buildHookProviderCacheKey,
...providerHookRuntimeTesting,
} as const;
function resolveProviderPluginsForHooks(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
onlyPluginIds?: string[];
providerRefs?: string[];
}): ProviderPlugin[] {
const env = params.env ?? process.env;
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
const cacheBucket = resolveHookProviderCacheBucket({
config: params.config,
env,
});
const cacheKey = buildHookProviderCacheKey({
config: params.config,
workspaceDir,
onlyPluginIds: params.onlyPluginIds,
providerRefs: params.providerRefs,
env,
});
const cached = cacheBucket.get(cacheKey);
if (cached) {
return cached;
}
if (
isPluginProvidersLoadInFlight({
...params,
workspaceDir,
env,
activate: false,
cache: false,
bundledProviderAllowlistCompat: true,
bundledProviderVitestCompat: true,
})
) {
return [];
}
const resolved = resolvePluginProviders({
...params,
workspaceDir,
env,
activate: false,
cache: false,
bundledProviderAllowlistCompat: true,
bundledProviderVitestCompat: true,
});
cacheBucket.set(cacheKey, resolved);
return resolved;
}
function resolveProviderPluginsForCatalogHooks(params: {
config?: OpenClawConfig;
workspaceDir?: string;
@@ -218,20 +101,6 @@ function resolveProviderPluginsForCatalogHooks(params: {
});
}
export function resolveProviderRuntimePlugin(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): ProviderPlugin | undefined {
return resolveProviderPluginsForHooks({
config: params.config,
workspaceDir: params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState(),
env: params.env,
providerRefs: [params.provider],
}).find((plugin) => matchesProviderId(plugin, params.provider));
}
export function runProviderDynamicModel(params: {
provider: string;
config?: OpenClawConfig;
@@ -433,22 +302,6 @@ export function applyProviderResolvedTransportWithPlugin(params: {
};
}
function resolveProviderHookPlugin(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
}): ProviderPlugin | undefined {
return (
resolveProviderRuntimePlugin(params) ??
resolveProviderPluginsForHooks({
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env,
}).find((candidate) => matchesProviderId(candidate, params.provider))
);
}
export function normalizeProviderModelIdWithPlugin(params: {
provider: string;
config?: OpenClawConfig;
@@ -612,16 +465,6 @@ export function resolveProviderReasoningOutputModeWithPlugin(params: {
return mode === "native" || mode === "tagged" ? mode : undefined;
}
export function prepareProviderExtraParams(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
context: ProviderPrepareExtraParamsContext;
}) {
return resolveProviderRuntimePlugin(params)?.prepareExtraParams?.(params.context) ?? undefined;
}
export function resolveProviderStreamFn(params: {
provider: string;
config?: OpenClawConfig;
@@ -632,16 +475,6 @@ export function resolveProviderStreamFn(params: {
return resolveProviderRuntimePlugin(params)?.createStreamFn?.(params.context) ?? undefined;
}
export function wrapProviderStreamFn(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
context: ProviderWrapStreamFnContext;
}) {
return resolveProviderHookPlugin(params)?.wrapStreamFn?.(params.context) ?? undefined;
}
export function resolveProviderTransportTurnStateWithPlugin(params: {
provider: string;
config?: OpenClawConfig;