chat 4.13.1 → 4.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -31
- package/dist/{chunk-WKJEG4FE.js → chunk-THM4ACIE.js} +12 -4
- package/dist/chunk-THM4ACIE.js.map +1 -0
- package/dist/index.d.ts +409 -415
- package/dist/index.js +63 -29
- package/dist/index.js.map +1 -1
- package/dist/{jsx-runtime-COVsDskT.d.ts → jsx-runtime-Bdt1Dwzf.d.ts} +71 -71
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/docs/actions.mdx +98 -0
- package/docs/adapters/discord.mdx +217 -0
- package/docs/adapters/gchat.mdx +232 -0
- package/docs/adapters/github.mdx +225 -0
- package/docs/adapters/index.mdx +110 -0
- package/docs/adapters/linear.mdx +207 -0
- package/docs/adapters/meta.json +12 -0
- package/docs/adapters/slack.mdx +293 -0
- package/docs/adapters/teams.mdx +225 -0
- package/docs/api/cards.mdx +217 -0
- package/docs/api/channel.mdx +176 -0
- package/docs/api/chat.mdx +469 -0
- package/docs/api/index.mdx +29 -0
- package/docs/api/markdown.mdx +235 -0
- package/docs/api/message.mdx +163 -0
- package/docs/api/meta.json +14 -0
- package/docs/api/modals.mdx +222 -0
- package/docs/api/postable-message.mdx +166 -0
- package/docs/api/thread.mdx +186 -0
- package/docs/cards.mdx +213 -0
- package/docs/direct-messages.mdx +56 -0
- package/docs/emoji.mdx +77 -0
- package/docs/ephemeral-messages.mdx +77 -0
- package/docs/error-handling.mdx +147 -0
- package/docs/files.mdx +77 -0
- package/docs/getting-started.mdx +12 -0
- package/docs/guides/code-review-hono.mdx +248 -0
- package/docs/guides/discord-nuxt.mdx +237 -0
- package/docs/guides/meta.json +4 -0
- package/docs/guides/slack-nextjs.mdx +245 -0
- package/docs/index.mdx +92 -0
- package/docs/meta.json +20 -0
- package/docs/modals.mdx +208 -0
- package/docs/posting-messages.mdx +177 -0
- package/docs/slash-commands.mdx +110 -0
- package/docs/state/index.mdx +31 -0
- package/docs/state/ioredis.mdx +81 -0
- package/docs/state/memory.mdx +52 -0
- package/docs/state/meta.json +9 -0
- package/docs/state/redis.mdx +93 -0
- package/docs/streaming.mdx +99 -0
- package/docs/usage.mdx +338 -0
- package/package.json +10 -10
- package/dist/chunk-WKJEG4FE.js.map +0 -1
package/docs/modals.mdx
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Modals
|
|
3
|
+
description: Collect structured user input through modal dialogs with text fields, dropdowns, and validation.
|
|
4
|
+
type: guide
|
|
5
|
+
prerequisites:
|
|
6
|
+
- /docs/actions
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Modals open form dialogs in response to button clicks or [slash commands](/docs/slash-commands). They support text inputs, dropdowns, radio buttons, and server-side validation. Currently supported on Slack.
|
|
10
|
+
|
|
11
|
+
## Open a modal
|
|
12
|
+
|
|
13
|
+
Modals are opened from [action handlers](/docs/actions) or [slash command handlers](/docs/slash-commands) using `event.openModal()`:
|
|
14
|
+
|
|
15
|
+
```tsx title="lib/bot.tsx" lineNumbers
|
|
16
|
+
import { Modal, TextInput, Select, SelectOption } from "chat";
|
|
17
|
+
|
|
18
|
+
bot.onAction("feedback", async (event) => {
|
|
19
|
+
await event.openModal(
|
|
20
|
+
<Modal
|
|
21
|
+
callbackId="feedback_form"
|
|
22
|
+
title="Send Feedback"
|
|
23
|
+
submitLabel="Send"
|
|
24
|
+
closeLabel="Cancel"
|
|
25
|
+
notifyOnClose
|
|
26
|
+
>
|
|
27
|
+
<TextInput
|
|
28
|
+
id="message"
|
|
29
|
+
label="Your Feedback"
|
|
30
|
+
placeholder="Tell us what you think..."
|
|
31
|
+
multiline
|
|
32
|
+
/>
|
|
33
|
+
<Select id="category" label="Category" placeholder="Select a category">
|
|
34
|
+
<SelectOption label="Bug Report" value="bug" />
|
|
35
|
+
<SelectOption label="Feature Request" value="feature" />
|
|
36
|
+
<SelectOption label="General" value="general" />
|
|
37
|
+
</Select>
|
|
38
|
+
<TextInput
|
|
39
|
+
id="email"
|
|
40
|
+
label="Email (optional)"
|
|
41
|
+
placeholder="your@email.com"
|
|
42
|
+
optional
|
|
43
|
+
/>
|
|
44
|
+
</Modal>
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Components
|
|
50
|
+
|
|
51
|
+
### Modal
|
|
52
|
+
|
|
53
|
+
The top-level container for the form.
|
|
54
|
+
|
|
55
|
+
| Prop | Type | Description |
|
|
56
|
+
|------|------|-------------|
|
|
57
|
+
| `callbackId` | `string` | Identifier for matching submit/close handlers |
|
|
58
|
+
| `title` | `string` | Modal title |
|
|
59
|
+
| `submitLabel` | `string` (optional) | Submit button text (defaults to "Submit") |
|
|
60
|
+
| `closeLabel` | `string` (optional) | Cancel button text (defaults to "Cancel") |
|
|
61
|
+
| `notifyOnClose` | `boolean` (optional) | Fire `onModalClose` when user cancels |
|
|
62
|
+
| `privateMetadata` | `string` (optional) | Custom context passed through to handlers |
|
|
63
|
+
|
|
64
|
+
### TextInput
|
|
65
|
+
|
|
66
|
+
A text field for user input.
|
|
67
|
+
|
|
68
|
+
| Prop | Type | Description |
|
|
69
|
+
|------|------|-------------|
|
|
70
|
+
| `id` | `string` | Field identifier (key in `event.values`) |
|
|
71
|
+
| `label` | `string` | Field label |
|
|
72
|
+
| `placeholder` | `string` (optional) | Placeholder text |
|
|
73
|
+
| `initialValue` | `string` (optional) | Pre-filled value |
|
|
74
|
+
| `multiline` | `boolean` (optional) | Render as textarea |
|
|
75
|
+
| `optional` | `boolean` (optional) | Allow empty submission |
|
|
76
|
+
| `maxLength` | `number` (optional) | Maximum character count |
|
|
77
|
+
|
|
78
|
+
### Select
|
|
79
|
+
|
|
80
|
+
A dropdown for selecting a single option.
|
|
81
|
+
|
|
82
|
+
| Prop | Type | Description |
|
|
83
|
+
|------|------|-------------|
|
|
84
|
+
| `id` | `string` | Field identifier |
|
|
85
|
+
| `label` | `string` | Field label |
|
|
86
|
+
| `placeholder` | `string` (optional) | Placeholder text |
|
|
87
|
+
| `initialOption` | `string` (optional) | Pre-selected value |
|
|
88
|
+
| `optional` | `boolean` (optional) | Allow empty submission |
|
|
89
|
+
|
|
90
|
+
### RadioSelect
|
|
91
|
+
|
|
92
|
+
A radio button group for mutually exclusive options.
|
|
93
|
+
|
|
94
|
+
| Prop | Type | Description |
|
|
95
|
+
|------|------|-------------|
|
|
96
|
+
| `id` | `string` | Field identifier |
|
|
97
|
+
| `label` | `string` | Field label |
|
|
98
|
+
| `initialOption` | `string` (optional) | Pre-selected value |
|
|
99
|
+
| `optional` | `boolean` (optional) | Allow empty submission |
|
|
100
|
+
|
|
101
|
+
### SelectOption
|
|
102
|
+
|
|
103
|
+
An option for `Select` or `RadioSelect`.
|
|
104
|
+
|
|
105
|
+
| Prop | Type | Description |
|
|
106
|
+
|------|------|-------------|
|
|
107
|
+
| `label` | `string` | Display text |
|
|
108
|
+
| `value` | `string` | Value passed to handler |
|
|
109
|
+
| `description` | `string` (optional) | Help text below the label |
|
|
110
|
+
|
|
111
|
+
## Handle submissions
|
|
112
|
+
|
|
113
|
+
Register a handler with `onModalSubmit` using the same `callbackId`:
|
|
114
|
+
|
|
115
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
116
|
+
bot.onModalSubmit("feedback_form", async (event) => {
|
|
117
|
+
const { message, category, email } = event.values;
|
|
118
|
+
|
|
119
|
+
// Validate input — return errors to show in the modal
|
|
120
|
+
if (!message || message.length < 5) {
|
|
121
|
+
return {
|
|
122
|
+
action: "errors",
|
|
123
|
+
errors: { message: "Feedback must be at least 5 characters" },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Post confirmation to the original thread
|
|
128
|
+
if (event.relatedThread) {
|
|
129
|
+
await event.relatedThread.post(`Feedback received! Category: ${category}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Update the message that triggered the modal
|
|
133
|
+
if (event.relatedMessage) {
|
|
134
|
+
await event.relatedMessage.edit("Feedback submitted!");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Return nothing (or { action: "close" }) to close the modal
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Response types
|
|
142
|
+
|
|
143
|
+
| Response | Description |
|
|
144
|
+
|----------|-------------|
|
|
145
|
+
| `undefined` or `{ action: "close" }` | Close the modal |
|
|
146
|
+
| `{ action: "errors", errors: { fieldId: "message" } }` | Show validation errors on specific fields |
|
|
147
|
+
| `{ action: "update", modal: ModalElement }` | Replace the modal content |
|
|
148
|
+
| `{ action: "push", modal: ModalElement }` | Push a new modal view onto the stack |
|
|
149
|
+
|
|
150
|
+
### ModalSubmitEvent
|
|
151
|
+
|
|
152
|
+
| Property | Type | Description |
|
|
153
|
+
|----------|------|-------------|
|
|
154
|
+
| `callbackId` | `string` | Modal identifier |
|
|
155
|
+
| `viewId` | `string` | Platform view ID |
|
|
156
|
+
| `values` | `Record<string, string>` | Form field values keyed by input `id` |
|
|
157
|
+
| `user` | `Author` | The user who submitted |
|
|
158
|
+
| `privateMetadata` | `string` (optional) | Custom context from the Modal component |
|
|
159
|
+
| `relatedThread` | `Thread` (optional) | Thread where the modal was triggered |
|
|
160
|
+
| `relatedMessage` | `SentMessage` (optional) | Message with the button that opened the modal |
|
|
161
|
+
| `relatedChannel` | `Channel` (optional) | Channel where the modal was triggered (from [slash commands](/docs/slash-commands)) |
|
|
162
|
+
| `adapter` | `Adapter` | The platform adapter |
|
|
163
|
+
| `raw` | `unknown` | Platform-specific payload |
|
|
164
|
+
|
|
165
|
+
## Handle cancellation
|
|
166
|
+
|
|
167
|
+
Optionally handle when users cancel a modal. Requires `notifyOnClose` on the `Modal` component:
|
|
168
|
+
|
|
169
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
170
|
+
bot.onModalClose("feedback_form", async (event) => {
|
|
171
|
+
console.log(`${event.user.userName} cancelled the feedback form`);
|
|
172
|
+
|
|
173
|
+
if (event.relatedThread) {
|
|
174
|
+
await event.relatedThread.post("No worries, let us know if you change your mind!");
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Pass context with privateMetadata
|
|
180
|
+
|
|
181
|
+
Use `privateMetadata` to carry context from the button click through to the submit handler:
|
|
182
|
+
|
|
183
|
+
```tsx title="lib/bot.tsx" lineNumbers
|
|
184
|
+
bot.onAction("report", async (event) => {
|
|
185
|
+
await event.openModal(
|
|
186
|
+
<Modal
|
|
187
|
+
callbackId="report_form"
|
|
188
|
+
title="Report Bug"
|
|
189
|
+
submitLabel="Submit"
|
|
190
|
+
privateMetadata={JSON.stringify({
|
|
191
|
+
reportType: event.value,
|
|
192
|
+
threadId: event.threadId,
|
|
193
|
+
})}
|
|
194
|
+
>
|
|
195
|
+
<TextInput id="title" label="Bug Title" />
|
|
196
|
+
<TextInput id="steps" label="Steps to Reproduce" multiline />
|
|
197
|
+
</Modal>
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
bot.onModalSubmit("report_form", async (event) => {
|
|
202
|
+
const metadata = event.privateMetadata
|
|
203
|
+
? JSON.parse(event.privateMetadata)
|
|
204
|
+
: {};
|
|
205
|
+
|
|
206
|
+
console.log(metadata.reportType); // "bug"
|
|
207
|
+
});
|
|
208
|
+
```
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Posting Messages
|
|
3
|
+
description: Different ways to render and send messages with thread.post().
|
|
4
|
+
type: guide
|
|
5
|
+
prerequisites:
|
|
6
|
+
- /docs/usage
|
|
7
|
+
related:
|
|
8
|
+
- /docs/cards
|
|
9
|
+
- /docs/streaming
|
|
10
|
+
- /docs/files
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
`thread.post()` accepts several message formats, each suited to different use cases. Choose the format that best fits your content — from plain strings to structured AST to rich interactive cards.
|
|
14
|
+
|
|
15
|
+
## Plain text
|
|
16
|
+
|
|
17
|
+
The simplest option. Pass a string and it goes through as-is to the platform.
|
|
18
|
+
|
|
19
|
+
```typescript title="lib/bot.ts"
|
|
20
|
+
await thread.post("Hello world");
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This sends the string directly without any formatting conversion.
|
|
24
|
+
|
|
25
|
+
## Markdown
|
|
26
|
+
|
|
27
|
+
Pass a `{ markdown }` object to have the SDK convert standard markdown to each platform's native format — mrkdwn for Slack, HTML for Teams, and so on.
|
|
28
|
+
|
|
29
|
+
```typescript title="lib/bot.ts"
|
|
30
|
+
await thread.post({
|
|
31
|
+
markdown: "**Bold**, _italic_, and `code`",
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Under the hood, the SDK parses the markdown into an mdast AST, then each adapter converts it to the platform's format.
|
|
36
|
+
|
|
37
|
+
## AST builders
|
|
38
|
+
|
|
39
|
+
For programmatic control over message formatting, use the mdast AST builder functions exported from `chat`. This is the recommended approach for most use cases — it gives you fine-grained control without the overhead of card rendering.
|
|
40
|
+
|
|
41
|
+
```typescript title="lib/bot.ts"
|
|
42
|
+
import { root, paragraph, text, strong, link } from "chat";
|
|
43
|
+
|
|
44
|
+
await thread.post({
|
|
45
|
+
ast: root([
|
|
46
|
+
paragraph([
|
|
47
|
+
strong([text("Deployment complete")]),
|
|
48
|
+
text(" — "),
|
|
49
|
+
link("https://example.com", [text("View site")]),
|
|
50
|
+
]),
|
|
51
|
+
]),
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Available builders
|
|
56
|
+
|
|
57
|
+
| Builder | Description | Example |
|
|
58
|
+
|---------|-------------|---------|
|
|
59
|
+
| `root(children)` | Root node (required wrapper) | `root([paragraph([...])])` |
|
|
60
|
+
| `paragraph(children)` | Paragraph block | `paragraph([text("Hello")])` |
|
|
61
|
+
| `text(value)` | Plain text | `text("Hello")` |
|
|
62
|
+
| `strong(children)` | **Bold** text | `strong([text("bold")])` |
|
|
63
|
+
| `emphasis(children)` | _Italic_ text | `emphasis([text("italic")])` |
|
|
64
|
+
| `strikethrough(children)` | ~~Strikethrough~~ text | `strikethrough([text("done")])` |
|
|
65
|
+
| `inlineCode(value)` | `Inline code` | `inlineCode("const x = 1")` |
|
|
66
|
+
| `codeBlock(value, lang?)` | Fenced code block | `codeBlock("const x = 1", "ts")` |
|
|
67
|
+
| `link(url, children, title?)` | Hyperlink | `link("https://...", [text("click")])` |
|
|
68
|
+
| `blockquote(children)` | Block quote | `blockquote([paragraph([text("...")])])` |
|
|
69
|
+
|
|
70
|
+
### Parsing markdown to AST
|
|
71
|
+
|
|
72
|
+
You can also parse a markdown string into an AST, manipulate it, then send it:
|
|
73
|
+
|
|
74
|
+
```typescript title="lib/bot.ts"
|
|
75
|
+
import { parseMarkdown, stringifyMarkdown } from "chat";
|
|
76
|
+
|
|
77
|
+
const ast = parseMarkdown("**Hello** world");
|
|
78
|
+
// Manipulate the AST...
|
|
79
|
+
await thread.post({ ast });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Cards
|
|
83
|
+
|
|
84
|
+
When you need interactive elements like buttons, dropdowns, or structured layouts, use cards. Cards render natively on each platform — Block Kit on Slack, Adaptive Cards on Teams, and Google Chat Cards.
|
|
85
|
+
|
|
86
|
+
### Function syntax
|
|
87
|
+
|
|
88
|
+
Use the function-call API for type-safe card construction:
|
|
89
|
+
|
|
90
|
+
```typescript title="lib/bot.ts"
|
|
91
|
+
import { Card, Text, Actions, Button } from "chat";
|
|
92
|
+
|
|
93
|
+
await thread.post(
|
|
94
|
+
Card({
|
|
95
|
+
title: "Order #1234",
|
|
96
|
+
children: [
|
|
97
|
+
Text("Your order has been received!"),
|
|
98
|
+
Actions([
|
|
99
|
+
Button({ id: "approve", label: "Approve", style: "primary" }),
|
|
100
|
+
Button({ id: "reject", label: "Reject", style: "danger" }),
|
|
101
|
+
]),
|
|
102
|
+
],
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### JSX syntax
|
|
108
|
+
|
|
109
|
+
You can also use JSX if you configure the `chat` JSX runtime:
|
|
110
|
+
|
|
111
|
+
```json title="tsconfig.json"
|
|
112
|
+
{
|
|
113
|
+
"compilerOptions": {
|
|
114
|
+
"jsx": "react-jsx",
|
|
115
|
+
"jsxImportSource": "chat"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```tsx title="lib/bot.tsx"
|
|
121
|
+
import { Card, CardText, Actions, Button } from "chat";
|
|
122
|
+
|
|
123
|
+
await thread.post(
|
|
124
|
+
<Card title="Order #1234">
|
|
125
|
+
<CardText>Your order has been received!</CardText>
|
|
126
|
+
<Actions>
|
|
127
|
+
<Button id="approve" style="primary">Approve</Button>
|
|
128
|
+
<Button id="reject" style="danger">Reject</Button>
|
|
129
|
+
</Actions>
|
|
130
|
+
</Card>
|
|
131
|
+
);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
<Callout type="warn">
|
|
135
|
+
The JSX syntax requires `jsxImportSource: "chat"` in your `tsconfig.json` (or a per-file `/** @jsxImportSource chat */` pragma). Without this, TypeScript won't recognize the card JSX types. If you run into type issues with JSX, use the function-call syntax instead — it produces the same output with better type inference.
|
|
136
|
+
</Callout>
|
|
137
|
+
|
|
138
|
+
See the [Cards](/docs/cards) page for the full list of card components.
|
|
139
|
+
|
|
140
|
+
## Streaming
|
|
141
|
+
|
|
142
|
+
Pass an `AsyncIterable<string>` (like the AI SDK's `textStream`) to stream a message in real time. The SDK uses platform-native streaming where available and falls back to post-then-edit on other platforms.
|
|
143
|
+
|
|
144
|
+
```typescript title="lib/bot.ts"
|
|
145
|
+
import { streamText } from "ai";
|
|
146
|
+
|
|
147
|
+
const result = streamText({ model, prompt: message.text });
|
|
148
|
+
await thread.post(result.textStream);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
See the [Streaming](/docs/streaming) page for details on platform behavior and configuration.
|
|
152
|
+
|
|
153
|
+
## Attachments and files
|
|
154
|
+
|
|
155
|
+
Any structured message format (`markdown`, `ast`, or `card`) supports `files` for uploading attachments alongside the message:
|
|
156
|
+
|
|
157
|
+
```typescript title="lib/bot.ts"
|
|
158
|
+
await thread.post({
|
|
159
|
+
markdown: "Here's the report:",
|
|
160
|
+
files: [{ data: buffer, filename: "report.pdf" }],
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
See the [Files](/docs/files) page for more on attachments.
|
|
165
|
+
|
|
166
|
+
## Choosing a format
|
|
167
|
+
|
|
168
|
+
| Format | Use when | Example |
|
|
169
|
+
|--------|----------|---------|
|
|
170
|
+
| Plain string | Simple, unformatted text | Status updates, acknowledgements |
|
|
171
|
+
| `{ markdown }` | You have a markdown string (e.g. from a template) | Notifications with links and formatting |
|
|
172
|
+
| `{ ast }` | You need programmatic formatting control | Dynamic messages built from data |
|
|
173
|
+
| Card (function) | You need buttons, fields, or structured layouts | Approval flows, dashboards |
|
|
174
|
+
| Card (JSX) | Same as above, with JSX syntax preference | Same use cases as function cards |
|
|
175
|
+
| `AsyncIterable` | Streaming AI responses | Chat with LLMs |
|
|
176
|
+
|
|
177
|
+
For most cases, **AST builders** give the best balance of control and simplicity. Reach for **cards** when you need interactive elements like buttons or dropdowns.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Slash Commands
|
|
3
|
+
description: Handle slash command invocations and respond with messages or modals.
|
|
4
|
+
type: guide
|
|
5
|
+
prerequisites:
|
|
6
|
+
- /docs/getting-started
|
|
7
|
+
related:
|
|
8
|
+
- /docs/modals
|
|
9
|
+
- /docs/adapters/slack
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Slash commands let users invoke your bot with `/command` syntax. Register handlers with `onSlashCommand` to respond.
|
|
13
|
+
|
|
14
|
+
## Handle a specific command
|
|
15
|
+
|
|
16
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
17
|
+
bot.onSlashCommand("/status", async (event) => {
|
|
18
|
+
await event.channel.post("All systems operational!");
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Handle multiple commands
|
|
23
|
+
|
|
24
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
25
|
+
bot.onSlashCommand(["/help", "/info"], async (event) => {
|
|
26
|
+
await event.channel.post(`You invoked ${event.command}`);
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Catch-all handler
|
|
31
|
+
|
|
32
|
+
Register a handler without a command to catch all slash commands:
|
|
33
|
+
|
|
34
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
35
|
+
bot.onSlashCommand(async (event) => {
|
|
36
|
+
console.log(`Command: ${event.command}, Args: ${event.text}`);
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## SlashCommandEvent
|
|
41
|
+
|
|
42
|
+
The `event` object passed to slash command handlers:
|
|
43
|
+
|
|
44
|
+
| Property | Type | Description |
|
|
45
|
+
|----------|------|-------------|
|
|
46
|
+
| `command` | `string` | The command name (e.g., `/status`) |
|
|
47
|
+
| `text` | `string` | Arguments after the command |
|
|
48
|
+
| `user` | `Author` | The user who invoked the command |
|
|
49
|
+
| `channel` | `Channel` | The channel where the command was invoked |
|
|
50
|
+
| `adapter` | `Adapter` | The platform adapter |
|
|
51
|
+
| `triggerId` | `string` (optional) | Platform trigger ID for opening modals |
|
|
52
|
+
| `openModal` | `(modal) => Promise<{ viewId: string } \| undefined>` | Open a modal dialog |
|
|
53
|
+
| `raw` | `unknown` | Platform-specific payload |
|
|
54
|
+
|
|
55
|
+
## Respond to a command
|
|
56
|
+
|
|
57
|
+
Use `event.channel` to post messages:
|
|
58
|
+
|
|
59
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
60
|
+
bot.onSlashCommand("/greet", async (event) => {
|
|
61
|
+
// Public message to the channel
|
|
62
|
+
await event.channel.post(`Hello, ${event.user.fullName}!`);
|
|
63
|
+
|
|
64
|
+
// Ephemeral message (only visible to the user)
|
|
65
|
+
await event.channel.postEphemeral(
|
|
66
|
+
event.user,
|
|
67
|
+
"This message is just for you!",
|
|
68
|
+
{ fallbackToDM: false }
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Open a modal
|
|
74
|
+
|
|
75
|
+
Use `event.openModal()` to open a [modal](/docs/modals) in response to a slash command:
|
|
76
|
+
|
|
77
|
+
```tsx title="lib/bot.tsx" lineNumbers
|
|
78
|
+
import { Modal, TextInput, Select, SelectOption } from "chat";
|
|
79
|
+
|
|
80
|
+
bot.onSlashCommand("/feedback", async (event) => {
|
|
81
|
+
const result = await event.openModal(
|
|
82
|
+
<Modal callbackId="feedback_form" title="Send Feedback" submitLabel="Send">
|
|
83
|
+
<TextInput id="message" label="Your Feedback" multiline />
|
|
84
|
+
<Select id="category" label="Category">
|
|
85
|
+
<SelectOption label="Bug" value="bug" />
|
|
86
|
+
<SelectOption label="Feature" value="feature" />
|
|
87
|
+
</Select>
|
|
88
|
+
</Modal>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (!result) {
|
|
92
|
+
await event.channel.post("Couldn't open the feedback form. Please try again.");
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
<Callout type="info">
|
|
98
|
+
When a modal is opened from a slash command, the submit handler receives `relatedChannel` instead of `relatedThread`. Use this to post back to the channel where the command was invoked.
|
|
99
|
+
</Callout>
|
|
100
|
+
|
|
101
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
102
|
+
bot.onModalSubmit("feedback_form", async (event) => {
|
|
103
|
+
const { message, category } = event.values;
|
|
104
|
+
|
|
105
|
+
// Post to the channel where the slash command was invoked
|
|
106
|
+
if (event.relatedChannel) {
|
|
107
|
+
await event.relatedChannel.post(`Feedback received! Category: ${category}`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Overview
|
|
3
|
+
description: Pluggable state adapters for thread subscriptions, distributed locking, and caching.
|
|
4
|
+
type: overview
|
|
5
|
+
prerequisites:
|
|
6
|
+
- /docs/getting-started
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
State adapters handle persistent storage for thread subscriptions, distributed locks (to prevent duplicate processing), and caching. You must provide a state adapter when creating a `Chat` instance.
|
|
10
|
+
|
|
11
|
+
## Comparison
|
|
12
|
+
|
|
13
|
+
| Adapter | Package | Persistence | Multi-instance | Use case |
|
|
14
|
+
|---------|---------|-------------|----------------|----------|
|
|
15
|
+
| [Redis](/docs/state/redis) | `@chat-adapter/state-redis` | Yes | Yes | Production (recommended) |
|
|
16
|
+
| [ioredis](/docs/state/ioredis) | `@chat-adapter/state-ioredis` | Yes | Yes | Production (Cluster/Sentinel) |
|
|
17
|
+
| [Memory](/docs/state/memory) | `@chat-adapter/state-memory` | No | No | Development and testing |
|
|
18
|
+
|
|
19
|
+
## What state adapters manage
|
|
20
|
+
|
|
21
|
+
### Thread subscriptions
|
|
22
|
+
|
|
23
|
+
When your bot calls `thread.subscribe()`, the state adapter persists that subscription. On subsequent webhooks, the SDK checks subscriptions to route messages to `onSubscribedMessage` handlers. With Redis, subscriptions survive restarts and work across multiple instances.
|
|
24
|
+
|
|
25
|
+
### Distributed locking
|
|
26
|
+
|
|
27
|
+
When a webhook arrives, the SDK acquires a lock on the thread to prevent duplicate processing. This is critical for serverless deployments where multiple instances may receive the same event.
|
|
28
|
+
|
|
29
|
+
### Caching
|
|
30
|
+
|
|
31
|
+
State adapters provide key-value storage with TTL for thread state (`thread.setState()`), message deduplication, and other internal caching.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ioredis
|
|
3
|
+
description: Alternative Redis state adapter using ioredis with Cluster and Sentinel support.
|
|
4
|
+
type: reference
|
|
5
|
+
prerequisites:
|
|
6
|
+
- /docs/getting-started
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
An alternative Redis state adapter using [ioredis](https://www.npmjs.com/package/ioredis). Use this if you already have ioredis in your project or need Redis Cluster/Sentinel support.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh title="Terminal"
|
|
14
|
+
pnpm add @chat-adapter/state-ioredis
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
20
|
+
import { Chat } from "chat";
|
|
21
|
+
import { createIORedisState } from "@chat-adapter/state-ioredis";
|
|
22
|
+
|
|
23
|
+
const bot = new Chat({
|
|
24
|
+
userName: "mybot",
|
|
25
|
+
adapters: { /* ... */ },
|
|
26
|
+
state: createIORedisState({
|
|
27
|
+
url: process.env.REDIS_URL!,
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Using an existing client
|
|
33
|
+
|
|
34
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
35
|
+
import Redis from "ioredis";
|
|
36
|
+
|
|
37
|
+
const client = new Redis("redis://localhost:6379");
|
|
38
|
+
|
|
39
|
+
const state = createIORedisState({ client });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
| Option | Required | Description |
|
|
45
|
+
|--------|----------|-------------|
|
|
46
|
+
| `url` | Yes* | Redis connection URL |
|
|
47
|
+
| `client` | No | Existing `ioredis` client instance |
|
|
48
|
+
| `keyPrefix` | No | Prefix for all keys (default: `"chat-sdk"`) |
|
|
49
|
+
|
|
50
|
+
*Either `url` or `client` is required.
|
|
51
|
+
|
|
52
|
+
## When to use ioredis vs redis
|
|
53
|
+
|
|
54
|
+
**Use `@chat-adapter/state-ioredis` when:**
|
|
55
|
+
|
|
56
|
+
- You already use ioredis in your project
|
|
57
|
+
- You need Redis Cluster support
|
|
58
|
+
- You need Redis Sentinel support
|
|
59
|
+
- You prefer the ioredis API
|
|
60
|
+
|
|
61
|
+
**Use `@chat-adapter/state-redis` when:**
|
|
62
|
+
|
|
63
|
+
- You want the official Redis client
|
|
64
|
+
- You're starting a new project
|
|
65
|
+
- You don't need Cluster or Sentinel
|
|
66
|
+
|
|
67
|
+
## Key structure
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
{keyPrefix}:subscriptions - SET of subscribed thread IDs
|
|
71
|
+
{keyPrefix}:lock:{threadId} - Lock key with TTL
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Features
|
|
75
|
+
|
|
76
|
+
- Persistent subscriptions across restarts
|
|
77
|
+
- Distributed locking across multiple instances
|
|
78
|
+
- Automatic reconnection
|
|
79
|
+
- Redis Cluster support
|
|
80
|
+
- Redis Sentinel support
|
|
81
|
+
- Key prefix namespacing
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Memory
|
|
3
|
+
description: In-memory state adapter for local development and testing.
|
|
4
|
+
type: reference
|
|
5
|
+
prerequisites:
|
|
6
|
+
- /docs/getting-started
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
An in-memory state adapter for development and testing. Zero configuration required.
|
|
10
|
+
|
|
11
|
+
<Callout type="warn">
|
|
12
|
+
Only use the memory adapter for local development and testing. State is lost on restart and locks don't work across multiple instances. For production, use [Redis](/docs/state/redis) or [ioredis](/docs/state/ioredis).
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```sh title="Terminal"
|
|
18
|
+
pnpm add @chat-adapter/state-memory
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```typescript title="lib/bot.ts" lineNumbers
|
|
24
|
+
import { Chat } from "chat";
|
|
25
|
+
import { createMemoryState } from "@chat-adapter/state-memory";
|
|
26
|
+
|
|
27
|
+
const bot = new Chat({
|
|
28
|
+
userName: "mybot",
|
|
29
|
+
adapters: { /* ... */ },
|
|
30
|
+
state: createMemoryState(),
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
No configuration options are needed.
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- Thread subscriptions (in-memory)
|
|
39
|
+
- Locking (single-process only)
|
|
40
|
+
- Zero configuration
|
|
41
|
+
|
|
42
|
+
## Limitations
|
|
43
|
+
|
|
44
|
+
- **Not suitable for production** — state is lost on restart
|
|
45
|
+
- **Single process only** — locks don't work across multiple instances
|
|
46
|
+
- **No persistence** — subscriptions reset when the process restarts
|
|
47
|
+
|
|
48
|
+
## When to use
|
|
49
|
+
|
|
50
|
+
- Local development
|
|
51
|
+
- Unit testing
|
|
52
|
+
- Quick prototyping
|