chat 4.28.1 → 4.29.0

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/dist/index.js CHANGED
@@ -61,131 +61,9 @@ import {
61
61
  toPlainText,
62
62
  walkAst
63
63
  } from "./chunk-V25FKIIL.js";
64
-
65
- // src/ai.ts
66
- var TEXT_MIME_PREFIXES = [
67
- "text/",
68
- "application/json",
69
- "application/xml",
70
- "application/javascript",
71
- "application/typescript",
72
- "application/yaml",
73
- "application/x-yaml",
74
- "application/toml"
75
- ];
76
- function isTextMimeType(mimeType) {
77
- return TEXT_MIME_PREFIXES.some(
78
- (prefix) => mimeType === prefix || mimeType.startsWith(prefix)
79
- );
80
- }
81
- async function attachmentToPart(att) {
82
- if (att.type === "image") {
83
- if (att.fetchData) {
84
- try {
85
- const buffer = await att.fetchData();
86
- const mimeType = att.mimeType ?? "image/png";
87
- return {
88
- type: "file",
89
- data: `data:${mimeType};base64,${buffer.toString("base64")}`,
90
- mediaType: mimeType,
91
- filename: att.name
92
- };
93
- } catch (error) {
94
- console.error("toAiMessages: failed to fetch image data", error);
95
- return null;
96
- }
97
- }
98
- return null;
99
- }
100
- if (att.type === "file" && att.mimeType && isTextMimeType(att.mimeType)) {
101
- if (att.fetchData) {
102
- try {
103
- const buffer = await att.fetchData();
104
- return {
105
- type: "file",
106
- data: `data:${att.mimeType};base64,${buffer.toString("base64")}`,
107
- filename: att.name,
108
- mediaType: att.mimeType
109
- };
110
- } catch (error) {
111
- console.error("toAiMessages: failed to fetch file data", error);
112
- return null;
113
- }
114
- }
115
- return null;
116
- }
117
- return null;
118
- }
119
- async function toAiMessages(messages, options) {
120
- const includeNames = options?.includeNames ?? false;
121
- const transformMessage = options?.transformMessage;
122
- const onUnsupported = options?.onUnsupportedAttachment ?? ((att) => {
123
- console.warn(
124
- `toAiMessages: unsupported attachment type "${att.type}"${att.name ? ` (${att.name})` : ""} \u2014 skipped`
125
- );
126
- });
127
- const sorted = [...messages].sort(
128
- (a, b) => (a.metadata.dateSent?.getTime() ?? 0) - (b.metadata.dateSent?.getTime() ?? 0)
129
- );
130
- const filtered = sorted.filter((msg) => msg.text.trim());
131
- const results = await Promise.all(
132
- filtered.map(async (msg) => {
133
- const role = msg.author.isMe ? "assistant" : "user";
134
- let textContent = includeNames && role === "user" ? `[${msg.author.userName}]: ${msg.text}` : msg.text;
135
- if (msg.links && msg.links.length > 0) {
136
- const linkParts = msg.links.map((link2) => {
137
- const parts = link2.fetchMessage ? [`[Embedded message: ${link2.url}]`] : [link2.url];
138
- if (link2.title) {
139
- parts.push(`Title: ${link2.title}`);
140
- }
141
- if (link2.description) {
142
- parts.push(`Description: ${link2.description}`);
143
- }
144
- if (link2.siteName) {
145
- parts.push(`Site: ${link2.siteName}`);
146
- }
147
- return parts.join("\n");
148
- }).join("\n\n");
149
- textContent += `
150
-
151
- Links:
152
- ${linkParts}`;
153
- }
154
- let aiMessage;
155
- if (role === "user") {
156
- const attachmentParts = [];
157
- for (const att of msg.attachments ?? []) {
158
- const part = await attachmentToPart(att);
159
- if (part) {
160
- attachmentParts.push(part);
161
- } else if (att.type === "video" || att.type === "audio") {
162
- onUnsupported(att, msg);
163
- }
164
- }
165
- if (attachmentParts.length > 0) {
166
- aiMessage = {
167
- role,
168
- content: [
169
- { type: "text", text: textContent },
170
- ...attachmentParts
171
- ]
172
- };
173
- } else {
174
- aiMessage = { role, content: textContent };
175
- }
176
- } else {
177
- aiMessage = { role, content: textContent };
178
- }
179
- if (transformMessage) {
180
- return { result: await transformMessage(aiMessage, msg), source: msg };
181
- }
182
- return { result: aiMessage, source: msg };
183
- })
184
- );
185
- return results.filter(
186
- (r) => r.result != null
187
- ).map((r) => r.result);
188
- }
64
+ import {
65
+ toAiMessages
66
+ } from "./chunk-HD375J7S.js";
189
67
 
