fix(discord): ignore empty components on media send

This commit is contained in:
Peter Steinberger
2026-03-22 21:45:33 +00:00
parent 77bdb33735
commit af4f2a8028
2 changed files with 41 additions and 4 deletions

View File

@@ -64,6 +64,15 @@ export const discordMessagingActionRuntime = {
unpinMessageDiscord,
};
function hasDiscordComponentObjectKeys(value: unknown): value is Record<string, unknown> {
return Boolean(
value &&
typeof value === "object" &&
!Array.isArray(value) &&
Object.keys(value as Record<string, unknown>).length > 0,
);
}
function parseDiscordMessageLink(link: string) {
const normalized = link.trim();
const match = normalized.match(
@@ -299,10 +308,9 @@ export async function handleDiscordMessagingAction(
const asVoice = params.asVoice === true;
const silent = params.silent === true;
const rawComponents = params.components;
const componentSpec =
rawComponents && typeof rawComponents === "object" && !Array.isArray(rawComponents)
? discordMessagingActionRuntime.readDiscordComponentSpec(rawComponents)
: null;
const componentSpec = hasDiscordComponentObjectKeys(rawComponents)
? discordMessagingActionRuntime.readDiscordComponentSpec(rawComponents)
: null;
const components: DiscordSendComponents | undefined =
Array.isArray(rawComponents) || typeof rawComponents === "function"
? (rawComponents as DiscordSendComponents)

View File

@@ -46,6 +46,7 @@ const discordSendMocks = {
removeOwnReactionsDiscord: vi.fn(async () => ({ removed: ["👍"] })),
removeReactionDiscord: vi.fn(async () => ({})),
searchMessagesDiscord: vi.fn(async () => ({})),
sendDiscordComponentMessage: vi.fn(async () => ({})),
sendMessageDiscord: vi.fn(async () => ({})),
sendPollDiscord: vi.fn(async () => ({})),
sendStickerDiscord: vi.fn(async () => ({})),
@@ -71,6 +72,7 @@ const {
removeOwnReactionsDiscord,
removeReactionDiscord,
searchMessagesDiscord,
sendDiscordComponentMessage,
sendMessageDiscord,
sendPollDiscord,
sendVoiceMessageDiscord,
@@ -366,6 +368,33 @@ describe("handleDiscordMessagingAction", () => {
);
});
it("ignores empty components objects for regular media sends", async () => {
sendMessageDiscord.mockClear();
sendDiscordComponentMessage.mockClear();
await handleDiscordMessagingAction(
"sendMessage",
{
to: "channel:123",
content: "hello",
mediaUrl: "/tmp/image.png",
components: {},
},
enableAllActions,
{ mediaLocalRoots: ["/tmp/agent-root"] },
);
expect(sendDiscordComponentMessage).not.toHaveBeenCalled();
expect(sendMessageDiscord).toHaveBeenCalledWith(
"channel:123",
"hello",
expect.objectContaining({
mediaUrl: "/tmp/image.png",
mediaLocalRoots: ["/tmp/agent-root"],
}),
);
});
it("rejects voice messages that include content", async () => {
await expect(
handleDiscordMessagingAction(