惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
Microsoft Azure Blog
Microsoft Azure Blog
阮一峰的网络日志
阮一峰的网络日志
宝玉的分享
宝玉的分享
量子位
N
Netflix TechBlog - Medium
M
MIT News - Artificial intelligence
GbyAI
GbyAI
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
博客园 - 叶小钗
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tailwind CSS Blog
Y
Y Combinator Blog
L
LangChain Blog
The Cloudflare Blog
T
The Blog of Author Tim Ferriss
U
Unit 42
Martin Fowler
Martin Fowler
aimingoo的专栏
aimingoo的专栏
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
月光博客
月光博客

Chat SDK Documentation

TanStack AI | Chat SDK History | Chat SDK History | Chat SDK List a vendor-official adapter | Chat SDK Approvals | Chat SDK Vercel Connect | Chat SDK Teams Low-Level APIs | Chat SDK CLI | Chat SDK Platform Adapters | Chat SDK State Adapters | Chat SDK Cards | Chat SDK Getting Started | Chat SDK Introduction | Chat SDK Modals | Chat SDK Slack Low-Level APIs | Chat SDK Streaming | Chat SDK Testing | Chat SDK Overview | Chat SDK toAiMessages | Chat SDK Cards | Chat SDK Overview | Chat SDK Markdown | Chat SDK Modals | Chat SDK AI SDK Tools | Chat SDK Types | Chat SDK Message Subject | Chat SDK Conversation History | Chat SDK Transcripts | Chat SDK Slack bot with Next.js and Redis Actions | Chat SDK
Threads, Messages, and Channels | Chat SDK
Author · 2026-04-06 · via Chat SDK Documentation

Work with threads, messages, and channels across platforms.

A Thread represents a conversation thread on any platform. It provides methods for posting messages, managing subscriptions, and accessing message history.

Thread instances are most often supplied by the SDK to your event handlers. You can also construct one explicitly from a thread ID — useful for cron jobs, workflow steps, or any other context outside an inbound webhook:

const thread = bot.thread("slack:C123ABC:1234567890.123456");
await thread.post("Reminder from a cron job");

For DM-style conversations, use bot.openDM(userIdOrAuthor) instead. It resolves the right channel and thread for user ID formats the SDK can infer.

Post a message

// Plain text
await thread.post("Hello world");

// Markdown (converted to each platform's format)
await thread.post("**Bold** and _italic_ text");

// Structured message with attachments
await thread.post({
  markdown: "Here's a file:",
  files: [{ data: buffer, filename: "report.pdf" }],
});

Subscribe and unsubscribe

Subscriptions persist across restarts (stored in your state adapter). When a non-DM thread is subscribed, all messages route to onSubscribedMessage. DM threads route to onDirectMessage first when a direct message handler is registered.

await thread.subscribe();
await thread.unsubscribe();

const subscribed = await thread.isSubscribed();

Participants

Get the unique human participants in a thread. Returns deduplicated authors, excluding all bots. Useful for deciding whether to subscribe based on how many humans are in the conversation.

bot.onNewMention(async (thread) => {
  const participants = await thread.getParticipants();
  if (participants.length === 1) {
    await thread.subscribe();
    await thread.post("I'm here to help!");
  }
});

bot.onSubscribedMessage(async (thread) => {
  const participants = await thread.getParticipants();
  if (participants.length > 1) {
    await thread.unsubscribe();
    return;
  }
  // respond...
});

Each call fetches the full message history to find all participants. On threads with long history this makes multiple API calls to the platform. Consider checking message.author against a known set before calling getParticipants() on every incoming message.

Typing indicator

await thread.startTyping();

Not all platforms support typing indicators. The call is a no-op on unsupported platforms. See the adapter feature matrix for details.

Message history

Access recent messages or iterate through full history:

// Cached messages from the webhook payload
const recent = thread.recentMessages;

// Newest first (auto-paginates)
for await (const msg of thread.messages) {
  console.log(msg.text);
}

// Oldest first (auto-paginates)
for await (const msg of thread.allMessages) {
  console.log(msg.text);
}

Thread state

