From 37ffd3463b8b334e51fe38b25b80f66bf885702c Mon Sep 17 00:00:00 2001 From: Solvely-Colin <211764741+Solvely-Colin@users.noreply.github.com> Date: Tue, 27 Jan 2026 23:08:59 +0000 Subject: [PATCH] discord: support forum post creation via thread-create Allow thread-create to create Discord forum posts by accepting initial content and appliedTagIds, and using POST /channels/{id}/threads. --- src/agents/tools/discord-actions-messaging.ts | 17 ++++++++++++-- .../plugins/actions/discord/handle-action.ts | 6 +++++ src/discord/send.messages.ts | 22 ++++++++++++++++++- src/discord/send.types.ts | 7 ++++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/agents/tools/discord-actions-messaging.ts b/src/agents/tools/discord-actions-messaging.ts index f90fb60de..562baa50e 100644 --- a/src/agents/tools/discord-actions-messaging.ts +++ b/src/agents/tools/discord-actions-messaging.ts @@ -279,18 +279,31 @@ export async function handleDiscordMessagingAction( const channelId = resolveChannelId(); const name = readStringParam(params, "name", { required: true }); const messageId = readStringParam(params, "messageId"); + // Optional initial content (required for forum posts). + const content = readStringParam(params, "content") ?? undefined; + // Optional applied tag ids (forum posts). + const appliedTagIds = Array.isArray(params.appliedTagIds) + ? params.appliedTagIds.filter((x) => typeof x === "string" && x.trim()) + : undefined; const autoArchiveMinutesRaw = params.autoArchiveMinutes; const autoArchiveMinutes = typeof autoArchiveMinutesRaw === "number" && Number.isFinite(autoArchiveMinutesRaw) ? autoArchiveMinutesRaw : undefined; + const thread = accountId ? await createThreadDiscord( channelId, - { name, messageId, autoArchiveMinutes }, + { name, messageId, autoArchiveMinutes, content, appliedTagIds }, { accountId }, ) - : await createThreadDiscord(channelId, { name, messageId, autoArchiveMinutes }); + : await createThreadDiscord(channelId, { + name, + messageId, + autoArchiveMinutes, + content, + appliedTagIds, + }); return jsonResult({ ok: true, thread }); } case "threadList": { diff --git a/src/channels/plugins/actions/discord/handle-action.ts b/src/channels/plugins/actions/discord/handle-action.ts index 90e95d14d..94189eef0 100644 --- a/src/channels/plugins/actions/discord/handle-action.ts +++ b/src/channels/plugins/actions/discord/handle-action.ts @@ -180,6 +180,10 @@ export async function handleDiscordMessageAction( if (action === "thread-create") { const name = readStringParam(params, "threadName", { required: true }); const messageId = readStringParam(params, "messageId"); + // Optional initial post content (required for forum post creation). + const content = readStringParam(params, "message"); + // Optional forum tag ids. + const appliedTagIds = readStringArrayParam(params, "appliedTagIds"); const autoArchiveMinutes = readNumberParam(params, "autoArchiveMin", { integer: true, }); @@ -190,6 +194,8 @@ export async function handleDiscordMessageAction( channelId: resolveChannelId(), name, messageId, + content, + appliedTagIds, autoArchiveMinutes, }, cfg, diff --git a/src/discord/send.messages.ts b/src/discord/send.messages.ts index 93bc378d7..b859f1674 100644 --- a/src/discord/send.messages.ts +++ b/src/discord/send.messages.ts @@ -94,10 +94,30 @@ export async function createThreadDiscord( ) { const rest = resolveDiscordRest(opts); const body: Record = { name: payload.name }; + if (payload.autoArchiveMinutes) { body.auto_archive_duration = payload.autoArchiveMinutes; } - const route = Routes.threads(channelId, payload.messageId); + + // Forum posts (channel type 15) require an initial message payload. + // If content is provided, create the thread with an initial post. + if (payload.content) { + body.message = { content: payload.content }; + if (Array.isArray(payload.appliedTagIds) && payload.appliedTagIds.length) { + body.applied_tags = payload.appliedTagIds; + } + // NOTE: discord-api-types Routes doesn't currently expose a helper for + // POST /channels/{channel.id}/threads (it only exposes listing archived threads), + // so use the raw route string. + const route = `/channels/${channelId}/threads`; + return await rest.post(route, { body }); + } + + // Regular threads: create from an existing message when messageId is provided, + // otherwise create a standard thread in the channel. + const route = payload.messageId + ? Routes.threads(channelId, payload.messageId) + : `/channels/${channelId}/threads`; return await rest.post(route, { body }); } diff --git a/src/discord/send.types.ts b/src/discord/send.types.ts index 5ea63366a..edb901a4f 100644 --- a/src/discord/send.types.ts +++ b/src/discord/send.types.ts @@ -67,9 +67,16 @@ export type DiscordMessageEdit = { }; export type DiscordThreadCreate = { + /** Optional message id to start a thread from (for classic threads). */ messageId?: string; + /** Thread / forum post title. */ name: string; + /** Auto-archive duration in minutes. */ autoArchiveMinutes?: number; + /** Optional initial post content (required for forum post creation). */ + content?: string; + /** Optional forum tag ids (applied tags). */ + appliedTagIds?: string[]; }; export type DiscordThreadList = {