openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
501 lines (500 loc) • 22.4 kB
JavaScript
import { n as resolveGlobalSingleton } from "./global-singleton-PwlQSEal.js";
import { _ as uniqueStrings } from "./string-normalization-WNUDCpXX.js";
import { s as normalizePluginsConfig } from "./config-state-CikNNz7T.js";
import { i as getPluginModuleLoaderStats } from "./plugin-module-loader-cache-lz0qhu-X.js";
import { n as GATEWAY_CLIENT_IDS, r as GATEWAY_CLIENT_MODES } from "./client-info-CcqJJIan.js";
import "./version-51ymduTn.js";
import { o as resolveSafeTimeoutDelayMs } from "./timeouts-DdTImbzl.js";
import { t as ADMIN_SCOPE } from "./operator-scopes-CS3xdS-V.js";
import "./method-scopes-BtdN3y7s.js";
import { t as applyPluginAutoEnable } from "./plugin-auto-enable-pAtqig-B.js";
import { c as loadOpenClawPlugins, n as clearActivatedPluginRuntimeState } from "./loader-Dp13N9qc.js";
import { I as createEmptyPluginRegistry, S as setActivePluginRegistry } from "./runtime-B2ROiq5l.js";
import { t as getPluginRuntimeGatewayRequestScope } from "./gateway-request-scope-BAEdAUQ6.js";
import { r as createPluginRuntimeLoaderLogger } from "./load-context-DpVH8qxj.js";
import { a as normalizeModelRef, c as parseModelRef } from "./model-selection-normalize-roKDxQ9_.js";
import "./model-selection-CA_65iXi.js";
import { n as loadPluginLookUpTable } from "./plugin-lookup-table-BiEbugjx.js";
import { randomUUID } from "node:crypto";
import { performance } from "node:perf_hooks";
//#region src/gateway/server-plugins.ts
const FALLBACK_GATEWAY_CONTEXT_STATE_KEY = Symbol.for("openclaw.fallbackGatewayContextState");
const getFallbackGatewayContextState = () => resolveGlobalSingleton(FALLBACK_GATEWAY_CONTEXT_STATE_KEY, () => ({
context: void 0,
resolveContext: void 0
}));
function setFallbackGatewayContextResolver(resolveContext) {
const fallbackGatewayContextState = getFallbackGatewayContextState();
fallbackGatewayContextState.context = void 0;
fallbackGatewayContextState.resolveContext = resolveContext;
return () => {
const currentFallbackGatewayContextState = getFallbackGatewayContextState();
if (currentFallbackGatewayContextState.resolveContext === resolveContext) {
currentFallbackGatewayContextState.context = void 0;
currentFallbackGatewayContextState.resolveContext = void 0;
}
};
}
function getFallbackGatewayContext() {
const fallbackGatewayContextState = getFallbackGatewayContextState();
return fallbackGatewayContextState.resolveContext?.() ?? fallbackGatewayContextState.context;
}
function hasInProcessGatewayContext() {
return Boolean(getPluginRuntimeGatewayRequestScope()?.context ?? getFallbackGatewayContext());
}
const PLUGIN_SUBAGENT_POLICY_STATE_KEY = Symbol.for("openclaw.pluginSubagentOverridePolicyState");
const getPluginSubagentPolicyState = () => resolveGlobalSingleton(PLUGIN_SUBAGENT_POLICY_STATE_KEY, () => ({ policies: {} }));
function normalizeAllowedModelRef(raw) {
const trimmed = raw.trim();
if (!trimmed) return null;
if (trimmed === "*") return "*";
const slash = trimmed.indexOf("/");
if (slash <= 0 || slash >= trimmed.length - 1) return null;
const providerRaw = trimmed.slice(0, slash).trim();
const modelRaw = trimmed.slice(slash + 1).trim();
if (!providerRaw || !modelRaw) return null;
const normalized = normalizeModelRef(providerRaw, modelRaw);
return `${normalized.provider}/${normalized.model}`;
}
function setPluginSubagentOverridePolicies(cfg) {
const pluginSubagentPolicyState = getPluginSubagentPolicyState();
const normalized = normalizePluginsConfig(cfg.plugins);
const policies = {};
for (const [pluginId, entry] of Object.entries(normalized.entries)) {
const allowModelOverride = entry.subagent?.allowModelOverride === true;
const hasConfiguredAllowlist = entry.subagent?.hasAllowedModelsConfig === true;
const configuredAllowedModels = entry.subagent?.allowedModels ?? [];
const allowedModels = /* @__PURE__ */ new Set();
let allowAnyModel = false;
for (const modelRef of configuredAllowedModels) {
const normalizedModelRef = normalizeAllowedModelRef(modelRef);
if (!normalizedModelRef) continue;
if (normalizedModelRef === "*") {
allowAnyModel = true;
continue;
}
allowedModels.add(normalizedModelRef);
}
if (!allowModelOverride && !hasConfiguredAllowlist && allowedModels.size === 0 && !allowAnyModel) continue;
policies[pluginId] = {
allowModelOverride,
allowAnyModel,
hasConfiguredAllowlist,
allowedModels
};
}
pluginSubagentPolicyState.policies = policies;
}
function authorizeFallbackModelOverride(params) {
const pluginSubagentPolicyState = getPluginSubagentPolicyState();
const pluginId = params.pluginId?.trim();
if (!pluginId) return {
allowed: false,
reason: "provider/model override requires plugin identity in fallback subagent runs."
};
const policy = pluginSubagentPolicyState.policies[pluginId];
if (!policy?.allowModelOverride) return {
allowed: false,
reason: `plugin "${pluginId}" is not trusted for fallback provider/model override requests. See https://docs.openclaw.ai/plugins/sdk-runtime#api-runtime-subagent and search for: plugins.entries.<id>.subagent.allowModelOverride`
};
if (policy.allowAnyModel) return { allowed: true };
if (policy.hasConfiguredAllowlist && policy.allowedModels.size === 0) return {
allowed: false,
reason: `plugin "${pluginId}" configured subagent.allowedModels, but none of the entries normalized to a valid provider/model target.`
};
if (policy.allowedModels.size === 0) return { allowed: true };
const requestedModelRef = resolveRequestedFallbackModelRef(params);
if (!requestedModelRef) return {
allowed: false,
reason: "fallback provider/model overrides that use an allowlist must resolve to a canonical provider/model target."
};
if (policy.allowedModels.has(requestedModelRef)) return { allowed: true };
return {
allowed: false,
reason: `model override "${requestedModelRef}" is not allowlisted for plugin "${pluginId}".`
};
}
function resolveRequestedFallbackModelRef(params) {
if (params.provider && params.model) {
const normalizedRequest = normalizeModelRef(params.provider, params.model);
return `${normalizedRequest.provider}/${normalizedRequest.model}`;
}
const rawModel = params.model?.trim();
if (!rawModel || !rawModel.includes("/")) return null;
const parsed = parseModelRef(rawModel, "");
if (!parsed?.provider || !parsed.model) return null;
return `${parsed.provider}/${parsed.model}`;
}
function createSyntheticOperatorClient(params) {
const pluginRuntimeOwnerId = typeof params?.pluginRuntimeOwnerId === "string" && params.pluginRuntimeOwnerId.trim() ? params.pluginRuntimeOwnerId.trim() : void 0;
return {
connect: {
minProtocol: 4,
maxProtocol: 4,
client: {
id: GATEWAY_CLIENT_IDS.GATEWAY_CLIENT,
version: "internal",
platform: "node",
mode: GATEWAY_CLIENT_MODES.BACKEND
},
role: "operator",
scopes: params?.scopes ?? ["operator.write"]
},
internal: {
allowModelOverride: params?.allowModelOverride === true,
...params?.agentRunTracking ? { agentRunTracking: params.agentRunTracking } : {},
...params?.scopes?.includes("operator.approvals") ? { approvalRuntime: true } : {},
...pluginRuntimeOwnerId ? { pluginRuntimeOwnerId } : {}
}
};
}
function hasAdminScope(client) {
return (Array.isArray(client?.connect?.scopes) ? client.connect.scopes : []).includes(ADMIN_SCOPE);
}
function canClientUseModelOverride(client) {
return hasAdminScope(client) || client?.internal?.allowModelOverride === true;
}
function mergeGatewayClientInternal(client, internal) {
if (!client || !internal) return client ?? null;
return {
...client,
internal: {
...client.internal,
...internal
}
};
}
function unwrapGatewayMethodDispatchResponse(method, response) {
if (!response.ok) throw new Error(response.error?.message ?? `Gateway method "${method}" failed.`);
return response.payload;
}
function resolveInProcessDispatchTimeoutMs(timeoutMs) {
return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) ? resolveSafeTimeoutDelayMs(timeoutMs) : void 0;
}
function resolveInProcessDispatchDeadlineMs(timeoutMs) {
const safeTimeoutMs = resolveInProcessDispatchTimeoutMs(timeoutMs);
return safeTimeoutMs === void 0 ? void 0 : Date.now() + safeTimeoutMs;
}
function resolveRemainingInProcessDispatchTimeoutMs(deadlineMs) {
return deadlineMs === void 0 ? void 0 : resolveSafeTimeoutDelayMs(deadlineMs - Date.now(), { minMs: 0 });
}
async function waitForInProcessDispatch(method, promise, deadlineMs) {
const remainingTimeoutMs = resolveRemainingInProcessDispatchTimeoutMs(deadlineMs);
if (remainingTimeoutMs === void 0) return await promise;
let timeout;
try {
return await Promise.race([promise, new Promise((_resolve, reject) => {
timeout = setTimeout(() => {
reject(/* @__PURE__ */ new Error(`gateway request timeout for ${method}`));
}, remainingTimeoutMs);
})]);
} finally {
if (timeout) clearTimeout(timeout);
}
}
async function dispatchGatewayMethodInProcessRaw(method, params, options) {
const scope = getPluginRuntimeGatewayRequestScope();
const context = scope?.context ?? getFallbackGatewayContext();
const isWebchatConnect = scope?.isWebchatConnect ?? (() => false);
if (!context) throw new Error(`In-process gateway dispatch requires a gateway request scope (method: ${method}). No scope set and no fallback context available.`);
if (options?.requireScopedClient === true && !scope?.client) throw new Error(`In-process gateway dispatch requires an authenticated plugin request scope (method: ${method}).`);
let firstResponse;
let finalResponse;
let resolveFirstResponse;
let rejectFirstResponse;
let resolveFinalResponse;
let rejectFinalResponse;
let postFirstResponseError;
const firstResponsePromise = new Promise((resolve, reject) => {
resolveFirstResponse = resolve;
rejectFirstResponse = reject;
});
const deadlineMs = resolveInProcessDispatchDeadlineMs(options?.timeoutMs);
const { handleGatewayRequest } = await import("./server-methods-xd1wrn22.js");
const pluginRuntimeOwnerId = typeof options?.pluginRuntimeOwnerId === "string" && options.pluginRuntimeOwnerId.trim() ? options.pluginRuntimeOwnerId.trim() : void 0;
const syntheticClient = createSyntheticOperatorClient({
allowModelOverride: options?.allowSyntheticModelOverride === true,
agentRunTracking: options?.agentRunTracking,
...pluginRuntimeOwnerId ? { pluginRuntimeOwnerId } : {},
scopes: options?.syntheticScopes
});
const scopedClient = mergeGatewayClientInternal(scope?.client, pluginRuntimeOwnerId || options?.agentRunTracking ? {
...options?.agentRunTracking ? { agentRunTracking: options.agentRunTracking } : {},
...pluginRuntimeOwnerId ? { pluginRuntimeOwnerId } : {}
} : void 0);
if (options?.disableSyntheticClient === true && !scopedClient) throw new Error(`In-process gateway dispatch requires a scoped client (method: ${method}).`);
handleGatewayRequest({
req: {
type: "req",
id: `plugin-subagent-${randomUUID()}`,
method,
params
},
client: options?.forceSyntheticClient === true ? syntheticClient : scopedClient ?? (options?.disableSyntheticClient === true ? null : syntheticClient),
isWebchatConnect,
respond: (ok, payload, error, meta) => {
const response = {
ok,
payload,
error,
...meta ? { meta } : {}
};
if (!firstResponse) {
firstResponse = response;
resolveFirstResponse?.(response);
return;
}
if (!finalResponse) {
finalResponse = response;
resolveFinalResponse?.(response);
}
},
context
}).then(() => {
if (!firstResponse) rejectFirstResponse?.(/* @__PURE__ */ new Error(`Gateway method "${method}" completed without a response.`));
}).catch((err) => {
const error = err instanceof Error ? err : new Error(String(err));
if (!firstResponse) {
rejectFirstResponse?.(error);
return;
}
postFirstResponseError = error;
rejectFinalResponse?.(error);
});
firstResponse = await waitForInProcessDispatch(method, firstResponsePromise, deadlineMs);
const firstPayload = firstResponse.payload;
if (options?.expectFinal !== true || firstPayload?.status !== "accepted") return firstResponse;
if (postFirstResponseError) throw postFirstResponseError;
return finalResponse ?? await new Promise((resolve, reject) => {
resolveFinalResponse = resolve;
const timeoutMs = resolveRemainingInProcessDispatchTimeoutMs(deadlineMs);
const timeout = timeoutMs === void 0 ? void 0 : setTimeout(() => {
reject(/* @__PURE__ */ new Error(`gateway request timeout for ${method}`));
}, timeoutMs);
const clearFinalTimeout = () => {
if (timeout) clearTimeout(timeout);
};
rejectFinalResponse = (err) => {
clearFinalTimeout();
reject(err);
};
if (postFirstResponseError) {
rejectFinalResponse(postFirstResponseError);
return;
}
if (finalResponse) {
clearFinalTimeout();
resolve(finalResponse);
return;
}
resolveFinalResponse = (response) => {
clearFinalTimeout();
resolve(response);
};
});
}
async function dispatchGatewayMethod(method, params, options) {
return unwrapGatewayMethodDispatchResponse(method, await dispatchGatewayMethodInProcessRaw(method, params, options));
}
async function dispatchGatewayMethodInProcess(method, params, options) {
return await dispatchGatewayMethod(method, params, options);
}
function createGatewaySubagentRuntime() {
const getSessionMessages = async (params) => {
const payload = await dispatchGatewayMethod("sessions.get", {
key: params.sessionKey,
...params.limit != null && { limit: params.limit }
});
return { messages: Array.isArray(payload?.messages) ? payload.messages : [] };
};
return {
async run(params) {
const scope = getPluginRuntimeGatewayRequestScope();
const pluginId = typeof scope?.pluginId === "string" && scope.pluginId.trim() ? scope.pluginId.trim() : void 0;
const overrideRequested = Boolean(params.provider || params.model);
const hasRequestScopeClient = Boolean(scope?.client);
let allowOverride = hasRequestScopeClient && canClientUseModelOverride(scope?.client ?? null);
let allowSyntheticModelOverride = false;
if (overrideRequested && !allowOverride && !hasRequestScopeClient) {
const fallbackAuth = authorizeFallbackModelOverride({
pluginId: scope?.pluginId,
provider: params.provider,
model: params.model
});
if (!fallbackAuth.allowed) throw new Error(fallbackAuth.reason);
allowOverride = true;
allowSyntheticModelOverride = true;
}
if (overrideRequested && !allowOverride) throw new Error("provider/model override is not authorized for this plugin subagent run.");
const runId = (await dispatchGatewayMethod("agent", {
sessionKey: params.sessionKey,
message: params.message,
deliver: params.deliver ?? false,
...allowOverride && params.provider && { provider: params.provider },
...allowOverride && params.model && { model: params.model },
...params.extraSystemPrompt && { extraSystemPrompt: params.extraSystemPrompt },
...params.lane && { lane: params.lane },
...params.lightContext === true && { bootstrapContextMode: "lightweight" },
idempotencyKey: params.idempotencyKey || randomUUID()
}, {
allowSyntheticModelOverride,
agentRunTracking: "plugin_subagent",
...pluginId ? { pluginRuntimeOwnerId: pluginId } : {}
}))?.runId;
if (typeof runId !== "string" || !runId) throw new Error("Gateway agent method returned an invalid runId.");
return { runId };
},
async waitForRun(params) {
const payload = await dispatchGatewayMethod("agent.wait", {
runId: params.runId,
...params.timeoutMs != null && { timeoutMs: params.timeoutMs }
});
const status = payload?.status;
if (status !== "ok" && status !== "error" && status !== "timeout") throw new Error(`Gateway agent.wait returned unexpected status: ${status}`);
return {
status,
...typeof payload?.error === "string" && payload.error && { error: payload.error }
};
},
getSessionMessages,
async getSession(params) {
return getSessionMessages(params);
},
async deleteSession(params) {
const scope = getPluginRuntimeGatewayRequestScope();
const pluginId = typeof scope?.pluginId === "string" && scope.pluginId.trim() ? scope.pluginId.trim() : void 0;
const pluginOwnedCleanupOptions = pluginId ? {
pluginRuntimeOwnerId: pluginId,
...!hasAdminScope(scope?.client) ? {
forceSyntheticClient: true,
syntheticScopes: [ADMIN_SCOPE]
} : {}
} : void 0;
await dispatchGatewayMethod("sessions.delete", {
key: params.sessionKey,
deleteTranscript: params.deleteTranscript ?? true
}, pluginOwnedCleanupOptions);
}
};
}
function createGatewayNodesRuntime() {
return {
async list(params) {
const payload = await dispatchGatewayMethod("node.list", {});
const nodes = Array.isArray(payload?.nodes) ? payload.nodes : [];
return { nodes: params?.connected === true ? nodes.filter((node) => node !== null && typeof node === "object" && node.connected === true) : nodes };
},
async invoke(params) {
return await dispatchGatewayMethod("node.invoke", {
nodeId: params.nodeId,
command: params.command,
...params.params !== void 0 && { params: params.params },
timeoutMs: params.timeoutMs,
idempotencyKey: params.idempotencyKey || randomUUID()
});
}
};
}
function createGatewayPluginRegistrationLogger(params) {
const logger = createPluginRuntimeLoaderLogger();
if (params?.suppressInfoLogs !== true) return logger;
return {
...logger,
info: (_message) => void 0
};
}
function loadGatewayPlugins(params) {
const started = performance.now();
const activationAutoEnabled = params.activationSourceConfig !== void 0 && params.autoEnabledReasons === void 0 ? applyPluginAutoEnable({
config: params.activationSourceConfig,
env: process.env,
...params.pluginLookUpTable?.manifestRegistry ? { manifestRegistry: params.pluginLookUpTable.manifestRegistry } : {},
discovery: params.pluginLookUpTable?.discovery
}) : void 0;
const autoEnableMs = performance.now() - started;
const autoEnabled = params.activationSourceConfig !== void 0 ? {
config: params.cfg,
changes: activationAutoEnabled?.changes ?? [],
autoEnabledReasons: params.autoEnabledReasons ?? activationAutoEnabled?.autoEnabledReasons ?? {}
} : params.autoEnabledReasons !== void 0 ? {
config: params.cfg,
changes: [],
autoEnabledReasons: params.autoEnabledReasons
} : applyPluginAutoEnable({
config: params.cfg,
env: process.env,
...params.pluginLookUpTable?.manifestRegistry ? { manifestRegistry: params.pluginLookUpTable.manifestRegistry } : {},
discovery: params.pluginLookUpTable?.discovery
});
const resolvedConfigMs = performance.now() - started;
const resolvedConfig = autoEnabled.config;
const pluginIds = params.pluginIds ?? [...(params.pluginLookUpTable ?? loadPluginLookUpTable({
config: resolvedConfig,
activationSourceConfig: params.activationSourceConfig,
workspaceDir: params.workspaceDir,
env: process.env
})).startup.pluginIds];
const pluginIdsMs = performance.now() - started;
if (pluginIds.length === 0) {
clearActivatedPluginRuntimeState();
const pluginRegistry = createEmptyPluginRegistry();
setActivePluginRegistry(pluginRegistry, void 0, "gateway-bindable", params.workspaceDir);
params.startupTrace?.detail("plugins.gateway-load", [
["autoEnableMs", autoEnableMs],
["resolvedConfigMs", resolvedConfigMs],
["pluginIdsMs", pluginIdsMs],
["loadMs", 0],
["pluginIds", "0"],
["pluginCount", 0],
["gatewayHandlerCount", 0]
]);
return {
pluginRegistry,
gatewayMethods: [...params.baseMethods]
};
}
const beforeLoad = performance.now();
const loaderStatsBefore = getPluginModuleLoaderStats();
const pluginRegistry = loadOpenClawPlugins({
config: resolvedConfig,
activationSourceConfig: params.activationSourceConfig ?? params.cfg,
autoEnabledReasons: autoEnabled.autoEnabledReasons,
workspaceDir: params.workspaceDir,
onlyPluginIds: pluginIds,
logger: createGatewayPluginRegistrationLogger({ suppressInfoLogs: params.suppressPluginInfoLogs }),
...params.coreGatewayHandlers !== void 0 && { coreGatewayHandlers: params.coreGatewayHandlers },
...params.coreGatewayMethodNames !== void 0 && { coreGatewayMethodNames: params.coreGatewayMethodNames },
...params.hostServices !== void 0 && { hostServices: params.hostServices },
runtimeOptions: { allowGatewaySubagentBinding: true },
preferSetupRuntimeForChannelPlugins: params.preferSetupRuntimeForChannelPlugins,
preferBuiltPluginArtifacts: true,
...params.startupTrace !== void 0 && { startupTrace: params.startupTrace },
...params.pluginLookUpTable?.manifestRegistry ? { manifestRegistry: params.pluginLookUpTable.manifestRegistry } : {}
});
const loadMs = performance.now() - beforeLoad;
const loaderStatsAfter = getPluginModuleLoaderStats();
const pluginMethods = Object.keys(pluginRegistry.gatewayHandlers);
const gatewayMethods = uniqueStrings([...params.baseMethods, ...pluginMethods]);
params.startupTrace?.detail("plugins.gateway-load", [
["autoEnableMs", autoEnableMs],
["resolvedConfigMs", resolvedConfigMs],
["pluginIdsMs", pluginIdsMs],
["loadMs", loadMs],
["pluginIds", String(pluginIds.length)],
["pluginCount", pluginIds.length],
["gatewayHandlers", String(pluginMethods.length)],
["gatewayHandlerCount", pluginMethods.length],
["loaderCallsCount", loaderStatsAfter.calls - loaderStatsBefore.calls],
["loaderNativeHitsCount", loaderStatsAfter.nativeHits - loaderStatsBefore.nativeHits],
["loaderNativeMissesCount", loaderStatsAfter.nativeMisses - loaderStatsBefore.nativeMisses],
["loaderSourceTransformForcedCount", loaderStatsAfter.sourceTransformForced - loaderStatsBefore.sourceTransformForced],
["loaderSourceTransformFallbacksCount", loaderStatsAfter.sourceTransformFallbacks - loaderStatsBefore.sourceTransformFallbacks],
["loaderTopSourceTransformTargets", loaderStatsAfter.topSourceTransformTargets.slice(0, 3).map((entry) => `${entry.count}:${entry.target}`).join(",")]
]);
return {
pluginRegistry,
gatewayMethods
};
}
//#endregion
export { hasInProcessGatewayContext as a, setPluginSubagentOverridePolicies as c, dispatchGatewayMethodInProcessRaw as i, createGatewaySubagentRuntime as n, loadGatewayPlugins as o, dispatchGatewayMethodInProcess as r, setFallbackGatewayContextResolver as s, createGatewayNodesRuntime as t };