190
68
  // src/channel.ts
191
69
  import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2 } from "@workflow/serde";
@@ -2515,9 +2393,13 @@ var Chat = class {
2515
2393
  /**
2516
2394
  * Register a handler for direct messages.
2517
2395
  *
2518
- * Called when a message is received in a DM thread that is not subscribed.
2519
- * If no `onDirectMessage` handlers are registered, DMs fall through to
2520
- * `onNewMention` for backward compatibility.
2396
+ * Called for every message received in a DM thread when at least one
2397
+ * direct message handler is registered. Direct message handlers run before
2398
+ * `onSubscribedMessage`, `onNewMention`, and pattern handlers.
2399
+ *
2400
+ * If no `onDirectMessage` handlers are registered, DMs continue through
2401
+ * normal routing. Unsubscribed DMs fall through to `onNewMention` for
2402
+ * backward compatibility.
2521
2403
  *
2522
2404
  * @param handler - Handler called for DM messages
2523
2405
  *
@@ -3534,7 +3416,7 @@ var Chat = class {
3534
3416
  * - Deduplication: Same message may arrive multiple times (e.g., Slack sends
3535
3417
  * both `message` and `app_mention` events, GChat sends direct webhook + Pub/Sub)
3536
3418
  * - Bot filtering: Messages from the bot itself are skipped
3537
- * - Concurrency: Controlled by `concurrency` config (drop, queue, debounce, concurrent)
3419
+ * - Concurrency: Controlled by `concurrency` config (drop, queue, debounce, burst, concurrent)
3538
3420
  */
3539
3421
  async handleIncomingMessage(adapter, threadId, message) {
3540
3422
  setMessageAdapter(message, adapter);
@@ -3583,7 +3465,7 @@ var Chat = class {
3583
3465
  await this.handleConcurrent(adapter, threadId, message);
3584
3466
  return;
3585
3467
  }
3586
- if (strategy === "queue" || strategy === "debounce") {
3468
+ if (strategy === "queue" || strategy === "debounce" || strategy === "burst") {
3587
3469
  await this.handleQueueOrDebounce(
3588
3470
  adapter,
3589
3471
  threadId,
@@ -3702,6 +3584,25 @@ var Chat = class {
3702
3584
  debounceMs
3703
3585
  });
3704
3586
  await this.debounceLoop(lock, adapter, threadId, lockKey);
3587
+ } else if (strategy === "burst") {
3588
+ await this._stateAdapter.enqueue(
3589
+ lockKey,
3590
+ {
3591
+ message,
3592
+ enqueuedAt: Date.now(),
3593
+ expiresAt: Date.now() + queueEntryTtlMs
3594
+ },
3595
+ maxQueueSize
3596
+ );
3597
+ this.logger.info("message-debouncing", {
3598
+ threadId,
3599
+ lockKey,
3600
+ messageId: message.id,
3601
+ debounceMs
3602
+ });
3603
+ await sleep(debounceMs);
3604
+ await this._stateAdapter.extendLock(lock, DEFAULT_LOCK_TTL_MS);
3605
+ await this.drainQueue(lock, adapter, threadId, lockKey);
3705
3606
  } else {
3706
3607
  await this.dispatchToHandlers(adapter, threadId, message);
3707
3608
  await this.drainQueue(lock, adapter, threadId, lockKey);
@@ -779,4 +779,4 @@ declare namespace JSX {
779
779
  }
780
780
  }
781
781
 
782
- export { type CardProps as $, type ActionsComponent as A, type ButtonComponent as B, type ChatElement as C, type DividerComponent as D, type ExternalSelectComponent as E, type FieldComponent as F, type FieldsElement as G, type ImageElement as H, type ImageComponent as I, type LinkButtonElement as J, type LinkButtonOptions as K, type LinkButtonComponent as L, type ModalElement as M, type LinkElement as N, type SectionElement as O, type TableAlignment as P, type TableElement as Q, type RadioSelectComponent as R, type SelectOptionElement as S, type TextComponent as T, type TableOptions as U, type TextElement as V, type TextStyle as W, type ButtonProps as X, type CardJSXElement as Y, type CardJSXProps as Z, type CardLinkProps as _, type CardElement as a, type ContainerProps as a0, type DividerProps as a1, type ExternalSelectProps as a2, type FieldProps as a3, type ImageProps as a4, type LinkButtonProps as a5, type ModalProps as a6, type SelectOptionProps as a7, type SelectProps as a8, type TextInputProps as a9, type TextProps as aa, type ExternalSelectElement as ab, type ExternalSelectOptions as ac, type ModalChild as ad, type ModalOptions as ae, type RadioSelectElement as af, type RadioSelectOptions as ag, type SelectElement as ah, type SelectOptions as ai, type TextInputElement as aj, type TextInputOptions as ak, type TableProps as al, type TableComponent as am, isCardLinkProps as an, jsx as ao, jsxs as ap, jsxDEV as aq, Fragment as ar, JSX as as, type CardChild as b, type CardComponent as c, cardChildToFallbackText as d, type CardLinkComponent as e, type FieldsComponent as f, fromReactElement as g, isJSX as h, isCardElement as i, type SectionComponent as j, Table as k, toModalElement as l, fromReactModalElement as m, isModalElement as n, type ModalComponent as o, type SelectComponent as p, type SelectOptionComponent as q, type TextInputComponent as r, type ActionsElement as s, toCardElement as t, type ButtonElement as u, type ButtonOptions as v, type ButtonStyle as w, type CardOptions as x, type DividerElement as y, type FieldElement as z };
782
+ export { type DividerProps as $, type ActionsComponent as A, type ButtonComponent as B, type CardElement as C, type DividerComponent as D, type ExternalSelectComponent as E, type FieldComponent as F, type LinkButtonOptions as G, type LinkElement as H, type ImageComponent as I, type SectionElement as J, type TableAlignment as K, type LinkButtonComponent as L, type ModalComponent as M, type TableElement as N, type TableOptions as O, type TextElement as P, type TextStyle as Q, type RadioSelectComponent as R, type SectionComponent as S, type TextComponent as T, type ButtonProps as U, type CardJSXElement as V, type CardJSXProps as W, type CardLinkProps as X, type CardProps as Y, type ChatElement as Z, type ContainerProps as _, type CardChild as a, type ExternalSelectProps as a0, type FieldProps as a1, type ImageProps as a2, type LinkButtonProps as a3, type ModalProps as a4, type SelectOptionProps as a5, type SelectProps as a6, type TextInputProps as a7, type TextProps as a8, type ExternalSelectElement as a9, type ExternalSelectOptions as aa, type ModalChild as ab, type ModalElement as ac, type ModalOptions as ad, type RadioSelectElement as ae, type RadioSelectOptions as af, type SelectElement as ag, type SelectOptionElement as ah, type SelectOptions as ai, type TextInputElement as aj, type TextInputOptions as ak, type TableProps as al, type TableComponent as am, isCardLinkProps as an, jsx as ao, jsxs as ap, jsxDEV as aq, Fragment as ar, JSX as as, type CardComponent as b, cardChildToFallbackText as c, type CardLinkComponent as d, type FieldsComponent as e, fromReactElement as f, isJSX as g, Table as h, isCardElement as i, toModalElement as j, fromReactModalElement as k, isModalElement as l, type SelectComponent as m, type SelectOptionComponent as n, type TextInputComponent as o, type ActionsElement as p, type ButtonElement as q, type ButtonOptions as r, type ButtonStyle as s, toCardElement as t, type CardOptions as u, type DividerElement as v, type FieldElement as w, type FieldsElement as x, type ImageElement as y, type LinkButtonElement as z };
@@ -1 +1 @@
1
- export { A as ActionsComponent, B as ButtonComponent, X as ButtonProps, c as CardComponent, Y as CardJSXElement, Z as CardJSXProps, e as CardLinkComponent, _ as CardLinkProps, $ as CardProps, C as ChatElement, a0 as ContainerProps, D as DividerComponent, a1 as DividerProps, E as ExternalSelectComponent, a2 as ExternalSelectProps, F as FieldComponent, a3 as FieldProps, f as FieldsComponent, ar as Fragment, I as ImageComponent, a4 as ImageProps, as as JSX, L as LinkButtonComponent, a5 as LinkButtonProps, o as ModalComponent, a6 as ModalProps, R as RadioSelectComponent, j as SectionComponent, p as SelectComponent, q as SelectOptionComponent, a7 as SelectOptionProps, a8 as SelectProps, am as TableComponent, al as TableProps, T as TextComponent, r as TextInputComponent, a9 as TextInputProps, aa as TextProps, an as isCardLinkProps, h as isJSX, ao as jsx, aq as jsxDEV, ap as jsxs, t as toCardElement, l as toModalElement } from './jsx-runtime-DxGwoLu2.js';
1
+ export { A as ActionsComponent, B as ButtonComponent, U as ButtonProps, b as CardComponent, V as CardJSXElement, W as CardJSXProps, d as CardLinkComponent, X as CardLinkProps, Y as CardProps, Z as ChatElement, _ as ContainerProps, D as DividerComponent, $ as DividerProps, E as ExternalSelectComponent, a0 as ExternalSelectProps, F as FieldComponent, a1 as FieldProps, e as FieldsComponent, ar as Fragment, I as ImageComponent, a2 as ImageProps, as as JSX, L as LinkButtonComponent, a3 as LinkButtonProps, M as ModalComponent, a4 as ModalProps, R as RadioSelectComponent, S as SectionComponent, m as SelectComponent, n as SelectOptionComponent, a5 as SelectOptionProps, a6 as SelectProps, am as TableComponent, al as TableProps, T as TextComponent, o as TextInputComponent, a7 as TextInputProps, a8 as TextProps, an as isCardLinkProps, g as isJSX, ao as jsx, aq as jsxDEV, ap as jsxs, t as toCardElement, j as toModalElement } from './jsx-runtime-CFq1K_Ve.js';
package/docs/adapters.mdx CHANGED
@@ -8,12 +8,13 @@ prerequisites:
8
8
 
9
9
  Adapters handle webhook verification, message parsing, and API calls for each platform. Install only the adapters you need. Browse all available adapters — including community-built ones — on the [Adapters](/adapters) page.
10
10
 
11
- Need a browser chat UI? See the [Web adapter](/adapters/web) — it speaks the AI SDK `useChat` protocol so the same bot serves Slack, Teams, **and** a `<Conversation>` from `ai-elements` out of the box.
11
+ Need a browser chat UI? See the [Web adapter](/adapters/official/web) — it speaks the AI SDK UI stream protocol and works with React (`@ai-sdk/react`), Vue (`@ai-sdk/vue`), and Svelte (`@ai-sdk/svelte`), so the same bot serves Slack, Teams, **and** any browser framework out of the box.
12
12
 
13
13
  Ready to build your own? Follow the [building](/docs/contributing/building) guide.
14
14
 
15
15
  ## Feature matrix
16
16
 
17
+ <GlobalFeatureMatrix type="platform" />
17
18
  ### Messaging
18
19
 
19
20
  | Feature | [Slack](/adapters/slack) | [Teams](/adapters/teams) | [Google Chat](/adapters/google-chat) | [Discord](/adapters/discord) | [Telegram](/adapters/telegram) | [GitHub](/adapters/github) | [Linear](/adapters/linear) | [WhatsApp](/adapters/whatsapp) | [Messenger](/adapters/messenger) |
@@ -21,7 +22,7 @@ Ready to build your own? Follow the [building](/docs/contributing/building) guid
21
22
  | Post message | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
22
23
  | Edit message | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Partial | <Cross /> | <Cross /> |
23
24
  | Delete message | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Warn /> Partial | <Cross /> | <Cross /> |
24
- | File uploads | <Check /> | <Check /> | <Cross /> | <Check /> | <Warn /> Single file | <Cross /> | <Cross /> | <Check /> Images, audio, docs | <Cross /> |
25
+ | File uploads | <Check /> | <Check /> | <Cross /> | <Check /> | <Warn /> Single file/media | <Cross /> | <Cross /> | <Check /> Images, audio, docs | <Cross /> |
25
26
  | Streaming | <Check /> Native | <Warn /> Native (DMs) / Buffered | <Warn /> Post+Edit | <Warn /> Post+Edit | <Warn /> Post+Edit | <Warn /> Buffered | <Warn /> Agent sessions / Post+Edit | <Warn /> Buffered | <Warn /> Buffered |
26
27
  | Scheduled messages | <Check /> Native | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
27
28
 
@@ -51,7 +52,7 @@ Ready to build your own? Follow the [building](/docs/contributing/building) guid
51
52
  | Ephemeral messages | <Check /> Native | <Cross /> | <Check /> Native | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> |
52
53
  | User lookup ([`getUser`](/docs/api/chat#getuser)) | <Check /> | <Warn /> Cached | <Warn /> Cached | <Check /> | <Warn /> Seen users | <Check /> | <Check /> | <Cross /> | <Cross /> |
53
54
  | Parent subject ([`message.subject`](/docs/subject)) | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Check /> | <Check /> | <Cross /> | <Cross /> |
54
- | Native client ([`.client`](/docs/api/chat#getadapter)) | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Check /> | <Check /> | <Cross /> | <Cross /> |
55
+ | Native client ([`.webClient` / `.octokit` / `.linearClient`](/docs/api/chat#getadapter)) | <Check /> | <Cross /> | <Cross /> | <Cross /> | <Cross /> | <Check /> | <Check /> | <Cross /> | <Cross /> |
55
56
  | Custom API endpoint (`apiUrl`) | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> | <Check /> |
56
57
 
57
58
  ### Message history
@@ -114,3 +115,32 @@ Each adapter auto-detects credentials from environment variables, so you only ne
114
115
  </Callout>
115
116
 
116
117
  Each adapter creates a webhook handler accessible via `bot.webhooks.<name>`.
118
+
119
+ ## Customizing an adapter via subclassing
120
+
121
+ Each official adapter exposes its extension surface as `protected` members so you can subclass it to override or extend platform-specific behavior without forking the package. Use this when you need to handle a payload type the built-in adapter doesn't cover, intercept verification, or wrap an existing handler.
122
+
123
+ ```typescript title="lib/custom-telegram.ts" lineNumbers
124
+ import { TelegramAdapter, type TelegramUpdate } from "@chat-adapter/telegram";
125
+ import type { WebhookOptions } from "chat";
126
+
127
+ export class CustomTelegramAdapter extends TelegramAdapter {
128
+ protected override processUpdate(
129
+ update: TelegramUpdate,
130
+ options?: WebhookOptions
131
+ ): void {
132
+ // Handle a payload type the base adapter doesn't, e.g. chat_join_request.
133
+ if ("chat_join_request" in update) {
134
+ this.logger.info("Received chat_join_request", { update });
135
+ return;
136
+ }
137
+ super.processUpdate(update, options);
138
+ }
139
+ }
140
+ ```
141
+
142
+ Construct your subclass anywhere you'd construct the base adapter — for example, `adapters: { telegram: new CustomTelegramAdapter({ ... }) }`. Members marked `private` (internal caches, in-flight runtime state, one-shot warning flags) intentionally remain inaccessible; if you find a hook you need that isn't `protected`, please open an issue.
143
+
144
+ <Callout type="warn">
145
+ The `protected` extension surface is intentionally broader than the public API but is not yet considered fully stable. Method signatures may evolve (renames, parameter changes, new hook splits) in minor releases as we learn from real-world subclasses. Pin the adapter version you build against, watch the changelog for the affected adapter, and prefer overriding the smallest hook that solves your problem so upgrades stay easy. If you rely on a particular hook, please open an issue so we can promote it to a stable, documented extension point.
146
+ </Callout>
@@ -0,0 +1,227 @@
1
+ ---
2
+ title: AI SDK Tools
3
+ description: Give an AI agent the ability to operate inside your workspace. Post messages, send DMs, react, edit, delete; all with built-in approval gates.
4
+ type: guide
5
+ prerequisites:
6
+ - /docs/usage
7
+ related:
8
+ - /docs/ai
9
+ - /docs/ai/to-ai-messages
10
+ - /docs/streaming
11
+ - /docs/conversation-history
12
+ ---
13
+
14
+ `createChatTools` exposes Chat SDK operations as ready-to-use [AI SDK](https://ai-sdk.dev) tools so an agent can act inside the same workspaces your bot is connected to: read messages, post replies, send DMs, react, edit, delete, and manage thread subscriptions across every adapter you've registered.
15
+
16
+ Write operations require user approval out of the box, toggle them globally or per-tool when you want unattended execution.
17
+
18
+ ## Installation
19
+
20
+ The tools live in the [`chat/ai`](/docs/ai) subpath of the core `chat` package:
21
+
22
+ ```ts
23
+ import { createChatTools } from "chat/ai";
24
+ ```
25
+
26
+ `ai` and `zod` are optional peer dependencies — install them if you haven't already:
27
+
28
+ <PackageInstall package="ai zod" />
29
+
30
+ <Callout>
31
+ Pair `createChatTools` with [`toAiMessages`](/docs/ai/to-ai-messages)
32
+ to feed prior thread history into the agent before it picks a tool.
33
+ Both ship from the same `chat/ai` subpath, which keeps the optional
34
+ `ai` / `zod` peer deps out of bundles that don't import them.
35
+ </Callout>
36
+
37
+ ## Quick start
38
+
39
+ Pass your `Chat` instance and the tools you want into any AI SDK call:
40
+
41
+ ```typescript title="lib/agent.ts" lineNumbers
42
+ import { Chat } from "chat";
43
+ import { createChatTools } from "chat/ai";
44
+ import { createSlackAdapter } from "@chat-adapter/slack";
45
+ import { createMemoryState } from "@chat-adapter/state-memory";
46
+ import { generateText } from "ai";
47
+
48
+ const chat = new Chat({
49
+ userName: "mybot",
50
+ adapters: { slack: createSlackAdapter() },
51
+ state: createMemoryState(),
52
+ });
53
+
54
+ const result = await generateText({
55
+ model: "anthropic/claude-sonnet-4.6",
56
+ tools: createChatTools({
57
+ chat,
58
+ preset: "messenger",
59
+ requireApproval: false, // unattended script, no human-in-the-loop needed
60
+ }),
61
+ prompt:
62
+ "Post a friendly hello in slack:C0123ABC and react to it with a thumbs up.",
63
+ });
64
+ ```
65
+
66
+ Each tool resolves the right adapter from the id prefix you give it (`slack:...`, `discord:...`, `gchat:...`), so the same agent can drive any platform your `Chat` instance is wired up to.
67
+
68
+ ## Presets
69
+
70
+ Pass `preset` to scope the toolset down to what an agent actually needs.
71
+
72
+ ```typescript
73
+ // Read-only — fetch messages, threads, channel info, users
74
+ createChatTools({ chat, preset: "reader" });
75
+
76
+ // Basic posting — read + post + DM + react + typing indicator
77
+ createChatTools({ chat, preset: "messenger" });
78
+
79
+ // Full management — everything including edit, delete, subscriptions
80
+ createChatTools({ chat, preset: "moderator" });
81
+ ```
82
+
83
+ Presets compose — pass an array to combine them:
84
+
85
+ ```typescript
86
+ createChatTools({ chat, preset: ["reader", "messenger"] });
87
+ ```
88
+
89
+ Omit `preset` entirely to get every tool (same as `'moderator'`).
90
+
91
+ | Preset | Tools included |
92
+ |---|---|
93
+ | `reader` | `fetchMessages`, `fetchChannelMessages`, `fetchThread`, `listThreads`, `getThreadParticipants`, `getChannelInfo`, `getUser` |
94
+ | `messenger` | `fetchMessages`, `fetchThread`, `getChannelInfo`, `getUser`, `postMessage`, `postChannelMessage`, `sendDirectMessage`, `addReaction`, `removeReaction`, `startTyping` |
95
+ | `moderator` | All read tools plus `postMessage`, `postChannelMessage`, `sendDirectMessage`, `editMessage`, `deleteMessage`, `addReaction`, `removeReaction`, `subscribeThread`, `unsubscribeThread`, `startTyping` |
96
+
97
+ ## Approval control
98
+
99
+ Write operations (posting, editing, deleting, reacting, subscribing) default to `needsApproval: true`. The AI SDK pauses execution and surfaces an approval request that your application is expected to confirm before the tool runs. This keeps a human in the loop for anything visible to the workspace.
100
+
101
+ ```typescript
102
+ // All writes need approval (default)
103
+ createChatTools({ chat });
104
+
105
+ // No approval needed
106
+ createChatTools({ chat, requireApproval: false });
107
+
108
+ // Per-tool — only destructive actions need approval
109
+ createChatTools({
110
+ chat,
111
+ requireApproval: {
112
+ deleteMessage: true,
113
+ editMessage: true,
114
+ sendDirectMessage: false,
115
+ postMessage: false,
116
+ addReaction: false,
117
+ },
118
+ });
119
+ ```
120
+
121
+ Read tools (`fetchMessages`, `fetchThread`, `getChannelInfo`, …) and the `startTyping` indicator never require approval.
122
+
123
+ ## Cherry-picking tools
124
+
125
+ Each tool is also exported as a standalone factory you can hand to `tools` directly:
126
+
127
+ ```typescript title="lib/agent.ts" lineNumbers
128
+ import { fetchMessages, postMessage, addReaction } from "chat/ai";
129
+
130
+ const tools = {
131
+ fetchMessages: fetchMessages(chat),
132
+ postMessage: postMessage(chat, { needsApproval: false }),
133
+ addReaction: addReaction(chat, { needsApproval: false }),
134
+ };
135
+ ```
136
+
137
+ Useful when you want a small, targeted toolset without going through `createChatTools`.
138
+
139
+ ## Tool overrides
140
+
141
+ Customize any AI SDK [`tool()`](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling) property per tool, keyed by tool name:
142
+
143
+ ```typescript
144
+ import type { ChatToolName, ToolOverrides } from "chat/ai";
145
+
146
+ createChatTools({
147
+ chat,
148
+ overrides: {
149
+ postMessage: {
150
+ description: "Reply in the active customer support thread.",
151
+ needsApproval: false,
152
+ },
153
+ deleteMessage: { needsApproval: true },
154
+ },
155
+ });
156
+ ```
157
+
158
+ | Property | Type | Description |
159
+ |---|---|---|
160
+ | `description` | `string` | Custom tool description shown to the model |
161
+ | `title` | `string` | Human-readable title |
162
+ | `strict` | `boolean` | Strict mode for input generation |
163
+ | `inputExamples` | `array` | Examples that show the model what tool input should look like |
164
+ | `metadata` | `object` | Tool metadata propagated to tool call and result parts |
165
+ | `needsApproval` | `boolean \| function` | Gate execution behind an approval request |
166
+ | `providerOptions` | `ProviderOptions` | Provider-specific metadata |
167
+ | `onInputStart` | `function` | Callback when argument streaming starts |
168
+ | `onInputDelta` | `function` | Callback on each streaming delta |
169
+ | `onInputAvailable` | `function` | Callback when full input is available |
170
+ | `toModelOutput` | `function` | Custom mapping of tool result to model output |
171
+
172
+ Core properties (`execute`, `inputSchema`, `outputSchema`, and tool-kind fields like `type`, `id`, `args`) cannot be overridden so tool semantics stay stable.
173
+
174
+ ## Available tools
175
+
176
+ All ids accept the full Chat SDK form: `slack:C123ABC:1234567890.123456` for a thread, `slack:C123ABC` for a channel, and the platform-native user id (e.g. `U123456` on Slack, `users/123` on Google Chat). The tools auto-detect the adapter from the prefix.
177
+
178
+ ### Reading
179
+
180
+ | Tool | Description |
181
+ |---|---|
182
+ | `fetchMessages` | Fetch recent messages from a thread (paginated) |
183
+ | `fetchChannelMessages` | Fetch top-level messages in a channel (not thread replies) |
184
+ | `fetchThread` | Fetch metadata for a thread (channel id, visibility, DM status) |
185
+ | `listThreads` | List recent threads in a channel with their root message |
186
+ | `getThreadParticipants` | Return the unique non-bot participants in a thread |
187
+ | `getChannelInfo` | Fetch channel metadata (name, member count, visibility) |
188
+ | `getUser` | Look up a user's profile by id |
189
+
190
+ ### Writing
191
+
192
+ | Tool | Description | Default approval |
193
+ |---|---|---|
194
+ | `postMessage` | Post a reply in an existing thread | required |
195
+ | `postChannelMessage` | Post a top-level message in a channel | required |
196
+ | `sendDirectMessage` | Open a DM with a user and post in it | required |
197
+ | `editMessage` | Edit a message the bot previously posted | required |
198
+ | `deleteMessage` | Delete a message the bot previously posted | required |
199
+ | `addReaction` | Add an emoji reaction to a message | required |
200
+ | `removeReaction` | Remove a previously-added reaction | required |
201
+ | `subscribeThread` | Subscribe the bot to all future messages in a thread | required |
202
+ | `unsubscribeThread` | Stop receiving non-mention messages in a thread | required |
203
+ | `startTyping` | Show a typing indicator in a thread | not gated |
204
+
205
+ ## API
206
+
207
+ ### `createChatTools(options)`
208
+
209
+ Returns an object of tools, ready to spread into `tools` of any AI SDK call.
210
+
211
+ ```typescript
212
+ type ChatToolsOptions = {
213
+ chat: Chat;
214
+ requireApproval?: boolean | Partial<Record<ChatWriteToolName, boolean>>;
215
+ preset?: ChatToolPreset | ChatToolPreset[];
216
+ overrides?: Partial<Record<ChatToolName, ToolOverrides>>;
217
+ };
218
+
219
+ type ChatToolPreset = "reader" | "messenger" | "moderator";
220
+ ```
221
+
222
+ | Option | Description |
223
+ |---|---|
224
+ | `chat` | The `Chat` instance the tools dispatch operations against. Required. |
225
+ | `preset` | Preset (or array of presets) restricting which tools are returned. Omit to get every tool. |
226
+ | `requireApproval` | `true` (default), `false`, or per-tool overrides. Read tools and `startTyping` are never gated. |
227
+ | `overrides` | Per-tool customization of any AI SDK `tool()` property except `execute`, `inputSchema`, and `outputSchema`. |
@@ -0,0 +1,63 @@
1
+ ---
2
+ title: Overview
3
+ description: AI utilities that ship with Chat SDK — agent tools, message conversion, and supporting types.
4
+ type: overview
5
+ ---
6
+
7
+ The `chat/ai` subpath is the home for AI utilities that ship with Chat SDK.
8
+
9
+ ```ts
10
+ import { createChatTools, toAiMessages } from "chat/ai";
11
+ ```
12
+
13
+ Add the optional peers if you don't already have them:
14
+
15
+ <PackageInstall package="ai zod" />
16
+
17
+ ## What's included
18
+
19
+ | Page | What it gives you |
20
+ |---|---|
21
+ | [AI SDK Tools](/docs/ai/ai-sdk-tools) | `createChatTools` and standalone tool factories that let an agent post messages, send DMs, react, edit, delete, and manage subscriptions across every adapter your `Chat` instance has registered. Built-in approval gates and presets keep writes safe. |
22
+ | [`toAiMessages`](/docs/ai/to-ai-messages) | Convert Chat SDK [`Message[]`](/docs/api/message) into the `{ role, content }[]` shape expected by AI SDK calls. Handles role mapping, attachments, links, sorting, and optional per-message transforms. |
23
+ | [Types](/docs/ai/types) | Reference for every type exported from `chat/ai` — agent message shapes, tool option contracts, presets, approval config, and the binding type that ties tools to your `Chat` instance. |
24
+
25
+ ## Typical flow
26
+
27
+ A Chat SDK bot wired to a tool-calling agent usually looks like this:
28
+
29
+ ```typescript title="lib/agent.ts" lineNumbers
30
+ import { Chat } from "chat";
31
+ import { createChatTools, toAiMessages } from "chat/ai";
32
+ import { ToolLoopAgent } from "ai";
33
+
34
+ const chat = new Chat({ /* adapters, state, ... */ });
35
+
36
+ const agent = new ToolLoopAgent({
37
+ model: "anthropic/claude-sonnet-4.6",
38
+ instructions: "You operate inside a chat workspace via Chat SDK tools.",
39
+ tools: createChatTools({ chat, preset: "messenger", requireApproval: true }),
40
+ });
41
+
42
+ bot.onSubscribedMessage(async (thread) => {
43
+ const { messages } = await thread.adapter.fetchMessages(thread.id, {
44
+ limit: 20,
45
+ });
46
+ const history = await toAiMessages(messages);
47
+ const result = await agent.stream({ prompt: history });
48
+ await thread.post(result.fullStream);
49
+ });
50
+ ```
51
+
52
+ 1. [`toAiMessages`](/docs/ai/to-ai-messages) converts messages into an output compatible with AI SDK's `ModelMessage[]`.
53
+ 2. [`createChatTools`](/docs/ai/ai-sdk-tools) gives the agent pre-built and fully customizable AI SDK tools.
54
+ 3. The streamed response is rendered back into the thread via the standard [`thread.post(stream)`](/docs/streaming) flow.
55
+
56
+ ## Backwards compatibility
57
+
58
+ `toAiMessages` and the related `Ai*` types are still re-exported from the top-level `chat` package so older bots keep working. Those re-exports are now flagged with `@deprecated` JSDoc — your editor will surface a hint pointing at `chat/ai`. Migrating is a one-line import change:
59
+
60
+ ```diff
61
+ - import { toAiMessages } from "chat";
62
+ + import { toAiMessages } from "chat/ai";
63
+ ```
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "AI",
3
+ "pages": ["index", "ai-sdk-tools", "to-ai-messages", "types"]
4
+ }
@@ -2,18 +2,31 @@
2
2
  title: toAiMessages
3
3
  description: Convert Chat SDK messages to AI SDK conversation format.
4
4
  type: reference
5
+ related:
6
+ - /docs/ai
7
+ - /docs/ai/ai-sdk-tools
8
+ - /docs/ai/types
9
+ - /docs/streaming
5
10
  ---
6
11
 
7
- Convert an array of `Message` objects into the `{ role, content }[]` format expected by AI SDKs. The output is structurally compatible with AI SDK's `ModelMessage[]`.
12
+ Convert an array of [`Message`](/docs/api/message) objects into the `{ role, content }[]` format expected by the AI SDK. The output is structurally compatible with AI SDK's `ModelMessage[]`.
8
13
 
9
14
  ```typescript
10
- import { toAiMessages } from "chat";
15
+ import { toAiMessages } from "chat/ai";
11
16
  ```
12
17
 
18
+ <Callout>
19
+ `toAiMessages` is also re-exported from the main `chat` entrypoint
20
+ for backwards compatibility (with a `@deprecated` JSDoc hint), but
21
+ new code should import it from [`chat/ai`](/docs/ai) alongside
22
+ [`createChatTools`](/docs/ai/ai-sdk-tools) and the rest of the AI
23
+ utilities.
24
+ </Callout>
25
+
13
26
  ## Usage
14
27
 
15
28
  ```typescript title="lib/bot.ts" lineNumbers
16
- import { toAiMessages } from "chat";
29
+ import { toAiMessages } from "chat/ai";
17
30
 
18
31
  bot.onSubscribedMessage(async (thread, message) => {
19
32
  const result = await thread.adapter.fetchMessages(thread.id, { limit: 20 });