UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

245 lines (244 loc) 9.81 kB
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js"; import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js"; import { a as getReplyPayloadMetadata } from "./reply-payload-DahKk-7n.js"; import { s as isOutboundDeliveryError } from "./deliver-types-Diy-VQKA.js"; import { t as createMessageReceiptFromOutboundResults } from "./receipt-DdpXkxJo.js"; import { a as createRenderedMessageBatch } from "./deliver-prepare-6uVAVf8W.js"; import { C as normalizeOutboundReplyFacts } from "./reply-payload-Bfsi665c.js"; import { _ as assertSessionWriterDeliveryAuthorized } from "./delivery-queue-recovery-CeXGRhn8.js"; import { a as resolvePendingFinalDeliveryCompletion } from "./pending-final-delivery-jd8h0cqi.js"; import { n as deliverOutboundPayloadsInternal } from "./deliver-BngawiqN.js"; import { o as markLiveMessagePreviewUpdated, t as createLiveMessageState } from "./live-CIdslFJ4.js"; //#region src/channels/message/send.ts /** * Durable channel message sender. * * Sends rendered reply payloads, records live preview state, and classifies delivery outcomes. */ const log = createSubsystemLogger("channels/message/send"); /** Whether platform delivery completed or advanced far enough that retry could duplicate it. */ function durableMessageBatchMayHaveReachedRecipient(result) { if (result.status === "sent" || result.status === "partial_failed") return true; if (result.status === "suppressed" && result.reason === "adapter_returned_no_identity") return true; return result.payloadOutcomes?.some((outcome) => outcome.status === "failed" ? outcome.sentBeforeError : outcome.status === "sent" || outcome.reason === "adapter_returned_no_identity") === true; } function serializeDurableMessagePayloadOutcomes(outcomes, options) { if (!outcomes || outcomes.length === 0) return; return outcomes.map((outcome) => { if (outcome.status === "sent") return { index: outcome.index, status: "sent", resultCount: outcome.results.length }; if (outcome.status === "suppressed") return { index: outcome.index, status: "suppressed", reason: outcome.reason, ...options?.includeHookEffect === true && outcome.hookEffect ? { hookEffect: outcome.hookEffect } : {} }; return { index: outcome.index, status: "failed", error: formatErrorMessage(outcome.error), sentBeforeError: outcome.sentBeforeError, stage: outcome.stage }; }); } const neverAbortedSignal = new AbortController().signal; function toDurableMessageIntent(intent, renderedBatch) { return { id: intent.id, channel: intent.channel, to: intent.to, ...intent.accountId ? { accountId: intent.accountId } : {}, durability: intent.queuePolicy === "required" ? "required" : "best_effort", renderedBatch }; } function toDurablePayloadOutcome(outcome) { return outcome; } function toDurablePayloadOutcomes(outcomes) { return outcomes.map((outcome) => toDurablePayloadOutcome(outcome)); } async function withDurableMessageSendContextCore(params, run) { let deliveryIntent; const { attempt, durability, onDeleteReceipt, onDeliveryIntent, onEditReceipt, onCommitReceipt, onPreviewUpdate, onSendFailure, onPayloadDeliveryOutcome, payloads, preview, previousReceipt, signal, abortSignal, ...deliveryParams } = params; const replyToId = normalizeOutboundReplyFacts(deliveryParams)?.replyToId; const effectiveSignal = signal ?? abortSignal; const queuePolicy = durability === "best_effort" ? "best_effort" : "required"; let liveState = preview ?? createLiveMessageState(); const ctx = { id: `${params.channel}:${params.to}`, channel: params.channel, to: params.to, ...params.accountId ? { accountId: params.accountId } : {}, durability: durability ?? "required", attempt: attempt ?? 1, signal: effectiveSignal ?? neverAbortedSignal, ...previousReceipt ? { previousReceipt } : {}, preview: liveState, render: async () => createRenderedMessageBatch(payloads), previewUpdate: async (rendered) => { liveState = onPreviewUpdate ? await onPreviewUpdate(rendered, liveState) : markLiveMessagePreviewUpdated(liveState, rendered); ctx.preview = liveState; return liveState; }, send: async (rendered) => { const payloadOutcomes = []; const durablePayloadOutcomes = () => toDurablePayloadOutcomes(payloadOutcomes); try { const results = await deliverOutboundPayloadsInternal({ ...deliveryParams, payloads: rendered.payloads, renderedBatchPlan: rendered.plan, queuePolicy, ...effectiveSignal ? { abortSignal: effectiveSignal } : {}, onPayloadDeliveryOutcome: (outcome) => { payloadOutcomes.push(outcome); onPayloadDeliveryOutcome?.(outcome); }, onDeliveryIntent: (intent) => { deliveryIntent = intent; const durableIntent = toDurableMessageIntent(intent, rendered); ctx.intent = durableIntent; onDeliveryIntent?.(durableIntent); } }); const receipt = createMessageReceiptFromOutboundResults({ results, threadId: params.threadId == null ? void 0 : String(params.threadId), replyToId }); const failedOutcome = payloadOutcomes.find((outcome) => outcome.status === "failed"); if (failedOutcome) { if (results.length > 0) return { status: "partial_failed", results, receipt, error: failedOutcome.error, sentBeforeError: true, ...deliveryIntent ? { deliveryIntent } : {}, ...payloadOutcomes.length > 0 ? { payloadOutcomes: durablePayloadOutcomes() } : {} }; return { status: "failed", error: failedOutcome.error, stage: failedOutcome.stage, ...payloadOutcomes.length > 0 ? { payloadOutcomes: durablePayloadOutcomes() } : {} }; } if (results.length === 0) return { status: "suppressed", results: [], receipt, ...deliveryIntent ? { deliveryIntent } : {}, reason: payloadOutcomes.find((outcome) => outcome.status === "suppressed")?.reason ?? "no_visible_result", ...payloadOutcomes.length > 0 ? { payloadOutcomes: durablePayloadOutcomes() } : {} }; return { status: "sent", results, receipt, ...deliveryIntent ? { deliveryIntent } : {}, ...payloadOutcomes.length > 0 ? { payloadOutcomes: durablePayloadOutcomes() } : {} }; } catch (error) { if (isOutboundDeliveryError(error)) { if (error.results.length > 0) { const receipt = createMessageReceiptFromOutboundResults({ results: error.results, threadId: params.threadId == null ? void 0 : String(params.threadId), replyToId }); return { status: "partial_failed", results: error.results, receipt, error, sentBeforeError: true, ...deliveryIntent ? { deliveryIntent } : {}, ...error.payloadOutcomes.length > 0 ? { payloadOutcomes: toDurablePayloadOutcomes(error.payloadOutcomes) } : {} }; } return { status: "failed", error, stage: error.stage, ...error.payloadOutcomes.length > 0 ? { payloadOutcomes: toDurablePayloadOutcomes(error.payloadOutcomes) } : {} }; } return { status: "failed", error }; } }, edit: async (receipt, rendered) => { if (!onEditReceipt) throw new Error("message send context edit is not configured"); const editedReceipt = await onEditReceipt(receipt, rendered); liveState = { ...liveState, receipt: editedReceipt, lastRendered: rendered }; ctx.preview = liveState; return editedReceipt; }, delete: async (receipt) => { if (!onDeleteReceipt) throw new Error("message send context delete is not configured"); await onDeleteReceipt(receipt); }, commit: async (receipt) => { await onCommitReceipt?.(receipt); }, fail: async (error) => { try { await onSendFailure?.(error); } catch (cleanupError) { log.warn(`message send failure cleanup failed; preserving original send error: ${formatErrorMessage(cleanupError)}`); } } }; try { return await run(ctx); } catch (error) { await ctx.fail(error); throw error; } } async function sendDurableMessageBatchCore(params) { const pendingFinalCompletion = params.deliveryCompletion ? void 0 : resolvePendingFinalDeliveryCompletion(params.payloads); const pendingFinalDelivery = pendingFinalCompletion ? { deliveryCompletion: pendingFinalCompletion, deliveryIntentId: pendingFinalCompletion.deliveryId, durability: "required" } : {}; const ephemeralWriterAuthorities = pendingFinalCompletion ? [] : params.payloads.flatMap((payload) => { const authority = getReplyPayloadMetadata(payload)?.sessionWriterDeliveryAuthority; return authority ? [authority] : []; }); const onPlatformSendDispatch = ephemeralWriterAuthorities.length > 0 ? async () => { for (const authority of ephemeralWriterAuthorities) assertSessionWriterDeliveryAuthorized(authority); await params.onPlatformSendDispatch?.(); } : params.onPlatformSendDispatch; const assertDirectAdapterHandoff = ephemeralWriterAuthorities.length > 0 ? () => { params.assertDirectAdapterHandoff?.(); for (const authority of ephemeralWriterAuthorities) assertSessionWriterDeliveryAuthorized(authority); } : params.assertDirectAdapterHandoff; return await withDurableMessageSendContextCore({ ...params, ...pendingFinalDelivery, onPlatformSendDispatch, assertDirectAdapterHandoff }, async (ctx) => { const rendered = await ctx.render(); const result = await ctx.send(rendered); if (result.status === "sent" || result.status === "suppressed") await ctx.commit(result.receipt); else await ctx.fail(result.error); return result; }); } //#endregion export { withDurableMessageSendContextCore as i, sendDurableMessageBatchCore as n, serializeDurableMessagePayloadOutcomes as r, durableMessageBatchMayHaveReachedRecipient as t };