Visitar URL original
Slack: post text and image files as one message instead of a file message followed by a text message · Issue #1015 · vercel/chat · GitHub
Skip to content

Slack: post text and image files as one message instead of a file message followed by a text message #1015

Open

Description

@szachariah-tl

Problem Statement

On Slack, thread.post({ markdown, files }) produces two messages. The adapter's postMessage first calls uploadFiles, which runs files.completeUploadExternal with channel_id and thread_ts, so Slack posts the files as their own thread message; it then posts the text with chat.postMessage. In a thread the reader sees two bubbles per post, the text below the images it belongs to.

Slack also posts the file message asynchronously: completeUploadExternal returns before the file message is visible. When an app posts two messages in a row, each with text and images, the thread can end up ordered text, text, image, image.

Proposed Solution

When a Slack post carries image files, upload them privately and reference them from the text message's blocks, so one chat.postMessage carries both:

  1. files.completeUploadExternal with no channel_id. The files stay private to the bot and Slack posts nothing for them.
  2. chat.postMessage with blocks: a markdown block holding the text, followed by one image block per file using slack_file: { id }.

The post is synchronous, so thread order follows call order, and the markdown rendering of the text is kept. SentMessage.id becomes the real ts of the one message, so callers no longer get the synthetic file-{timestamp} id for a files-only post.

One Slack behaviour the adapter would need to absorb: for a short window after completeUploadExternal returns (about 500 ms in our runs), chat.postMessage rejects the reference with invalid_blocks and an error entry naming slack_file. A short bounded retry on that specific error addresses this.

The markdown block has the same 12,000-character cap as markdown_text, cumulative across the markdown blocks of one payload, so the existing fallback for long text still applies. Non-image files cannot go in an image block, so a post that carries them would keep the current path, or the behaviour could be opt-in through a post option.

Alternatives Considered

Calling the Slack Web API directly from our app: this is what we do today for this one path, which means bypassing the adapter's postMessage and reimplementing its token handling for one message type.

Use Case

import { Chat } from "chat";

// Today this is two Slack messages (files, then text); the request is
// for one message with the text above the images.
await bot.thread(threadId).post({
  markdown: noteText,
  files: [
    { data: screenshotA, filename: "before.png", mimeType: "image/png" },
    { data: screenshotB, filename: "after.png", mimeType: "image/png" },
  ],
});

Priority

Nice to have

Contribution

  • I am willing to help implement this feature

Additional Context

No response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions