chat
Version:
Unified chat abstraction for Slack, Teams, Google Chat, and Discord
269 lines (240 loc) • 7.71 kB
text/mdx
title: Message
description: Normalized message format with text, AST, author, and metadata.
type: reference
Incoming messages are normalized across all platforms into a consistent `Message` object.
```typescript
import { Message } from "chat";
```
## Properties
<TypeTable
type={{
id: {
description: 'Platform-specific message ID.',
type: 'string',
},
threadId: {
description: 'Thread ID in adapter:channel:thread format.',
type: 'string',
},
text: {
description: 'Plain text content with all formatting stripped.',
type: 'string',
},
formatted: {
description: 'mdast AST representation — the canonical format for processing.',
type: 'Root',
},
raw: {
description: 'Original platform-specific payload (escape hatch).',
type: 'unknown',
},
author: {
description: 'Message author info.',
type: 'Author',
},
metadata: {
description: 'Timestamps and edit status.',
type: 'MessageMetadata',
},
attachments: {
description: 'File attachments.',
type: 'Attachment[]',
},
links: {
description: 'Links found in the message, with optional preview metadata.',
type: 'LinkPreview[]',
},
isMention: {
description: 'Whether the bot was @-mentioned in this message.',
type: 'boolean | undefined',
},
subject: {
description: 'Resolves the parent resource (issue, PR) this message is about. Returns null on chat platforms. See [Message Subject](/docs/subject).',
type: 'Promise<MessageSubject | null>',
},
}}
/>
## Author
<TypeTable
type={{
userId: {
description: 'Platform-specific user ID.',
type: 'string',
},
userName: {
description: 'Username/handle for @-mentions.',
type: 'string',
},
fullName: {
description: 'Display name.',
type: 'string',
},
isBot: {
description: 'Whether the author is a bot.',
type: 'boolean | "unknown"',
},
isMe: {
description: 'Whether the author is this bot.',
type: 'boolean',
},
}}
/>
### How `isMe` works
Each adapter detects whether a message came from the bot itself. The detection logic varies by platform:
| Platform | Detection method |
|----------|-----------------|
| Slack | Checks `event.user === botUserId` (primary), then `event.bot_id === botId` (for `bot_message` subtypes). Both IDs are fetched during initialization via `auth.test`. |
| Teams | Checks `activity.from.id === appId` (exact match), then checks if `activity.from.id` ends with `:{appId}` (handles `28:{appId}` format). |
| Google Chat | Checks `message.sender.name === botUserId`. The bot user ID is learned dynamically from message annotations when the bot is first @-mentioned. |
<Callout type="info">
All adapters return `false` if the bot ID isn't known yet. This is a safe default that prevents the bot from ignoring messages it should process.
</Callout>
## MessageMetadata
<TypeTable
type={{
dateSent: {
description: 'When the message was sent.',
type: 'Date',
},
edited: {
description: 'Whether the message has been edited.',
type: 'boolean',
},
editedAt: {
description: 'When the message was last edited.',
type: 'Date | undefined',
},
}}
/>
## Attachment
<TypeTable
type={{
type: {
description: 'Attachment type.',
type: '"image" | "file" | "video" | "audio"',
},
url: {
description: 'URL to the file.',
type: 'string | undefined',
},
data: {
description: 'Binary data (if already fetched).',
type: 'Buffer | Blob | undefined',
},
name: {
description: 'Filename.',
type: 'string | undefined',
},
mimeType: {
description: 'MIME type.',
type: 'string | undefined',
},
size: {
description: 'File size in bytes.',
type: 'number | undefined',
},
'fetchData()': {
description: 'Fetch the attachment data. Handles platform auth automatically.',
type: '() => Promise<Buffer> | undefined',
},
fetchMetadata: {
description: 'Platform-specific IDs for reconstructing fetchData after serialization (e.g. WhatsApp mediaId, Telegram fileId).',
type: 'Record<string, string> | undefined',
},
}}
/>
## LinkPreview
Links found in incoming messages are extracted and exposed as `LinkPreview` objects. On platforms that support it (currently Slack), links pointing to other chat messages include a `fetchMessage()` callback to retrieve the full linked message.
<TypeTable
type={{
url: {
description: 'The URL.',
type: 'string',
},
title: {
description: 'Title from unfurl metadata (if available).',
type: 'string | undefined',
},
description: {
description: 'Description from unfurl metadata (if available).',
type: 'string | undefined',
},
imageUrl: {
description: 'Preview image URL (if available).',
type: 'string | undefined',
},
siteName: {
description: 'Site name, e.g. "Vercel" (if available).',
type: 'string | undefined',
},
'fetchMessage()': {
description: 'Fetch the linked chat message. Available when the URL points to a message on the same platform (e.g. a Slack message link).',
type: '() => Promise<Message> | undefined',
},
}}
/>
<Callout type="info">
When using [`toAiMessages()`](/docs/ai/to-ai-messages), link metadata is automatically appended to the message content. Embedded message links are labeled as `[Embedded message: ...]` so the AI model understands the context.
</Callout>
### Platform support
| Platform | Link extraction | `fetchMessage()` |
|----------|----------------|-------------------|
| Slack | URLs from `rich_text` blocks or `<url>` text patterns | Slack message links (`*.slack.com/archives/...`) |
| Others | Not yet — `links` is always `[]` | — |
## MessageSubject
Returned by `message.subject` on platforms with parent resources. See [Message Subject](/docs/subject) for usage.
<TypeTable
type={{
type: {
description: 'Resource kind, e.g. "issue" or "pull_request".',
type: 'string',
},
id: {
description: 'Resource identifier (e.g. "ENG-123" or "42").',
type: 'string',
},
title: {
description: 'Resource title.',
type: 'string | undefined',
},
description: {
description: 'Full description/body in markdown.',
type: 'string | undefined',
},
status: {
description: 'Current status (e.g. "In Progress", "open").',
type: 'string | undefined',
},
url: {
description: 'Web URL to the resource.',
type: 'string | undefined',
},
author: {
description: 'Resource creator.',
type: '{ id: string; name: string } | undefined',
},
assignee: {
description: 'Current assignee.',
type: '{ id: string; name: string } | undefined',
},
labels: {
description: 'Labels/tags.',
type: 'string[] | undefined',
},
raw: {
description: 'Full platform API response.',
type: 'unknown',
},
}}
/>
## Serialization
Messages can be serialized for workflow engines and external systems.
```typescript
// Serialize
const json = message.toJSON();
// Deserialize
const restored = Message.fromJSON(json);
```
The serialized format converts `Date` fields to ISO strings and omits non-serializable fields like `data` buffers and `fetchData` functions. The `fetchMetadata` field is preserved so that adapters can reconstruct `fetchData` when the message is rehydrated from a queue.