Summary
Slack top-level DMs can be handled with chat.onDirectMessage, but there does not appear to be a high-level Chat SDK path for streaming a response as a Slack assistant-style thread reply to the incoming DM message.
This is related to, but narrower than, #268 / #292. PR #292 intentionally preserves empty threadTs for top-level DMs so openDM() subscription matching continues to work, and it guards Slack API calls against empty threadTs. That makes sense.
The remaining gap is native Slack streaming from a fresh message.im event:
chat.onDirectMessage(async (thread, message) => {
const result = streamText(/* ... */);
await thread.post(result.fullStream);
});
For a top-level Slack DM, the thread ID is still slack:D...:. Slack chat.startStream requires a real thread_ts, and Slack's streaming docs say streamed messages should reply to a user request. For this case, the correct Slack thread_ts appears to be the incoming user message event.ts.
Current behavior
In @chat-adapter/slack@4.28.1, top-level DM events are represented with an empty thread timestamp:
const threadTs = isDM ? event.thread_ts || "" : event.thread_ts || event.ts;
Then thread.post(result.fullStream) eventually calls the Slack adapter's stream() with a thread ID that has no thread context. The current adapter correctly rejects this:
Slack streaming requires a valid thread context (non-empty threadTs)
Desired behavior
There should be a supported SDK-level way to stream a reply to the specific incoming top-level DM message, without app code needing to construct Slack adapter thread IDs manually.
Ideally this would allow the simple handler above to work, by using the current/incoming message context to stream against message.raw.ts for top-level message.im events while preserving the existing empty-thread identity for DM conversation/subscription matching.
Alternative API shapes would also work, for example:
message.reply(result.fullStream)
thread.replyTo(message, result.fullStream)
- an adapter option passed internally from
ThreadImpl.handleStream() such as replyToMessageTs
- documented guidance if this is intentionally out of scope
Workaround
The workaround is to drop below the Chat SDK abstraction and call the Slack adapter directly:
import { fromFullStream } from "chat";
const raw = message.raw as {
channel?: string;
ts?: string;
team?: string;
team_id?: string;
};
await adapter.stream(`slack:${raw.channel}:${raw.ts}`, fromFullStream(result.fullStream), {
recipientUserId: message.author.userId,
recipientTeamId: raw.team_id ?? raw.team,
});
This works, but it depends on Slack adapter thread ID internals and bypasses the normal thread.post(stream) abstraction.
Why this matters
For Slack assistant-style apps, a very common user behavior is to DM the bot directly. The expected UX is that the user's top-level DM becomes the new chat thread and the bot streams a response in that thread. Today that seems possible only by using adapter internals.
Environment
chat: 4.28.1
@chat-adapter/slack: 4.28.1
- Slack Events API:
message.im
- Slack Assistants API/native streaming enabled
Summary
Slack top-level DMs can be handled with
chat.onDirectMessage, but there does not appear to be a high-level Chat SDK path for streaming a response as a Slack assistant-style thread reply to the incoming DM message.This is related to, but narrower than, #268 / #292. PR #292 intentionally preserves empty
threadTsfor top-level DMs soopenDM()subscription matching continues to work, and it guards Slack API calls against emptythreadTs. That makes sense.The remaining gap is native Slack streaming from a fresh
message.imevent:For a top-level Slack DM, the thread ID is still
slack:D...:. Slackchat.startStreamrequires a realthread_ts, and Slack's streaming docs say streamed messages should reply to a user request. For this case, the correct Slackthread_tsappears to be the incoming user messageevent.ts.Current behavior
In
@chat-adapter/slack@4.28.1, top-level DM events are represented with an empty thread timestamp:Then
thread.post(result.fullStream)eventually calls the Slack adapter'sstream()with a thread ID that has no thread context. The current adapter correctly rejects this:Desired behavior
There should be a supported SDK-level way to stream a reply to the specific incoming top-level DM message, without app code needing to construct Slack adapter thread IDs manually.
Ideally this would allow the simple handler above to work, by using the current/incoming message context to stream against
message.raw.tsfor top-levelmessage.imevents while preserving the existing empty-thread identity for DM conversation/subscription matching.Alternative API shapes would also work, for example:
message.reply(result.fullStream)thread.replyTo(message, result.fullStream)ThreadImpl.handleStream()such asreplyToMessageTsWorkaround
The workaround is to drop below the Chat SDK abstraction and call the Slack adapter directly:
This works, but it depends on Slack adapter thread ID internals and bypasses the normal
thread.post(stream)abstraction.Why this matters
For Slack assistant-style apps, a very common user behavior is to DM the bot directly. The expected UX is that the user's top-level DM becomes the new chat thread and the bot streams a response in that thread. Today that seems possible only by using adapter internals.
Environment
chat:4.28.1@chat-adapter/slack:4.28.1message.im