UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

201 lines (200 loc) 8.53 kB
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js"; import { y as uniqueStrings } from "./string-normalization-DsCfAx8q.js"; import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js"; import { f as resolveAgentIdFromSessionKey } from "./session-key-BnWWjqNc.js"; import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js"; import { o as resolveSessionStorePathCore } from "./paths-CXdaYWF_.js"; import "./session-accessor-YsytfDtG.js"; import { u as loadSessionEntryReadOnly, y as updateSessionLastRoute } from "./session-accessor.sqlite-entry-CWk3jL7s.js"; import { t as getChannelPlugin } from "./registry-Cz6cv4VC.js"; import "./plugins-Cozj1Enf.js"; import { r as inheritSessionCreationPolicy } from "./session-entry-provenance-jzrCUpdQ.js"; import { s as resolveAgentRoute } from "./resolve-route-BRXfbiKP.js"; import "./inbound.runtime-th3B0GYh.js"; import { t as buildOutboundBaseSessionKey } from "./base-session-key-CQi1rAFG.js"; //#region src/infra/outbound/outbound-session.ts function resolveOutboundChannelPlugin(channel) { return getChannelPlugin(channel); } function rebaseOutboundSessionRoute(route, baseSessionKey) { if (route.sessionKey !== route.baseSessionKey && !route.sessionKey.startsWith(`${route.baseSessionKey}:`)) return null; return { ...route, sessionKey: `${baseSessionKey}${route.sessionKey.slice(route.baseSessionKey.length)}`, baseSessionKey }; } function stripProviderPrefix(raw, channel) { const trimmed = raw.trim(); const lower = normalizeLowercaseStringOrEmpty(trimmed); const prefix = `${normalizeLowercaseStringOrEmpty(channel)}:`; if (lower.startsWith(prefix)) return trimmed.slice(prefix.length).trim(); return trimmed; } function stripKindPrefix(raw) { return raw.replace(/^(user|channel|group|conversation|room|dm|thread):/i, "").trim(); } const FALLBACK_TARGET_KIND_PREFIXES = [ { kind: "direct", pattern: /^(user:|dm:)/i }, { kind: "channel", pattern: /^(channel:|conversation:|thread:)/i }, { kind: "group", pattern: /^(group:|room:)/i } ]; function normalizeInferredPeerKind(value) { return value === "direct" || value === "group" || value === "channel" ? value : void 0; } function inferPeerKindFromPlugin(params) { for (const target of params.targets) { const inferred = normalizeInferredPeerKind(params.plugin?.messaging?.inferTargetChatType?.({ to: target })); if (inferred) return inferred; } } function inferPeerKindFromFallbackPrefixes(targets) { for (const target of targets) for (const fallback of FALLBACK_TARGET_KIND_PREFIXES) if (fallback.pattern.test(target)) return fallback.kind; } function inferPeerKindFromCapabilities(plugin) { const chatTypes = []; for (const chatType of plugin?.capabilities?.chatTypes ?? []) if ((chatType === "direct" || chatType === "group" || chatType === "channel") && !chatTypes.includes(chatType)) chatTypes.push(chatType); return chatTypes.length === 1 ? chatTypes[0] : void 0; } function inferPeerKind(params) { const resolvedKind = params.resolvedTarget?.kind; if (resolvedKind === "user") return "direct"; if (resolvedKind === "channel") return "channel"; if (resolvedKind === "group") { const chatTypes = (params.plugin ?? resolveOutboundChannelPlugin(params.channel))?.capabilities?.chatTypes ?? []; const supportsChannel = chatTypes.includes("channel"); const supportsGroup = chatTypes.includes("group"); if (supportsChannel && !supportsGroup) return "channel"; return "group"; } const plugin = params.plugin ?? resolveOutboundChannelPlugin(params.channel); const strippedTarget = stripProviderPrefix(params.target, params.channel).trim(); const targets = uniqueStrings([params.target, strippedTarget].filter(Boolean)); return inferPeerKindFromPlugin({ plugin, targets }) ?? inferPeerKindFromFallbackPrefixes(targets) ?? inferPeerKindFromCapabilities(plugin) ?? "direct"; } function resolveFallbackSession(params) { const trimmed = stripProviderPrefix(params.target, params.channel).trim(); if (!trimmed) return null; const peerKind = inferPeerKind({ channel: params.channel, plugin: params.plugin, target: params.target, resolvedTarget: params.resolvedTarget }); if (!peerKind) return null; const peerId = stripKindPrefix(trimmed); if (!peerId) return null; const peer = { kind: peerKind, id: peerId }; const baseSessionKey = buildOutboundBaseSessionKey({ cfg: params.cfg, agentId: params.agentId, channel: params.channel, accountId: params.accountId, peer }); return { sessionKey: baseSessionKey, baseSessionKey, recipientSessionExact: false, peer, chatType: peerKind === "direct" ? "direct" : peerKind === "channel" ? "channel" : "group", from: peerKind === "direct" ? `${params.channel}:${peerId}` : `${params.channel}:${peerKind}:${peerId}`, to: `${peerKind === "direct" ? "user" : "channel"}:${peerId}` }; } /** Resolves the session route used to mirror outbound delivery into conversation state. */ async function resolveOutboundSessionRoute(params) { const target = params.target.trim(); if (!target) return null; const nextParams = { ...params, target }; const resolver = (params.plugin ?? resolveOutboundChannelPlugin(params.channel))?.messaging?.resolveOutboundSessionRoute; const route = resolver ? await resolver(nextParams) : resolveFallbackSession(nextParams); if (!route || route.recipientSessionExact !== true) return route; const bindingRoute = resolveAgentRoute({ cfg: params.cfg, channel: params.channel, defaultAgentId: params.agentId, accountId: params.accountId, peer: route.peer }); const isDirect = route.peer.kind === "direct"; const globalScope = isDirect ? params.cfg.session?.dmScope ?? "main" : params.cfg.session?.groupScope ?? "per-group"; const bindingScope = isDirect ? bindingRoute.dmScope : bindingRoute.groupScope; if (normalizeAgentId(bindingRoute.agentId) !== normalizeAgentId(params.agentId)) return { ...route, recipientSessionExact: false }; return bindingScope !== globalScope ? rebaseOutboundSessionRoute(route, bindingRoute.sessionKey) : route; } function resolveOutboundSessionCreation(params) { if (params.creation || !params.sourceSessionKey) return params.creation; const source = loadSessionEntryReadOnly({ sessionKey: params.sourceSessionKey, storePath: resolveSessionStorePathCore(params.cfg.session?.store, { agentId: resolveAgentIdFromSessionKey(params.sourceSessionKey) }) }); return source?.sandbox === "required" ? { via: source.createdVia ?? "channel", ...inheritSessionCreationPolicy(source) } : void 0; } async function persistOutboundSessionEntry(params) { const storePath = resolveSessionStorePathCore(params.cfg.session?.store, { agentId: resolveAgentIdFromSessionKey(params.route.sessionKey) }); const ctx = { From: params.route.from, To: params.route.to, SessionKey: params.route.sessionKey, AccountId: params.accountId ?? void 0, ChatType: params.route.chatType, Provider: params.channel, Surface: params.channel, MessageThreadId: params.route.threadId, OriginatingChannel: params.channel, OriginatingTo: params.route.to, NativeDirectUserId: params.route.peer.kind === "direct" ? params.route.peer.id : void 0, NativeChannelId: params.route.peer.kind === "direct" ? void 0 : params.route.peer.id, SessionCreation: resolveOutboundSessionCreation(params) }; return await updateSessionLastRoute({ storePath, sessionKey: params.route.sessionKey, createIfMissing: true, channel: params.channel, to: params.route.to, accountId: params.accountId ?? void 0, threadId: params.route.threadId, ctx, ...params.assertCommitAllowed ? { assertCommitAllowed: params.assertCommitAllowed } : {} }); } /** Persists best-effort session metadata for an outbound-only route. */ async function ensureOutboundSessionEntry(params) { try { await persistOutboundSessionEntry(params); } catch (error) { if (params.creation?.sandbox === "required" || params.sourceSessionKey) createSubsystemLogger("outbound/session").warn(`Failed to preserve outbound session creation policy for ${params.route.sessionKey}: ${String(error)}`); } } /** Persists the route required to bind an exact conversation address to local context. */ async function bindOutboundSessionEntry(params) { if (!await persistOutboundSessionEntry(params)) throw new Error(`Failed to bind outbound session ${params.route.sessionKey}`); } //#endregion export { ensureOutboundSessionEntry as n, resolveOutboundSessionRoute as r, bindOutboundSessionEntry as t };