UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

349 lines (348 loc) • 15.4 kB
import { F as resolveTimerTimeoutMs } from "./number-coercion-CuVhdCBs.mjs"; import { r as createLazyRuntimeModule } from "./lazy-runtime-CJrzFzqU.mjs"; import { t as formatErrorMessage } from "./errors-O33D01XW.mjs"; import { i as GATEWAY_CLIENT_NAMES, r as GATEWAY_CLIENT_MODES } from "./client-info-C8RatpKO.mjs"; import "./message-channel-j-k-Nup3.mjs"; import { a as projectOutboundPayloadPlanForDelivery, s as projectOutboundPayloadPlanForMirror, t as createOutboundPayloadPlan } from "./payloads-RF8yW6z7.mjs"; import { s as resolveOutboundDurableFinalDeliverySupport } from "./deliver-prepare-eSoKByIL.mjs"; import { r as normalizeOutboundReplyFacts } from "./reply-policy-D40tu-98.mjs"; import { c as findOutboundHandoffRejectedError, s as assertOutboundHandoffCurrent } from "./delivery-queue-reconciliation-D24sjkey.mjs"; import { t as buildOutboundSessionContext } from "./session-context-Kov7ff_A.mjs"; import { i as serializeDurableMessagePayloadOutcomes, n as sendDurableMessageBatchCore, t as durableMessageBatchMayHaveReachedRecipient } from "./send-CqAjSzNt.mjs"; import { n as deriveDurableFinalDeliveryRequirementsForBatch } from "./capabilities-CZr1Vdri.mjs"; import "./deliver-ByaMG-sT.mjs"; import { u as createChannelPartialDeliveryError } from "./live-TWzvpgza.mjs"; import "./runtime-BZREclVS.mjs"; import { n as normalizePollInput } from "./polls-C-v11_tu.mjs"; import { r as resolveMessageChannelSelection } from "./channel-selection-H42iGAM4.mjs"; import { a as resolveOutboundTarget } from "./targets-BUerMF9S.mjs"; //#region src/infra/outbound/message-gateway-options.ts /** Normalizes outbound gateway options and fills CLI defaults. */ function resolveOutboundMessageGatewayOptions(gateway) { const clientName = gateway?.clientName ?? GATEWAY_CLIENT_NAMES.CLI; const mode = gateway?.mode ?? GATEWAY_CLIENT_MODES.CLI; const url = mode === GATEWAY_CLIENT_MODES.BACKEND || clientName === GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT ? void 0 : gateway?.url; return { config: gateway?.config, localPortOverride: gateway?.localPortOverride, ignoreEnvUrlOverride: gateway?.ignoreEnvUrlOverride, tlsFingerprint: gateway?.tlsFingerprint, url, token: gateway?.token, timeoutMs: resolveTimerTimeoutMs(gateway?.timeoutMs, 1e4), clientName, clientDisplayName: gateway?.clientDisplayName, mode }; } //#endregion //#region src/infra/outbound/message.ts const SEND_BUFFER_MEDIA_URL = "buffer://message-send/attachment"; const loadMessageConfigRuntime = createLazyRuntimeModule(() => import("./message.config.runtime.js")); const loadMessageGatewayRuntime = createLazyRuntimeModule(() => import("./message.gateway.runtime.js")); function normalizeMessagePollDeliveryResult(result) { const { channelId, conversationId, ...delivery } = result; return { ...delivery, ...channelId ? { target: { kind: "channel", id: channelId } } : conversationId ? { target: { kind: "conversation", id: conversationId } } : {} }; } function assertPollOptionSupport(params) { if (typeof params.durationSeconds === "number" && params.outbound.supportsPollDurationSeconds !== true) throw new Error(`durationSeconds is not supported for ${params.channel} polls`); if (typeof params.isAnonymous === "boolean" && params.outbound.supportsAnonymousPolls !== true) throw new Error(`isAnonymous is not supported for ${params.channel} polls`); } async function assertRequiredMessageSendDurability(params) { const support = await resolveOutboundDurableFinalDeliverySupport({ cfg: params.cfg, agentId: params.agentId, channel: params.channel, requirements: deriveDurableFinalDeliveryRequirementsForBatch({ ...params, reconcileUnknownSend: true }) }); if (support.ok) return; const suffix = support.reason === "capability_mismatch" && support.capability ? `missing ${support.capability}` : support.reason; throw new Error(`Required durable message send is unsupported for ${params.channel}: ${suffix}. Use queuePolicy:"best_effort" for best-effort delivery, omit bestEffort:false in message-tool calls, or use a channel with required durable delivery support.`); } async function callMessageGateway(params) { const gateway = resolveOutboundMessageGatewayOptions(params.gateway); const agentRuntimeIdentityToken = params.gateway?.request ? void 0 : await params.gateway?.resolveAgentRuntimeIdentityToken?.(); await params.onPlatformSendDispatch?.(); assertOutboundHandoffCurrent(params.assertDirectAdapterHandoff); if (params.gateway?.request) return await params.gateway.request({ method: params.method, params: params.params, timeoutMs: gateway.timeoutMs }); const { callGatewayLeastPrivilege } = await loadMessageGatewayRuntime(); return await callGatewayLeastPrivilege({ ...gateway, method: params.method, params: params.params, agentRuntimeIdentityToken }); } async function resolveMessageConfig(cfg) { if (cfg) return cfg; const { getRuntimeConfig } = await loadMessageConfigRuntime(); return getRuntimeConfig(); } async function resolveGatewayIdempotencyKey(idempotencyKey) { if (idempotencyKey) return idempotencyKey; const { randomIdempotencyKey } = await loadMessageGatewayRuntime(); return randomIdempotencyKey(); } async function sendMessage(params) { const cfg = await resolveMessageConfig(params.cfg); const reply = normalizeOutboundReplyFacts({ reply: params.reply, replyToId: params.replyToId }); const { channel, plugin } = params.preparedPlugin ? { channel: params.preparedPlugin.id, plugin: params.preparedPlugin } : await resolveMessageChannelSelection({ cfg, channel: params.channel }); const deliveryMode = plugin.outbound?.deliveryMode ?? "direct"; const hasRealMediaSource = [params.mediaUrl, ...params.mediaUrls ?? []].filter((source) => Boolean(source)).some((source) => source !== SEND_BUFFER_MEDIA_URL); const shouldForwardBuffer = deliveryMode === "gateway" && Boolean(params.buffer) && !hasRealMediaSource; const mediaUrl = params.mediaUrl ?? (shouldForwardBuffer ? SEND_BUFFER_MEDIA_URL : void 0); const mediaUrls = params.mediaUrls ?? (shouldForwardBuffer ? [SEND_BUFFER_MEDIA_URL] : void 0); const outboundPayloads = params.payloads && params.payloads.length > 0 ? params.payloads : [{ text: params.content, mediaUrl, mediaUrls, audioAsVoice: params.asVoice === true }]; const outboundPlan = createOutboundPayloadPlan(outboundPayloads); const normalizedPayloads = projectOutboundPayloadPlanForDelivery(outboundPlan); const mirrorProjection = projectOutboundPayloadPlanForMirror(outboundPlan); const mirrorText = mirrorProjection.text; const mirrorMediaUrls = mirrorProjection.mediaUrls; const primaryMediaUrl = mirrorMediaUrls[0] ?? mediaUrl ?? null; const baseResult = { channel, to: params.to, via: deliveryMode === "gateway" ? "gateway" : "direct", mediaUrl: primaryMediaUrl, mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : void 0 }; if (params.dryRun) return { ...baseResult, dryRun: true }; if (deliveryMode !== "gateway" || params.gatewayOwnedDelivery === true) { const outboundChannel = channel; const resolvedTarget = resolveOutboundTarget({ channel: outboundChannel, plugin, to: params.to, cfg, accountId: params.accountId, mode: "explicit" }); if (!resolvedTarget.ok) throw resolvedTarget.error; const outboundSession = buildOutboundSessionContext({ cfg, agentId: params.agentId, sessionKey: params.requesterSessionKey ?? params.mirror?.sessionKey, conversationType: params.conversationType, requesterAccountId: params.requesterAccountId ?? params.accountId, requesterSenderId: params.requesterSenderId, requesterSenderName: params.requesterSenderName, requesterSenderUsername: params.requesterSenderUsername, requesterSenderE164: params.requesterSenderE164 }); const requireUnknownSendReconciliation = params.requireUnknownSendReconciliation ?? params.queuePolicy === "required"; if (requireUnknownSendReconciliation) await assertRequiredMessageSendDurability({ cfg, agentId: params.agentId, channel: outboundChannel, payloads: normalizedPayloads, replyToId: reply?.replyToId, threadId: params.threadId, silent: params.silent }); const send = await sendDurableMessageBatchCore({ cfg, channel: outboundChannel, to: resolvedTarget.to, session: outboundSession, runId: params.runId, executionIdentityToken: params.executionIdentityToken, accountId: params.accountId, conversationReadOrigin: params.conversationReadOrigin, payloads: normalizedPayloads, reply, threadId: params.threadId, gifPlayback: params.gifPlayback, forceDocument: params.forceDocument, deps: params.deps, bestEffort: params.bestEffort, ...requireUnknownSendReconciliation ? { requireUnknownSendReconciliation: true } : {}, durability: params.bestEffort || params.queuePolicy === "best_effort" ? "best_effort" : "required", signal: params.abortSignal, silent: params.silent, mediaAccess: params.mediaAccess, formatting: params.parseMode ? { parseMode: params.parseMode } : void 0, preparedMessageId: params.preparedMessageId, deliveryIntentId: params.deliveryIntentId, deliveryCompletion: params.deliveryCompletion, reusePendingDeliveryIntent: params.reusePendingDeliveryIntent, deliveryRetryOwner: params.deliveryRetryOwner, completionRetention: params.completionRetention, ...params.onDeliveryIntent ? { onDeliveryIntent: params.onDeliveryIntent } : {}, ...params.onDeliveryAttempt ? { onDeliveryAttempt: params.onDeliveryAttempt } : {}, ...params.onDeliveryResult ? { onDeliveryResult: params.onDeliveryResult } : {}, ...params.onPlatformSendDispatch ? { onPlatformSendDispatch: params.onPlatformSendDispatch } : {}, assertDirectAdapterHandoff: params.assertDirectAdapterHandoff, skipQueue: params.skipQueue, ...params.onDeliveredPayload ? { onDeliveredPayload: params.onDeliveredPayload } : {}, mirror: params.mirror ? { ...params.mirror, text: mirrorText || params.content, mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : void 0, idempotencyKey: params.mirror.idempotencyKey ?? params.idempotencyKey } : void 0 }, params.conversationDeliveryTarget); const sendMayHaveReachedRecipient = durableMessageBatchMayHaveReachedRecipient(send); const handoffRejection = send.status === "failed" && !sendMayHaveReachedRecipient ? send.payloadOutcomes?.map((outcome) => outcome.status === "failed" ? findOutboundHandoffRejectedError(outcome.error) : void 0).find((error) => error !== void 0) ?? findOutboundHandoffRejectedError(send.error) : void 0; if (handoffRejection) throw handoffRejection; if (!params.bestEffort && params.gateway?.clientName !== GATEWAY_CLIENT_NAMES.CLI && (send.status === "failed" || send.status === "partial_failed")) { if (send.status === "partial_failed") throw createChannelPartialDeliveryError(send.error, { messageIds: send.results.map((result) => result.messageId), receipt: send.receipt, visibleReplySent: true }); throw send.error; } const results = send.status === "sent" || send.status === "partial_failed" ? send.results : []; const payloadOutcomes = serializeDurableMessagePayloadOutcomes(send.payloadOutcomes); const sentBeforeError = send.status !== "sent" && sendMayHaveReachedRecipient; return { ...baseResult, result: results.at(-1), deliveryStatus: send.status, ...send.status === "suppressed" ? { suppressionReason: send.reason } : {}, ...send.status === "failed" || send.status === "partial_failed" ? { error: formatErrorMessage(send.error) } : {}, ...sentBeforeError ? { sentBeforeError: true } : {}, ...payloadOutcomes ? { payloadOutcomes } : {} }; } const result = await callMessageGateway({ gateway: params.gateway, method: "send", onPlatformSendDispatch: params.onPlatformSendDispatch, assertDirectAdapterHandoff: params.assertDirectAdapterHandoff, params: { to: params.to, message: params.content, mediaUrl, mediaUrls: mirrorMediaUrls.length ? mirrorMediaUrls : mediaUrls, buffer: shouldForwardBuffer ? params.buffer : void 0, filename: shouldForwardBuffer ? params.filename : void 0, contentType: shouldForwardBuffer ? params.contentType : void 0, asVoice: params.asVoice, gifPlayback: params.gifPlayback, accountId: params.accountId, agentId: params.agentId, channel, replyToId: reply?.replyToId, threadId: params.threadId != null ? String(params.threadId) : void 0, forceDocument: params.forceDocument, silent: params.silent, parseMode: params.parseMode, sessionKey: params.mirror?.sessionKey, idempotencyKey: await resolveGatewayIdempotencyKey(params.idempotencyKey) } }); return { ...baseResult, result }; } async function sendPoll(params) { const cfg = await resolveMessageConfig(params.cfg); const { channel, plugin } = params.preparedPlugin ? { channel: params.preparedPlugin.id, plugin: params.preparedPlugin } : await resolveMessageChannelSelection({ cfg, channel: params.channel }); const outbound = plugin.outbound; if (!outbound?.sendPoll) throw new Error(`Unsupported poll channel: ${channel}`); const deliveryMode = outbound.deliveryMode ?? "direct"; const normalized = normalizePollInput(params, outbound.pollMaxOptions ? { maxOptions: outbound.pollMaxOptions } : void 0); const buildResult = (delivery) => ({ channel, to: params.to, question: normalized.question, options: normalized.options, maxSelections: normalized.maxSelections, durationSeconds: normalized.durationSeconds ?? null, durationHours: normalized.durationHours ?? null, via: deliveryMode === "gateway" ? "gateway" : "direct", ...delivery }); if (params.dryRun) return buildResult({ dryRun: true }); assertPollOptionSupport({ channel, outbound, durationSeconds: params.durationSeconds, isAnonymous: params.isAnonymous }); if (deliveryMode !== "gateway" || params.gatewayOwnedDelivery === true) { const resolvedTarget = resolveOutboundTarget({ channel, plugin, to: params.to, cfg, accountId: params.accountId, mode: "explicit" }); if (!resolvedTarget.ok) throw resolvedTarget.error; params.assertDirectAdapterHandoff?.(); return buildResult({ result: normalizeMessagePollDeliveryResult(await outbound.sendPoll({ cfg, to: resolvedTarget.to, poll: normalized, content: params.content, accountId: params.accountId, threadId: params.threadId, silent: params.silent, isAnonymous: params.isAnonymous, sessionKey: params.sessionKey, inboundEventKind: params.inboundEventKind, onPlatformSendDispatch: params.onPlatformSendDispatch, assertDirectAdapterHandoff: params.assertDirectAdapterHandoff })) }); } return buildResult({ result: normalizeMessagePollDeliveryResult(await callMessageGateway({ gateway: params.gateway, method: "poll", onPlatformSendDispatch: params.onPlatformSendDispatch, assertDirectAdapterHandoff: params.assertDirectAdapterHandoff, params: { ...normalized, to: params.to, threadId: params.threadId, silent: params.silent, isAnonymous: params.isAnonymous, channel, accountId: params.accountId, idempotencyKey: await resolveGatewayIdempotencyKey(params.idempotencyKey) } })) }); } //#endregion export { sendPoll as n, resolveOutboundMessageGatewayOptions as r, sendMessage as t };