openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
245 lines (244 loc) • 9.81 kB
JavaScript
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 };