Files
openclaw/src/plugins/provider-hook-runtime.ts
Peter Steinberger bb46b79d3c refactor: internalize OpenClaw agent runtime (#85341)
* refactor: extract agent core package

Introduce packages/agent-core as the OpenClaw-owned home for reusable agent loop, harness, session, prompt, and runtime dependency contracts.

* refactor: extract shared llm runtime

Move provider model registries, stream wrappers, OAuth helpers, and LLM utilities into src/llm with plugin-sdk barrels instead of depending on the old embedded runtime layout.

* refactor: remove pi runtime internals

Rename remaining Pi-shaped agent surfaces to OpenClaw agent runtime names, delete obsolete Pi docs and package graph checks, and add the third-party notice for incorporated code.

* refactor: tighten agent session runtime

Make agent-core/runtime dependencies explicit, consolidate compaction and session transcript helpers, and move model/session helpers behind OpenClaw-owned contracts.

* refactor: remove static model and pi auth paths

Drop static model catalogs and Pi auth bridges, move model/provider facts to manifest-owned runtime contracts, and harden internal embedded-agent utilities.

* refactor: remove legacy provider compat paths

* docs: remove agent parity notes

* fix: skip provider wildcard metadata parsing

* refactor: share session extension sdk loading

* refactor: inline acpx proxy error formatter

* refactor: fold edit recovery into edit tool

* fix: accept extension batch separator

* test: align startup provider plugin expectations

* fix: restore provider-scoped release discovery

* test: align static asset packaging expectations

* fix: run static provider catalogs during scoped discovery

* fix: add provider entry catalogs for scoped live discovery

* fix: load lightweight provider catalog entries

* fix: refresh provider-scoped plugin metadata

* fix: keep provider catalog entries on release live path

* fix: keep static manifest models in release live checks

* fix: harden release model discovery

* fix: reduce OpenAI live cache probe reasoning

* fix: disable OpenAI cache probe reasoning

* ci: extend OpenAI gateway live timeout

* fix: extend live gateway model budget

* fix: stabilize release validation regressions

* fix: honor provider aliases in model rows

* fix: stabilize release validation lanes

* fix: stabilize release memory qa

* ci: stabilize release validation lanes

* ci: prefer ipv4 for live docker node calls

* fix: restore shared tool-call stream wrapper

* ci: remove legacy pi test shard alias

* fix: clean up embedded agent test drift

* fix: stabilize runtime alias status

* fix: clean up embedded agent ci drift

* fix: restore release ci invariants

* fix: clean up post-rebase runtime drift

* fix: restore release ci checks

* fix: restore release ci after rebase

* fix: remove stale pi runtime path

* test: align compaction runtime expectations

* test: update plugin prerelease expectations

* fix: handle claude live tool approvals

* fix: stabilize release validation gates

* fix: finish agent runtime import

* test: finish post-rebase agent runtime mocks

* fix: keep codex compaction native

* fix: stabilize codex app-server hook tests

* test: isolate codex diagnostic active run

* test: remove codex diagnostic completion race

# Conflicts:
#	extensions/codex/src/app-server/run-attempt.test.ts

* ci: fix full release manifest performance run id

* refactor: narrow llm plugin sdk boundary

* chore: drop generated google boundary stamps

* fix: repair rebase fallout

* fix: clean up rebased runtime references

* fix: decode codex jwt payloads as base64url

* fix: preserve shipped pi runtime alias

* fix: add scoped sdk virtual modules

* fix: decode llm codex oauth jwt as base64url

* fix: avoid stale vertex adc negative cache

* fix: harden tool arg decoding and codeql path

* fix: keep vertex adc negative checks live

* refactor: consolidate codex jwt and edit helpers

* fix: await codex oauth node runtime imports

* fix: preserve sdk tool and notice contracts

* fix: preserve shipped compat config boundaries

* fix: align codex oauth callback host

* fix: terminate agent-core loop streams on failure

* fix: keep codex oauth callback alive during fallback

* ci: include session tools in critical codeql scans

* fix: keep Cloudflare Anthropic provider auth header

* docs: redirect legacy pi runtime pages

* fix: honor bundled web provider compat discovery

* fix: protect session output spill files

* fix: keep legacy agent dir env blocked

* fix: contain auto-discovered skill symlinks

* fix: harden agent core sdk proxy surfaces

* fix: restore approval reaction sdk compat

* fix: keep live docker runs bounded

* fix: keep codex oauth redirect host aligned

* fix: resolve post-rebase agent runtime drift

* fix: redact anthropic oauth parse failures

* fix: preserve responses strict tool shaping

* fix: repair agent runtime rebase cleanup

* docs: redirect retired parity pages

* fix: bound auto-discovered resources to roots

* fix: repair post-rebase agent test drift

* fix: preserve bundled provider allowlist migration

* fix: preserve manifest-owned provider aliases

* fix: declare photon image dependency

* fix: keep provider headers out of proxy body

* fix: preserve shipped env aliases

* fix: refresh control ui i18n generated state

* fix: quote read fallback paths

* fix: preview edits through configured backend

* test: satisfy core test typecheck

* fix: preserve ZAI usage auth fallback

* test: repair codex diagnostic test

* fix: repair agent runtime rebase drift

* test: finish embedded runner import rename

* fix: repair agent runtime rebase integrations

* test: align compaction oauth fallback expectations

* fix: allow sdk-auth session models

* fix: update doctor tool schema import

* fix: preserve bedrock plugin region

* fix: stream harmony-like prose immediately

* ci: include session runtime in codeql shards

* fix: repair latest rebase integrations

* fix: honor explicit codex websocket transport

* fix: keep openai-compatible credentials provider-scoped

* fix: refresh sdk api baseline after rebase

* fix: route cli runtime aliases through openclaw harness

* test: rename stale harness mock expectation

* test: rename embedded agent overflow calls

* test: clean embedded auth test wording

* test: use openclaw stream types in deepinfra cache test

* fix: refresh sdk api baseline on latest main

* fix: honor bundled discovery compat allowlists

* fix: refresh sdk api baseline after latest rebase

* fix: remove stale rebase imports

* test: rename stale model catalog mock

* test: mock renamed doctor runtime modules

* fix: map canonical kimi env auth

* fix: use internal model registry in bench script

* fix: migrate deepinfra provider catalog entry

* fix: enforce builtin tool suppression

* fix: route compaction auth and proxy payloads safely

* refactor: prune unused llm registry leftovers

* test: update codex hooks session import

* test: fix model picker ci coverage

* test: align model picker auth mock types
2026-05-27 19:24:04 +01:00

435 lines
14 KiB
TypeScript

import { resolveModelCatalogScope } from "../agents/model-catalog-scope.js";
import { normalizeProviderId } from "../agents/provider-id.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../shared/string-coerce.js";
import { getLoadedRuntimePluginRegistry } from "./active-runtime-registry.js";
import {
PluginLruCache,
resolveConfigScopedRuntimeCacheValue,
type ConfigScopedRuntimeCache,
} from "./plugin-cache-primitives.js";
import { resolvePluginControlPlaneFingerprint } from "./plugin-control-plane-context.js";
import { resolveProviderConfigApiOwnerHint } from "./provider-config-owner.js";
import { isPluginProvidersLoadInFlight, resolvePluginProviders } from "./providers.runtime.js";
import type { PluginRegistry } from "./registry-types.js";
import {
getActivePluginRegistryWorkspaceDirFromState,
getPluginRegistryState,
} from "./runtime-state.js";
import type {
ProviderPlugin,
ProviderExtraParamsForTransportContext,
ProviderPrepareExtraParamsContext,
ProviderResolveAuthProfileIdContext,
ProviderFollowupFallbackRouteContext,
ProviderFollowupFallbackRouteResult,
ProviderWrapStreamFnContext,
} from "./types.js";
let providerRuntimePluginCache: ConfigScopedRuntimeCache<ProviderPlugin | null> = new WeakMap();
const defaultProviderRuntimePluginCache = new PluginLruCache<ProviderPlugin | null>(128);
const PREPARED_PROVIDER_RUNTIME_SURFACES = ["channel"] as const;
export type ProviderRuntimePluginLookupParams = {
provider: string;
modelId?: string | null;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
applyAutoEnable?: boolean;
bundledProviderVitestCompat?: boolean;
};
export type ProviderRuntimePluginHandle = ProviderRuntimePluginLookupParams & {
plugin?: ProviderPlugin;
};
export type ProviderRuntimePluginHandleParams = ProviderRuntimePluginLookupParams & {
runtimeHandle?: ProviderRuntimePluginHandle;
};
export function clearProviderRuntimePluginCacheForTest(): void {
providerRuntimePluginCache = new WeakMap();
defaultProviderRuntimePluginCache.clear();
}
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,
);
}
function resolveProviderRuntimePluginCacheKey(
params: ProviderRuntimePluginLookupParams,
registryState = getPluginRegistryState(),
): string {
return JSON.stringify({
provider: normalizeLowercaseStringOrEmpty(params.provider),
modelId: resolveProviderRuntimeLookupModelId(params) ?? null,
pluginControlPlane: resolvePluginControlPlaneFingerprint({
config: params.config,
env: params.env,
workspaceDir: params.workspaceDir,
}),
plugins: params.config?.plugins,
models: params.config?.models?.providers,
workspaceDir: params.workspaceDir ?? "",
applyAutoEnable: params.applyAutoEnable ?? null,
bundledProviderVitestCompat: params.bundledProviderVitestCompat ?? null,
pluginRegistryKey: registryState?.key ?? null,
pluginRegistryVersion: registryState?.activeVersion ?? null,
});
}
function matchesProviderLiteralId(provider: ProviderPlugin, providerId: string): boolean {
const normalized = normalizeLowercaseStringOrEmpty(providerId);
return !!normalized && normalizeLowercaseStringOrEmpty(provider.id) === normalized;
}
function resolveProviderRuntimeLookupModelId(
params: ProviderRuntimePluginLookupParams & { context?: { modelId?: unknown } },
): string | undefined {
return normalizeOptionalString(
params.modelId ??
(typeof params.context?.modelId === "string" ? params.context.modelId : undefined),
);
}
function resolveProviderRuntimeLookupScope(
params: ProviderRuntimePluginLookupParams,
apiOwnerHint?: string,
): {
providerRefs: string[];
modelRefs?: string[];
} {
const providerRefs = apiOwnerHint ? [params.provider, apiOwnerHint] : [params.provider];
const modelId = resolveProviderRuntimeLookupModelId(params);
if (!modelId) {
return { providerRefs };
}
return {
providerRefs,
modelRefs: resolveModelCatalogScope({
cfg: params.config,
provider: params.provider,
model: modelId,
}).modelRefs,
};
}
function findProviderRuntimePluginInLoadedRegistries(params: {
lookup: ProviderRuntimePluginLookupParams;
apiOwnerHint?: string;
}): ProviderPlugin | undefined {
const activeRegistry = getLoadedRuntimePluginRegistry({
env: params.lookup.env,
workspaceDir: params.lookup.workspaceDir,
});
const activePlugin = activeRegistry
? findProviderRuntimePluginInRegistry({
registry: activeRegistry,
provider: params.lookup.provider,
apiOwnerHint: params.apiOwnerHint,
})
: undefined;
if (activePlugin) {
return activePlugin;
}
for (const surface of PREPARED_PROVIDER_RUNTIME_SURFACES) {
const registry = getLoadedRuntimePluginRegistry({
env: params.lookup.env,
workspaceDir: params.lookup.workspaceDir,
surface,
});
const plugin = registry
? findProviderRuntimePluginInRegistry({
registry,
provider: params.lookup.provider,
apiOwnerHint: params.apiOwnerHint,
})
: undefined;
if (plugin) {
return plugin;
}
}
return undefined;
}
function findProviderRuntimePluginInRegistry(params: {
registry: PluginRegistry;
provider: string;
apiOwnerHint?: string;
}): ProviderPlugin | undefined {
return params.registry.providers
.map((entry) => Object.assign({}, entry.provider, { pluginId: entry.pluginId }))
.find((plugin) => {
if (params.apiOwnerHint) {
return (
matchesProviderLiteralId(plugin, params.provider) ||
matchesProviderId(plugin, params.apiOwnerHint)
);
}
return matchesProviderId(plugin, params.provider);
});
}
export function resolveProviderPluginsForHooks(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
onlyPluginIds?: string[];
providerRefs?: readonly string[];
modelRefs?: readonly string[];
applyAutoEnable?: boolean;
bundledProviderVitestCompat?: boolean;
}): ProviderPlugin[] {
const env = params.env ?? process.env;
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
if (
isPluginProvidersLoadInFlight({
...params,
workspaceDir,
env,
activate: false,
applyAutoEnable: params.applyAutoEnable,
bundledProviderVitestCompat: params.bundledProviderVitestCompat ?? true,
})
) {
return [];
}
const resolved = resolvePluginProviders({
...params,
workspaceDir,
env,
activate: false,
applyAutoEnable: params.applyAutoEnable,
bundledProviderVitestCompat: params.bundledProviderVitestCompat ?? true,
});
return resolved;
}
export function resolveProviderRuntimePlugin(
params: ProviderRuntimePluginLookupParams,
): ProviderPlugin | undefined {
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
const env = params.env ?? process.env;
const lookup = { ...params, workspaceDir, env };
const apiOwnerHint = resolveProviderConfigApiOwnerHint({
provider: params.provider,
config: params.config,
});
const providerRefs = apiOwnerHint ? [params.provider, apiOwnerHint] : [params.provider];
const loadedPlugin = findProviderRuntimePluginInLoadedRegistries({
lookup,
apiOwnerHint,
});
if (loadedPlugin) {
return loadedPlugin;
}
if (
isPluginProvidersLoadInFlight({
...params,
workspaceDir,
env,
providerRefs,
activate: false,
applyAutoEnable: params.applyAutoEnable,
bundledProviderVitestCompat: params.bundledProviderVitestCompat ?? true,
})
) {
return undefined;
}
const cacheConfig = params.env && params.env !== process.env ? undefined : params.config;
const registryState = getPluginRegistryState();
const cacheKey = resolveProviderRuntimePluginCacheKey(lookup, registryState);
const load = () => {
const lookupScope = resolveProviderRuntimeLookupScope(params, apiOwnerHint);
return (
resolveProviderPluginsForHooks({
config: params.config,
workspaceDir,
env,
providerRefs: lookupScope.providerRefs,
modelRefs: lookupScope.modelRefs,
applyAutoEnable: params.applyAutoEnable,
bundledProviderVitestCompat: params.bundledProviderVitestCompat,
}).find((plugin) => {
if (apiOwnerHint) {
return (
matchesProviderLiteralId(plugin, params.provider) ||
matchesProviderId(plugin, apiOwnerHint)
);
}
return matchesProviderId(plugin, params.provider);
}) ?? null
);
};
const plugin = cacheConfig
? resolveConfigScopedRuntimeCacheValue({
cache: providerRuntimePluginCache,
config: cacheConfig,
key: cacheKey,
load,
})
: !registryState?.key
? load()
: (() => {
const cached = defaultProviderRuntimePluginCache.getResult(cacheKey);
if (cached.hit) {
return cached.value;
}
const loaded = load();
defaultProviderRuntimePluginCache.set(cacheKey, loaded);
return loaded;
})();
return plugin ?? undefined;
}
export function resolveLoadedProviderRuntimePlugin(
params: ProviderRuntimePluginLookupParams,
): ProviderPlugin | undefined {
const apiOwnerHint = resolveProviderConfigApiOwnerHint({
provider: params.provider,
config: params.config,
});
return findProviderRuntimePluginInLoadedRegistries({
lookup: params,
apiOwnerHint,
});
}
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 resolveProviderRuntimePluginHandle(
params: ProviderRuntimePluginLookupParams,
): ProviderRuntimePluginHandle {
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
const env = params.env;
const runtimePlugin = resolveProviderRuntimePlugin({
...params,
workspaceDir,
env,
});
return {
...params,
workspaceDir,
env,
plugin: runtimePlugin,
};
}
export function ensureProviderRuntimePluginHandle(
params: ProviderRuntimePluginHandleParams,
): ProviderRuntimePluginHandle {
const modelId = resolveProviderRuntimeLookupModelId(params);
if (
!params.runtimeHandle ||
(modelId && !params.runtimeHandle.plugin && params.runtimeHandle.modelId !== modelId)
) {
return resolveProviderRuntimePluginHandle({
provider: params.provider,
modelId,
config: params.config ?? params.runtimeHandle?.config,
workspaceDir: params.workspaceDir ?? params.runtimeHandle?.workspaceDir,
env: params.env ?? params.runtimeHandle?.env,
applyAutoEnable: params.runtimeHandle?.applyAutoEnable,
bundledProviderVitestCompat: params.runtimeHandle?.bundledProviderVitestCompat,
});
}
return params.runtimeHandle;
}
export function prepareProviderExtraParams(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
runtimeHandle?: ProviderRuntimePluginHandle;
context: ProviderPrepareExtraParamsContext;
}) {
return (
ensureProviderRuntimePluginHandle(params).plugin?.prepareExtraParams?.(params.context) ??
undefined
);
}
export function resolveProviderExtraParamsForTransport(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
runtimeHandle?: ProviderRuntimePluginHandle;
context: ProviderExtraParamsForTransportContext;
}) {
return (
ensureProviderRuntimePluginHandle(params).plugin?.extraParamsForTransport?.(params.context) ??
undefined
);
}
export function resolveProviderAuthProfileId(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
runtimeHandle?: ProviderRuntimePluginHandle;
context: ProviderResolveAuthProfileIdContext;
}): string | undefined {
const resolved = ensureProviderRuntimePluginHandle(params).plugin?.resolveAuthProfileId?.(
params.context,
);
return typeof resolved === "string" && resolved.trim() ? resolved.trim() : undefined;
}
export function resolveProviderFollowupFallbackRoute(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
runtimeHandle?: ProviderRuntimePluginHandle;
context: ProviderFollowupFallbackRouteContext;
}): ProviderFollowupFallbackRouteResult | undefined {
return (
ensureProviderRuntimePluginHandle(params).plugin?.followupFallbackRoute?.(params.context) ??
undefined
);
}
export function wrapProviderStreamFn(params: {
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
runtimeHandle?: ProviderRuntimePluginHandle;
context: ProviderWrapStreamFnContext;
}) {
return (
ensureProviderRuntimePluginHandle(params).plugin?.wrapStreamFn?.(params.context) ?? undefined
);
}