UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

221 lines (220 loc) 10.2 kB
import { c as normalizeOptionalLowercaseString } from "./string-coerce-CIXf7egm.js"; import { l as setNodeRunnerStateChangedListener, n as createNodeRegistryRuntime } from "./node-registry-private-CPqGMumT.js"; import { r as serializeEventPayload, t as NodeRegistry } from "./node-registry-DspP5BRY.js"; import { n as GATEWAY_EVENT_NODE_RUNNER_INVENTORY_CHANGED } from "./events-DFzVQFw-.js"; import { a as isPairedDeviceNodeBindingCurrent, s as resolveCurrentPairedDeviceNodeBinding } from "./device-pairing-node-state-DxNt2jjv.js"; //#region src/gateway/server-node-subscriptions.ts /** Manages node subscriptions to gateway session events. */ function createNodeSubscriptionManager() { const nodeSubscriptions = /* @__PURE__ */ new Map(); const sessionSubscribers = /* @__PURE__ */ new Map(); const toPayloadJSON = (payload) => { try { return serializeEventPayload(payload); } catch { return; } }; const settleFanout = async (entries, createSend) => { await Promise.allSettled(Array.from(entries, (entry) => Promise.resolve().then(createSend(entry)))); }; const subscribe = (nodeId, pairingGeneration, sessionKey) => { const normalizedNodeId = nodeId.trim(); const normalizedPairingGeneration = pairingGeneration.trim(); const normalizedSessionKey = sessionKey.trim(); if (!normalizedNodeId || !normalizedPairingGeneration || !normalizedSessionKey) return; let nodeEntry = nodeSubscriptions.get(normalizedNodeId); if (nodeEntry?.pairingGeneration !== normalizedPairingGeneration) { unsubscribeAll(normalizedNodeId); nodeEntry = void 0; } if (!nodeEntry) { nodeEntry = { pairingGeneration: normalizedPairingGeneration, sessionKeys: /* @__PURE__ */ new Set() }; nodeSubscriptions.set(normalizedNodeId, nodeEntry); } if (nodeEntry.sessionKeys.has(normalizedSessionKey)) return; nodeEntry.sessionKeys.add(normalizedSessionKey); let sessionMap = sessionSubscribers.get(normalizedSessionKey); if (!sessionMap) { sessionMap = /* @__PURE__ */ new Map(); sessionSubscribers.set(normalizedSessionKey, sessionMap); } sessionMap.set(normalizedNodeId, normalizedPairingGeneration); }; const unsubscribe = (nodeId, pairingGeneration, sessionKey) => { const normalizedNodeId = nodeId.trim(); const normalizedPairingGeneration = pairingGeneration.trim(); const normalizedSessionKey = sessionKey.trim(); if (!normalizedNodeId || !normalizedPairingGeneration || !normalizedSessionKey) return; const nodeEntry = nodeSubscriptions.get(normalizedNodeId); if (nodeEntry?.pairingGeneration !== normalizedPairingGeneration) return; nodeEntry.sessionKeys.delete(normalizedSessionKey); if (nodeEntry.sessionKeys.size === 0) nodeSubscriptions.delete(normalizedNodeId); const sessionMap = sessionSubscribers.get(normalizedSessionKey); if (sessionMap?.get(normalizedNodeId) === normalizedPairingGeneration) sessionMap.delete(normalizedNodeId); if (sessionMap?.size === 0) sessionSubscribers.delete(normalizedSessionKey); }; function unsubscribeAll(nodeId, pairingGeneration) { const normalizedNodeId = nodeId.trim(); const nodeEntry = nodeSubscriptions.get(normalizedNodeId); if (!nodeEntry || pairingGeneration !== void 0 && nodeEntry.pairingGeneration !== pairingGeneration.trim()) return; for (const sessionKey of nodeEntry.sessionKeys) { const sessionMap = sessionSubscribers.get(sessionKey); if (sessionMap?.get(normalizedNodeId) === nodeEntry.pairingGeneration) sessionMap.delete(normalizedNodeId); if (sessionMap?.size === 0) sessionSubscribers.delete(sessionKey); } nodeSubscriptions.delete(normalizedNodeId); } const updatePairingGeneration = (params) => { const normalizedNodeId = params.nodeId.trim(); const previousPairingGeneration = params.previousPairingGeneration.trim(); const nextPairingGeneration = params.nextPairingGeneration.trim(); const nodeEntry = nodeSubscriptions.get(normalizedNodeId); if (!nodeEntry || !previousPairingGeneration || nodeEntry.pairingGeneration !== previousPairingGeneration) return; if (!params.preserveSubscriptions || !nextPairingGeneration) { unsubscribeAll(normalizedNodeId, previousPairingGeneration); return; } nodeEntry.pairingGeneration = nextPairingGeneration; for (const sessionKey of nodeEntry.sessionKeys) sessionSubscribers.get(sessionKey)?.set(normalizedNodeId, nextPairingGeneration); }; const sendToSession = async (sessionKey, event, payload, sendEvent) => { const normalizedSessionKey = sessionKey.trim(); if (!normalizedSessionKey || !sendEvent) return; const subscribers = sessionSubscribers.get(normalizedSessionKey); if (!subscribers || subscribers.size === 0) return; const payloadJSON = toPayloadJSON(payload); if (payloadJSON === void 0) return; await settleFanout(subscribers, ([nodeId, pairingGeneration]) => () => sendEvent({ nodeId, pairingGeneration, event, payloadJSON })); }; const sendToAllSubscribed = async (event, payload, sendEvent) => { if (!sendEvent) return; const payloadJSON = toPayloadJSON(payload); if (payloadJSON === void 0) return; await settleFanout(nodeSubscriptions, ([nodeId, subscription]) => () => sendEvent({ nodeId, pairingGeneration: subscription.pairingGeneration, event, payloadJSON })); }; return { subscribe, unsubscribe, unsubscribeAll, updatePairingGeneration, sendToSession, sendToAllSubscribed }; } //#endregion //#region src/gateway/server-talk-nodes.ts const TALK_CAPABILITY = "talk"; const TALK_COMMAND_PREFIX = "talk."; /** Returns true when any connected node can handle talk routing. */ async function hasConnectedTalkNode(registry) { return (await registry.listCurrentConnected()).some(isTalkCapableNode); } function isTalkCapableNode(node) { return node.caps.some((capability) => normalizeOptionalLowercaseString(capability) === TALK_CAPABILITY) || node.commands.some((command) => normalizeOptionalLowercaseString(command)?.startsWith(TALK_COMMAND_PREFIX)); } //#endregion //#region src/gateway/server-node-session-runtime.ts /** Creates node registry/subscription runtime state for a gateway server. */ function createGatewayNodeSessionRuntime(params) { const nodeSubscriptions = createNodeSubscriptionManager(); const { nodeRegistry, nodeWorkerSupervisorTransport } = createNodeRegistryRuntime(() => new NodeRegistry({ listRegisteredNodePluginToolCommands: params.listRegisteredNodePluginToolCommands, getConfig: params.getConfig, resolveCurrentPairingState: params.resolveCurrentPairingState ?? resolveCurrentPairedDeviceNodeBinding, isPairingStateCurrent: params.isPairingStateCurrent ?? isPairedDeviceNodeBindingCurrent, onPairingInvalidated: params.onPairingInvalidated, onPairingGenerationChanged: (change) => { nodeSubscriptions.updatePairingGeneration({ ...change, preserveSubscriptions: change.preserveSessionState }); params.onPairingGenerationChanged?.(change); } })); setNodeRunnerStateChangedListener(nodeRegistry, (nodeId, change) => { params.onRunnerStateChanged?.(nodeId, change); if (change.inventoryChanged || change.availabilityChanged) params.broadcast(GATEWAY_EVENT_NODE_RUNNER_INVENTORY_CHANGED, { nodeId }, { dropIfSlow: true }); if (change.availabilityChanged) params.broadcast("sessions.changed", { reason: "runner-availability" }, { dropIfSlow: true }); }); const nodePresenceTimers = /* @__PURE__ */ new Map(); const sessionEventSubscribers = params.sessionEventSubscribers; const sessionMessageSubscribers = params.sessionMessageSubscribers; const nodeSendEvent = (opts) => { return nodeRegistry.sendEventRawForPairingGeneration(opts.nodeId, opts.pairingGeneration, opts.event, opts.payloadJSON ?? null); }; const nodeSendToSession = (sessionKey, event, payload) => { nodeSubscriptions.sendToSession(sessionKey, event, payload, nodeSendEvent); }; const nodeSendToAllSubscribed = (event, payload) => { nodeSubscriptions.sendToAllSubscribed(event, payload, nodeSendEvent); }; const resolveSubscriptionGeneration = (nodeId, connId) => { const node = nodeRegistry.get(nodeId); return connId && node?.connId === connId ? node.pairingGeneration : void 0; }; const nodeSubscribe = (nodeId, sessionKey, connId) => { const pairingGeneration = resolveSubscriptionGeneration(nodeId, connId); if (pairingGeneration) nodeSubscriptions.subscribe(nodeId, pairingGeneration, sessionKey); }; const nodeUnsubscribe = (nodeId, sessionKey, connId) => { const pairingGeneration = resolveSubscriptionGeneration(nodeId, connId); if (pairingGeneration) nodeSubscriptions.unsubscribe(nodeId, pairingGeneration, sessionKey); }; const sendVoiceWakeEventToCurrentNodes = (event, payload) => { const payloadJSON = serializeEventPayload(payload); for (const node of nodeRegistry.listConnected()) { const pairingGeneration = node.pairingGeneration; if (!pairingGeneration) { if (node.pairingIdentity) nodeRegistry.sendEventForPairingIdentity({ nodeId: node.nodeId, connId: node.connId, pairingIdentity: node.pairingIdentity, event, payload }).catch(() => void 0); continue; } nodeRegistry.sendEventRawForPairingGeneration(node.nodeId, pairingGeneration, event, payloadJSON).catch(() => void 0); } }; const broadcastVoiceWakeChanged = (triggers) => { params.broadcast("voicewake.changed", { triggers }, { dropIfSlow: true }); sendVoiceWakeEventToCurrentNodes("voicewake.changed", { triggers }); }; const broadcastVoiceWakeRoutingChanged = (config) => { params.broadcast("voicewake.routing.changed", { config }, { dropIfSlow: true }); sendVoiceWakeEventToCurrentNodes("voicewake.routing.changed", { config }); }; const hasTalkNodeConnected = () => hasConnectedTalkNode(nodeRegistry); return { nodeRegistry, nodeWorkerSupervisorTransport, nodePresenceTimers, sessionEventSubscribers, sessionMessageSubscribers, nodeSendToSession, nodeSendToAllSubscribed, nodeSubscribe, nodeUnsubscribe, nodeUnsubscribeAll: nodeSubscriptions.unsubscribeAll, broadcastVoiceWakeChanged, broadcastVoiceWakeRoutingChanged, hasTalkNodeConnected }; } //#endregion export { createGatewayNodeSessionRuntime };