Outbound: avoid empty multi-media fallback sends

This commit is contained in:
liuxiaopai-ai
2026-03-03 16:51:48 +08:00
committed by Shakker
parent efdf2ca0d7
commit bb07b2b93a
2 changed files with 76 additions and 0 deletions

View File

@@ -918,9 +918,59 @@ describe("deliverOutboundPayloads", () => {
text: "caption",
}),
);
expect(logMocks.warn).toHaveBeenCalledWith(
"Plugin outbound adapter does not implement sendMedia; media URLs will be dropped and text fallback will be used",
expect.objectContaining({
channel: "matrix",
mediaCount: 1,
}),
);
expect(results).toEqual([{ channel: "matrix", messageId: "mx-1" }]);
});
it("falls back to one sendText call for multi-media payloads when sendMedia is omitted", async () => {
const sendText = vi.fn().mockResolvedValue({ channel: "matrix", messageId: "mx-2" });
setActivePluginRegistry(
createTestRegistry([
{
pluginId: "matrix",
source: "test",
plugin: createOutboundTestPlugin({
id: "matrix",
outbound: { deliveryMode: "direct", sendText },
}),
},
]),
);
const results = await deliverOutboundPayloads({
cfg: {},
channel: "matrix",
to: "!room:1",
payloads: [
{
text: "caption",
mediaUrls: ["https://example.com/a.png", "https://example.com/b.png"],
},
],
});
expect(sendText).toHaveBeenCalledTimes(1);
expect(sendText).toHaveBeenCalledWith(
expect.objectContaining({
text: "caption",
}),
);
expect(logMocks.warn).toHaveBeenCalledWith(
"Plugin outbound adapter does not implement sendMedia; media URLs will be dropped and text fallback will be used",
expect.objectContaining({
channel: "matrix",
mediaCount: 2,
}),
);
expect(results).toEqual([{ channel: "matrix", messageId: "mx-2" }]);
});
it("emits message_sent failure when delivery errors", async () => {
hookMocks.runner.hasHooks.mockReturnValue(true);
const sendWhatsApp = vi.fn().mockRejectedValue(new Error("downstream failed"));

View File

@@ -97,6 +97,7 @@ type ChannelHandler = {
chunker: Chunker | null;
chunkerMode?: "text" | "markdown";
textChunkLimit?: number;
supportsMedia: boolean;
sendPayload?: (
payload: ReplyPayload,
overrides?: {
@@ -169,6 +170,7 @@ function createPluginHandler(
chunker,
chunkerMode,
textChunkLimit: outbound.textChunkLimit,
supportsMedia: Boolean(sendMedia),
sendPayload: outbound.sendPayload
? async (payload, overrides) =>
outbound.sendPayload!({
@@ -737,6 +739,30 @@ async function deliverOutboundPayloadsCore(
continue;
}
if (!handler.supportsMedia) {
log.warn(
"Plugin outbound adapter does not implement sendMedia; media URLs will be dropped and text fallback will be used",
{
channel,
to,
mediaCount: payloadSummary.mediaUrls.length,
},
);
const fallbackText = payloadSummary.text.trim();
if (!fallbackText) {
continue;
}
const beforeCount = results.length;
await sendTextChunks(fallbackText, sendOverrides);
const messageId = results.at(-1)?.messageId;
emitMessageSent({
success: results.length > beforeCount,
content: payloadSummary.text,
messageId,
});
continue;
}
let first = true;
let lastMessageId: string | undefined;
for (const url of payloadSummary.mediaUrls) {