openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
164 lines (163 loc) • 8.26 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { n as validateJsonSchemaValue } from "./schema-validator-CLfGeb79.js";
import { t as isPluginJsonValue } from "./host-hook-json-BdcyDerH.js";
import { c as WRITE_SCOPE } from "./operator-scopes-Dw7Gu2cA.js";
import { t as ErrorCodes } from "./gateway-error-details-w0nAGBBp.js";
import { h as getActivePluginSessionExtensionRegistry } from "./runtime-BL4wZfTq.js";
import { Bn as validatePluginsSessionActionParams, Un as validatePluginsUiDescriptorsParams, Vn as validatePluginsSessionActionResult, Wn as validatePluginsUiDescriptorsResult, _g as formatValidationErrors } from "./src-BiL5aQto.js";
import { r as authorizeOperatorScopesForRequiredScope } from "./method-scopes-K6J_UQGL.js";
import { d as errorShape, f as missingScopeErrorShape } from "./error-codes-Bo8q2D1o.js";
import { a as resolveStoredSessionKeyForAgentStore } from "./session-store-key-8xEjWSNi.js";
import { n as resolveRequestedSessionAgentId } from "./session-request-agent-CCRSEGCB.js";
import { t as assertValidParams } from "./validation-pzrlzFvo.js";
//#region src/gateway/server-methods/plugin-host-hooks.ts
const log = createSubsystemLogger("gateway/plugin-host-hooks");
function formatSessionActionPayloadSchemaErrors(errors) {
return errors.map((error) => error.text).join("; ");
}
/** Ensures plugin action result extension fields stay JSON-compatible on the wire. */
function validatePluginSessionActionJsonFields(result) {
for (const field of [
"result",
"reply",
"details"
]) if (result[field] !== void 0 && !isPluginJsonValue(result[field])) return `plugin session action ${field} must be JSON-compatible`;
}
/** Gateway handlers for plugin-declared Control UI descriptors and session actions. */
const pluginHostHookHandlers = {
"plugins.uiDescriptors": ({ params, respond }) => {
if (!assertValidParams(params, validatePluginsUiDescriptorsParams, "plugins.uiDescriptors", respond)) return;
const result = {
ok: true,
descriptors: (getActivePluginSessionExtensionRegistry()?.controlUiDescriptors ?? []).map((entry) => {
const descriptor = {
id: entry.descriptor.id,
pluginId: entry.pluginId,
pluginName: entry.pluginName,
surface: entry.descriptor.surface,
label: entry.descriptor.label
};
if (entry.descriptor.description !== void 0) descriptor.description = entry.descriptor.description;
if (entry.descriptor.placement !== void 0) descriptor.placement = entry.descriptor.placement;
if (entry.descriptor.schema !== void 0) descriptor.schema = entry.descriptor.schema;
if (entry.descriptor.requiredScopes !== void 0) descriptor.requiredScopes = entry.descriptor.requiredScopes;
return descriptor;
})
};
if (!validatePluginsUiDescriptorsResult(result)) {
log.warn("invalid plugins.uiDescriptors result", { errors: validatePluginsUiDescriptorsResult.errors });
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, `invalid plugins.uiDescriptors result: ${formatValidationErrors(validatePluginsUiDescriptorsResult.errors)}`));
return;
}
respond(true, result, void 0);
},
"plugins.sessionAction": async ({ params, client, respond, context }) => {
if (!assertValidParams(params, validatePluginsSessionActionParams, "plugins.sessionAction", respond)) return;
const pluginId = normalizeOptionalString(params.pluginId);
const actionId = normalizeOptionalString(params.actionId);
const rawSessionKey = normalizeOptionalString(params.sessionKey);
const sessionOwner = rawSessionKey ? resolveRequestedSessionAgentId(context.getRuntimeConfig(), rawSessionKey, normalizeOptionalString(params.agentId)) : void 0;
if (sessionOwner && !sessionOwner.ok) {
respond(false, void 0, sessionOwner.error);
return;
}
const sessionKey = rawSessionKey && sessionOwner?.ok ? resolveStoredSessionKeyForAgentStore({
cfg: context.getRuntimeConfig(),
agentId: sessionOwner.agentId,
sessionKey: rawSessionKey
}) : void 0;
if (!pluginId || !actionId) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "plugins.sessionAction pluginId and actionId must be non-empty"));
return;
}
const registry = getActivePluginSessionExtensionRegistry();
const pluginLoaded = Boolean(registry?.plugins.some((plugin) => plugin.id === pluginId && plugin.status === "loaded"));
const registration = (registry?.sessionActions ?? []).find((entry) => entry.pluginId === pluginId && entry.action.id === actionId);
if (!registration || !pluginLoaded) {
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, `unknown plugin session action: ${pluginId}/${actionId}`));
return;
}
const scopes = Array.isArray(client?.connect.scopes) ? client.connect.scopes : [];
const requiredScopes = registration.action.requiredScopes && registration.action.requiredScopes.length > 0 ? registration.action.requiredScopes : [WRITE_SCOPE];
const missingScope = requiredScopes.find((scope) => !authorizeOperatorScopesForRequiredScope(scope, scopes).allowed);
if (missingScope) {
respond(false, void 0, missingScopeErrorShape({
missingScope,
requiredScopes
}));
return;
}
try {
if (params.payload !== void 0 && !isPluginJsonValue(params.payload)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "plugin session action payload must be JSON-compatible"));
return;
}
if (registration.action.schema !== void 0) {
if (typeof registration.action.schema !== "boolean" && !isRecord(registration.action.schema)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "plugin session action schema must be an object or boolean"));
return;
}
const validation = validateJsonSchemaValue({
schema: registration.action.schema,
cacheKey: `plugin-session-action:${pluginId}:${actionId}`,
value: params.payload
});
if (!validation.ok) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `plugin session action payload does not match schema: ${formatSessionActionPayloadSchemaErrors(validation.errors)}`));
return;
}
}
const result = await registration.action.handler({
pluginId,
actionId,
...sessionKey ? { sessionKey } : {},
...sessionOwner?.ok ? { agentId: sessionOwner.agentId } : {},
...params.payload !== void 0 ? { payload: params.payload } : {},
client: {
...client?.connId ? { connId: client.connId } : {},
scopes: [...scopes]
}
});
if (result !== void 0 && !isRecord(result)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "plugin session action result must be an object"));
return;
}
const wireResult = result?.ok === false ? result : {
ok: true,
...result
};
if (!validatePluginsSessionActionResult(wireResult)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `invalid plugin session action result: ${formatValidationErrors(validatePluginsSessionActionResult.errors)}`));
return;
}
const jsonFieldError = result ? validatePluginSessionActionJsonFields(result) : void 0;
if (jsonFieldError) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, jsonFieldError));
return;
}
if (!wireResult.ok) {
respond(true, {
ok: false,
error: wireResult.error,
...wireResult.code !== void 0 ? { code: wireResult.code } : {},
...wireResult.details !== void 0 ? { details: wireResult.details } : {}
}, void 0);
return;
}
respond(true, {
ok: true,
...wireResult.result !== void 0 ? { result: wireResult.result } : {},
...wireResult.continueAgent !== void 0 ? { continueAgent: wireResult.continueAgent } : {},
...wireResult.reply !== void 0 ? { reply: wireResult.reply } : {}
});
} catch (error) {
log.warn(`plugin session action failed plugin=${pluginId} action=${actionId}: ${formatErrorMessage(error)}`);
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, "plugin session action failed"));
}
}
};
//#endregion
export { pluginHostHookHandlers };