Store typed, per-thread state that persists across requests. Pass a generic type parameter to Chat to get typed thread state across all handlers:

interface ThreadState {
  aiMode?: boolean;
  context?: string;
}

const bot = new Chat<typeof adapters, ThreadState>({
  // ...config
});

bot.onNewMention(async (thread) => {
  await thread.setState({ aiMode: true });

  const state = await thread.state; // ThreadState | null
  if (state?.aiMode) {
    // AI mode is enabled
  }
});

State is stored in your state adapter with a 30-day TTL. Use { replace: true } to replace state entirely instead of merging:

await thread.setState({ aiMode: false }, { replace: true });

Scheduled messages

Schedule a message for future delivery. The returned ScheduledMessage includes a cancel() method to abort before it's sent.

const scheduled = await thread.schedule("Reminder: standup in 5 minutes!", {
  postAt: new Date("2026-03-09T09:00:00Z"),
});

// Cancel before it's sent
await scheduled.cancel();

Scheduled messages are currently only supported by the Slack adapter. Other adapters throw NotImplementedError. See the feature matrix for details.

Incoming messages are normalized across platforms into a consistent format:

PropertyTypeDescription
idstringPlatform message ID
threadIdstringThread ID in adapter:channel:thread format
textstringPlain text content
formattedRootmdast AST representation
rawunknownOriginal platform-specific payload
authorAuthorMessage author info
metadataMessageMetadataTimestamps and edit status
attachmentsAttachment[] (optional)File attachments
isMentionboolean (optional)Whether the bot was @-mentioned
interface Author {
  userId: string;
  userName: string;
  fullName: string;
  isBot: boolean | "unknown";
  isMe: boolean; // true if message is from the bot itself
}

For richer user info (email, avatar), use chat.getUser():

const user = await bot.getUser(message.author);
console.log(user?.email); // "alice@company.com"

Sent messages

When you post a message, you get back a SentMessage with methods to edit, delete, and react:

const sent = await thread.post("Processing...");
// Do some work...
await sent.edit("Done!");

// Or delete
await sent.delete();

// Add/remove reactions
await sent.addReaction(emoji.check);
await sent.removeReaction(emoji.check);

A Channel represents the container that holds threads (e.g., a Slack channel, a Teams conversation). Navigate to a channel from a thread or get one directly:

// From a thread
const channel = thread.channel;

// Directly by ID
const channel = bot.channel("slack:C123ABC");

List threads

Iterate threads in a channel, most recently active first:

for await (const thread of channel.threads()) {
  console.log(thread.rootMessage.text, thread.replyCount);
}

Channel messages

Iterate top-level messages (not thread replies):

for await (const msg of channel.messages) {
  console.log(msg.text);
}

Post to a channel

Post a top-level message (not inside a thread):

await channel.post("Hello channel!");

Channel metadata

const info = await channel.fetchMetadata();
console.log(info.name, info.memberCount);

All thread IDs follow the pattern {adapter}:{channel}:{thread}:

  • Slack: slack:C123ABC:1234567890.123456
  • Teams: teams:{base64(conversationId)}:{base64(serviceUrl)}
  • Google Chat: gchat:spaces/ABC123:{base64(threadName)}
  • Discord: discord:{guildId}:{channelId}/{messageId}

You typically don't need to construct these yourself — they're provided by the SDK in event handlers.

The logger option is optional — if omitted, Chat SDK uses ConsoleLogger("info") by default. Each adapter also creates its own child logger automatically.

// Use defaults (ConsoleLogger at "info" level)
const bot = new Chat({
  // ...
});

// Or set a specific log level
const bot = new Chat({
  // ...
  logger: "debug", // "debug" | "info" | "warn" | "error" | "silent"
});

// Or use a custom ConsoleLogger for child loggers
import { ConsoleLogger } from "chat";

const logger = new ConsoleLogger("info");
const bot = new Chat({
  // ...
  logger,
});

You can pass child loggers to adapters for prefixed log output, but adapters create their own child loggers by default:

createSlackAdapter({
  logger: logger.child("slack"), // optional — auto-created if omitted
});