

























@@ -1,10 +1,4 @@
1-import { sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
2-import {
3-chunkByParagraph,
4-chunkMarkdownTextWithMode,
5-resolveChunkMode,
6-resolveTextChunkLimit,
7-} from "../../auto-reply/chunk.js";
1+import { resolveChunkMode, resolveTextChunkLimit } from "../../auto-reply/chunk.js";
82import type { ReplyPayload } from "../../auto-reply/types.js";
93import { loadChannelOutboundAdapter } from "../../channels/plugins/outbound/load.js";
104import type {
@@ -45,6 +39,11 @@ import {
4539} from "./delivery-queue.js";
4640import type { OutboundDeliveryFormattingOptions } from "./formatting.js";
4741import type { OutboundIdentity } from "./identity.js";
42+import {
43+planOutboundMediaMessageUnits,
44+planOutboundTextMessageUnits,
45+type OutboundMessageSendOverrides,
46+} from "./message-plan.js";
4847import type { DeliveryMirror } from "./mirror.js";
4948import {
5049createOutboundPayloadPlan,
@@ -82,14 +81,8 @@ async function loadChannelBootstrapRuntime() {
8281return await channelBootstrapRuntimePromise;
8382}
848385-type Chunker = (
86-text: string,
87-limit: number,
88-ctx?: { formatting?: OutboundDeliveryFormattingOptions },
89-) => string[];
90-9184type ChannelHandler = {
92-chunker: Chunker | null;
85+chunker: ChannelOutboundAdapter["chunker"] | null;
9386chunkerMode?: "text" | "markdown";
9487textChunkLimit?: number;
9588supportsMedia: boolean;
@@ -111,45 +104,25 @@ type ChannelHandler = {
111104resolveEffectiveTextChunkLimit?: (fallbackLimit?: number) => number | undefined;
112105sendPayload?: (
113106payload: ReplyPayload,
114-overrides?: {
115-replyToId?: string | null;
116-threadId?: string | number | null;
117-audioAsVoice?: boolean;
118-},
107+overrides?: OutboundMessageSendOverrides,
119108) => Promise<OutboundDeliveryResult>;
120109sendFormattedText?: (
121110text: string,
122-overrides?: {
123-replyToId?: string | null;
124-threadId?: string | number | null;
125-audioAsVoice?: boolean;
126-},
111+overrides?: OutboundMessageSendOverrides,
127112) => Promise<OutboundDeliveryResult[]>;
128113sendFormattedMedia?: (
129114caption: string,
130115mediaUrl: string,
131-overrides?: {
132-replyToId?: string | null;
133-threadId?: string | number | null;
134-audioAsVoice?: boolean;
135-},
116+overrides?: OutboundMessageSendOverrides,
136117) => Promise<OutboundDeliveryResult>;
137118sendText: (
138119text: string,
139-overrides?: {
140-replyToId?: string | null;
141-threadId?: string | number | null;
142-audioAsVoice?: boolean;
143-},
120+overrides?: OutboundMessageSendOverrides,
144121) => Promise<OutboundDeliveryResult>;
145122sendMedia: (
146123caption: string,
147124mediaUrl: string,
148-overrides?: {
149-replyToId?: string | null;
150-threadId?: string | number | null;
151-audioAsVoice?: boolean;
152-},
125+overrides?: OutboundMessageSendOverrides,
153126) => Promise<OutboundDeliveryResult>;
154127};
155128@@ -203,11 +176,16 @@ function createPluginHandler(
203176const chunkerMode = outbound.chunkerMode;
204177const resolveCtx = (overrides?: {
205178replyToId?: string | null;
179+replyToIdSource?: "explicit" | "implicit";
206180threadId?: string | number | null;
207181audioAsVoice?: boolean;
208182}): Omit<ChannelOutboundContext, "text" | "mediaUrl"> => ({
209183 ...baseCtx,
210184replyToId: overrides && "replyToId" in overrides ? overrides.replyToId : baseCtx.replyToId,
185+replyToIdSource:
186+overrides && "replyToIdSource" in overrides
187+ ? overrides.replyToIdSource
188+ : baseCtx.replyToIdSource,
211189threadId: overrides && "threadId" in overrides ? overrides.threadId : baseCtx.threadId,
212190audioAsVoice: overrides?.audioAsVoice,
213191});
@@ -841,55 +819,27 @@ async function deliverOutboundPayloadsCore(
841819replyToId: params.replyToId,
842820replyToMode: params.replyToMode,
843821});
844-const chunkTextForDelivery = (text: string, limit: number): string[] =>
845-params.formatting
846- ? handler.chunker!(text, limit, { formatting: params.formatting })
847- : handler.chunker!(text, limit);
848822849-const sendTextChunks = async (
850-text: string,
851-overrides?: {
852-replyToId?: string | null;
853-replyToIdSource?: "explicit" | "implicit";
854-threadId?: string | number | null;
855-audioAsVoice?: boolean;
856-},
857-) => {
858-const consumeReplyTo = <T extends NonNullable<typeof overrides>>(value: T): T =>
859-applyReplyToConsumption(value, {
860-consumeImplicitReply: value.replyToIdSource === "implicit",
861-});
862-throwIfAborted(abortSignal);
863-if (!handler.chunker || textLimit === undefined) {
864-results.push(await handler.sendText(text, consumeReplyTo(overrides ?? {})));
865-return;
866-}
867-if (chunkMode === "newline") {
868-const mode = handler.chunkerMode ?? "text";
869-const blockChunks =
870-mode === "markdown"
871- ? chunkMarkdownTextWithMode(text, textLimit, "newline")
872- : chunkByParagraph(text, textLimit);
873-874-if (!blockChunks.length && text) {
875-blockChunks.push(text);
876-}
877-for (const blockChunk of blockChunks) {
878-const chunks = chunkTextForDelivery(blockChunk, textLimit);
879-if (!chunks.length && blockChunk) {
880-chunks.push(blockChunk);
881-}
882-for (const chunk of chunks) {
883-throwIfAborted(abortSignal);
884-results.push(await handler.sendText(chunk, consumeReplyTo(overrides ?? {})));
885-}
823+const sendTextChunks = async (text: string, overrides: OutboundMessageSendOverrides = {}) => {
824+const units = planOutboundTextMessageUnits({
825+ text,
826+ overrides,
827+chunker: handler.chunker,
828+chunkerMode: handler.chunkerMode,
829+ textLimit,
830+ chunkMode,
831+formatting: params.formatting,
832+consumeReplyTo: (value) =>
833+applyReplyToConsumption(value, {
834+consumeImplicitReply: value.replyToIdSource === "implicit",
835+}),
836+});
837+for (const unit of units) {
838+if (unit.kind !== "text") {
839+continue;
886840}
887-return;
888-}
889-const chunks = chunkTextForDelivery(text, textLimit);
890-for (const chunk of chunks) {
891841throwIfAborted(abortSignal);
892-results.push(await handler.sendText(chunk, consumeReplyTo(overrides ?? {})));
842+results.push(await handler.sendText(unit.text, unit.overrides));
893843}
894844};
895845const normalizedPayloads = normalizePayloadsForChannelDelivery(outboundPayloadPlan, handler);
@@ -951,14 +901,16 @@ async function deliverOutboundPayloadsCore(
951901952902params.onPayload?.(payloadSummary);
953903const replyToResolution = resolveCurrentReplyTo(effectivePayload);
954-const sendOverrides = {
904+const sendOverrides: OutboundMessageSendOverrides = {
955905replyToId: replyToResolution.replyToId,
956906replyToIdSource: replyToResolution.source,
957-threadId: params.threadId ?? undefined,
958-audioAsVoice: effectivePayload.audioAsVoice === true ? true : undefined,
959-forceDocument: params.forceDocument,
907+...(params.threadId !== undefined ? { threadId: params.threadId } : {}),
908+...(effectivePayload.audioAsVoice === true ? { audioAsVoice: true } : {}),
909+...(params.forceDocument !== undefined ? { forceDocument: params.forceDocument } : {}),
960910};
961-const applySendReplyToConsumption = <T extends typeof sendOverrides>(overrides: T): T =>
911+const applySendReplyToConsumption = <T extends OutboundMessageSendOverrides>(
912+overrides: T,
913+): T =>
962914applyReplyToConsumption(overrides, {
963915consumeImplicitReply: replyToResolution.source === "implicit",
964916});
@@ -1074,32 +1026,24 @@ async function deliverOutboundPayloadsCore(
10741026let firstMessageId: string | undefined;
10751027let lastMessageId: string | undefined;
10761028const beforeCount = results.length;
1077-await sendMediaWithLeadingCaption({
1029+const mediaUnits = planOutboundMediaMessageUnits({
10781030mediaUrls: payloadSummary.mediaUrls,
10791031caption: payloadSummary.text,
1080-send: async ({ mediaUrl, caption }) => {
1081-throwIfAborted(abortSignal);
1082-if (handler.sendFormattedMedia) {
1083-const delivery = await handler.sendFormattedMedia(
1084-caption ?? "",
1085-mediaUrl,
1086-applySendReplyToConsumption(sendOverrides),
1087-);
1088-results.push(delivery);
1089-firstMessageId ??= delivery.messageId;
1090-lastMessageId = delivery.messageId;
1091-return;
1092-}
1093-const delivery = await handler.sendMedia(
1094-caption ?? "",
1095-mediaUrl,
1096-applySendReplyToConsumption(sendOverrides),
1097-);
1098-results.push(delivery);
1099-firstMessageId ??= delivery.messageId;
1100-lastMessageId = delivery.messageId;
1101-},
1032+overrides: sendOverrides,
1033+consumeReplyTo: applySendReplyToConsumption,
11021034});
1035+for (const unit of mediaUnits) {
1036+if (unit.kind !== "media") {
1037+continue;
1038+}
1039+throwIfAborted(abortSignal);
1040+const delivery = handler.sendFormattedMedia
1041+ ? await handler.sendFormattedMedia(unit.caption ?? "", unit.mediaUrl, unit.overrides)
1042+ : await handler.sendMedia(unit.caption ?? "", unit.mediaUrl, unit.overrides);
1043+results.push(delivery);
1044+firstMessageId ??= delivery.messageId;
1045+lastMessageId = delivery.messageId;
1046+}
11031047await maybePinDeliveredMessage({
11041048 handler,
11051049payload: effectivePayload,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。