openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
239 lines (238 loc) • 10 kB
JavaScript
import "./operator-scopes-D-CL26h0.mjs";
import { ei as validateSessionPortalCloseParams, fr as validatePortalCloseParams, mr as validatePortalOpenParams, ni as validateSessionPortalOpenParams, pr as validatePortalListParams, ti as validateSessionPortalListParams } from "./src-D2gvuSyP.mjs";
import { t as ErrorCodes } from "./gateway-error-details-DIefe7Zy.mjs";
import { f as errorShape } from "./error-codes-DI20vDEB.mjs";
import { t as captureSessionPortalTarget } from "./session-portal-target-D54R2k-a.mjs";
import { r as defineValidatedGatewayMethod } from "./validation-B7TobU6-.mjs";
import { n as resolveSessionEnvironmentCaller } from "./environments.session-DHXzfjrF.mjs";
//#region src/gateway/server-methods/portals.session.ts
function sessionPortalOwner(options, environmentId) {
const access = options.sessionAccessAuthority;
const environments = options.context.workerEnvironmentService;
const service = options.context.portalService;
if (!access || !environments || !service) throw new Error("Session preview authority or transport is unavailable");
if (access.sandboxRequired) throw new Error("This conversation requires sandbox isolation; attached machine previews are unavailable");
const target = captureSessionPortalTarget(environments, {
...access.target,
sessionLifecycleRevision: access.target.lifecycleRevision
}, environmentId);
const assertInvocationCurrent = () => {
access.assertCurrent();
target.assertCurrent();
};
assertInvocationCurrent();
const { binding } = target;
const resourceOwnerKey = JSON.stringify([
binding.agentId,
binding.sessionKey,
binding.sessionId,
binding.sessionLifecycleRevision ?? null,
binding.generation
]);
return {
...target,
resourceOwnerKey,
environments,
service,
access,
assertAllowed: assertInvocationCurrent
};
}
function fail(options, error) {
options.respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, error instanceof Error ? error.message : "Session preview request failed"));
}
function changed(options) {
options.context.broadcast("portal.changed", { portals: options.context.portalService.list().map(({ url: _url, tokenQuery: _token, ...portal }) => portal) }, { dropIfSlow: true });
}
const sessionPortalHandlers = {
"portal.session.list": defineValidatedGatewayMethod("portal.session.list", validateSessionPortalListParams, (options) => {
try {
const owner = sessionPortalOwner(options, options.params.environmentId);
const portals = owner.service.listWorkerPortals(owner.binding.environmentId, owner.binding.ownerEpoch, owner.resourceOwnerKey);
owner.assertAllowed();
options.respond(true, { portals });
} catch (error) {
fail(options, error);
}
}),
"portal.session.open": defineValidatedGatewayMethod("portal.session.open", validateSessionPortalOpenParams, async (options) => {
try {
const request = options.params;
const owner = sessionPortalOwner(options, request.environmentId);
await owner.touch();
owner.assertAllowed();
const session = owner.access.retainSession();
let connection;
try {
session.assertCurrent();
connection = await owner.environments.openNodePortal({
environmentId: owner.binding.environmentId,
ownerEpoch: owner.binding.ownerEpoch,
remotePort: request.port
});
} catch (error) {
session.release();
throw error;
}
const close = async () => {
try {
await connection.close();
} finally {
session.release();
}
};
try {
owner.assertAllowed();
session.assertCurrent();
} catch (error) {
await close();
throw error;
}
const opened = await owner.service.open({
targetPort: request.port,
resourceOwnerKey: owner.resourceOwnerKey,
assertCurrent: owner.assertAllowed,
ownerSignal: AbortSignal.any([session.signal, owner.signal]),
target: {
kind: "worker",
environmentId: owner.binding.environmentId,
ownerEpoch: owner.binding.ownerEpoch,
remotePort: request.port,
connect: () => connection.connect(() => {
session.assertCurrent();
owner.assertCurrent();
}, owner.touch)
},
onClose: close,
...request.title !== void 0 ? { title: request.title } : {},
...request.description !== void 0 ? { description: request.description } : {},
...request.path !== void 0 ? { path: request.path } : {}
});
changed(options);
owner.assertAllowed();
options.respond(true, opened);
} catch (error) {
fail(options, error);
}
}),
"portal.session.close": defineValidatedGatewayMethod("portal.session.close", validateSessionPortalCloseParams, async (options) => {
try {
const owner = sessionPortalOwner(options, options.params.environmentId);
if (!owner.service.listWorkerPortals(owner.binding.environmentId, owner.binding.ownerEpoch, owner.resourceOwnerKey).some(({ id }) => id === options.params.id)) throw new Error("Portal does not belong to this conversation's attached environment");
await owner.service.close(options.params.id, owner.assertAllowed);
changed(options);
owner.assertAllowed();
options.respond(true, { closed: true });
} catch (error) {
fail(options, error);
}
})
};
//#endregion
//#region src/gateway/server-methods/portals.ts
function requirePortalService(context, respond) {
const service = context.portalService;
if (!service) respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "portals unavailable"));
return service;
}
function redactPortalSummary(summary) {
const { tokenQuery: _tokenQuery, url: _url, ...redacted } = summary;
return redacted;
}
function attachedPortalOwner(options, environmentId) {
const { context } = options;
const environments = context.workerEnvironmentService;
if (!environments) throw new Error("Conversation environments are unavailable");
const caller = resolveSessionEnvironmentCaller(options);
const binding = environments.findSessionAttachment(caller.identity);
if (!binding || binding.environmentId !== environmentId) throw new Error("Portal environment is not attached to this conversation");
const assertCurrent = () => {
caller.assertCurrent();
environments.assertSessionAttachment(binding);
};
assertCurrent();
return {
binding,
environments,
assertCurrent
};
}
const portalHandlers = {
...sessionPortalHandlers,
"portal.list": defineValidatedGatewayMethod("portal.list", validatePortalListParams, (options) => {
const { params, respond, context, client } = options;
const service = requirePortalService(context, respond);
if (!service) return;
const scopes = Array.isArray(client?.connect?.scopes) ? client.connect.scopes : [];
let portals;
try {
const owner = params.environmentId ? attachedPortalOwner(options, params.environmentId) : void 0;
portals = owner ? service.listWorkerPortals(owner.binding.environmentId, owner.binding.ownerEpoch) : service.list();
} catch (error) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, String(error)));
return;
}
respond(true, { portals: scopes.includes("operator.write") || scopes.includes("operator.admin") ? portals : portals.map(redactPortalSummary) }, void 0);
}),
"portal.open": defineValidatedGatewayMethod("portal.open", validatePortalOpenParams, async (options) => {
const { params: request, respond, context } = options;
const service = requirePortalService(context, respond);
if (!service) return;
try {
const owner = request.environmentId ? attachedPortalOwner(options, request.environmentId) : void 0;
await owner?.environments.touchSessionAttachment(owner.binding);
owner?.assertCurrent();
const connection = owner ? await owner.environments.openNodePortal({
environmentId: owner.binding.environmentId,
ownerEpoch: owner.binding.ownerEpoch,
remotePort: request.port
}) : void 0;
try {
owner?.assertCurrent();
} catch (error) {
await connection?.close();
throw error;
}
const opened = await service.open({
targetPort: request.port,
...owner && connection ? {
target: {
kind: "worker",
environmentId: owner.binding.environmentId,
ownerEpoch: owner.binding.ownerEpoch,
remotePort: request.port,
connect: () => connection.connect(() => owner.environments.assertSessionAttachment(owner.binding), () => owner.environments.touchSessionAttachment(owner.binding))
},
assertCurrent: owner.assertCurrent,
onClose: connection.close
} : {},
...request.title !== void 0 ? { title: request.title } : {},
...request.description !== void 0 ? { description: request.description } : {},
...request.path !== void 0 ? { path: request.path } : {}
});
owner?.assertCurrent();
context.broadcast("portal.changed", { portals: service.list().map(redactPortalSummary) }, { dropIfSlow: true });
respond(true, opened, void 0);
} catch (error) {
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, error instanceof Error ? error.message : String(error)));
}
}),
"portal.close": defineValidatedGatewayMethod("portal.close", validatePortalCloseParams, async (options) => {
const { params, respond, context } = options;
const service = requirePortalService(context, respond);
if (!service) return;
try {
const owner = params.environmentId ? attachedPortalOwner(options, params.environmentId) : void 0;
if (owner && !service.listWorkerPortals(owner.binding.environmentId, owner.binding.ownerEpoch).some((portal) => portal.id === params.id)) throw new Error("Portal does not belong to the attached environment");
if (owner) await service.close(params.id, owner.assertCurrent);
else await service.close(params.id);
owner?.assertCurrent();
context.broadcast("portal.changed", { portals: service.list().map(redactPortalSummary) }, { dropIfSlow: true });
respond(true, { closed: true }, void 0);
} catch (error) {
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, error instanceof Error ? error.message : String(error)));
}
})
};
//#endregion
export { portalHandlers };