chat 4.14.0 → 4.16.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.
Files changed (38) hide show
  1. package/README.md +12 -0
  2. package/dist/{chunk-THM4ACIE.js → chunk-7S5DLTN2.js} +372 -9
  3. package/dist/chunk-7S5DLTN2.js.map +1 -0
  4. package/dist/index.d.ts +122 -5
  5. package/dist/index.js +367 -235
  6. package/dist/index.js.map +1 -1
  7. package/dist/{jsx-runtime-Bdt1Dwzf.d.ts → jsx-runtime-Bokk9xw5.d.ts} +80 -5
  8. package/dist/jsx-runtime.d.ts +1 -1
  9. package/dist/jsx-runtime.js +3 -1
  10. package/docs/adapters/discord.mdx +1 -0
  11. package/docs/adapters/index.mdx +51 -34
  12. package/docs/adapters/meta.json +1 -0
  13. package/docs/adapters/slack.mdx +16 -0
  14. package/docs/adapters/telegram.mdx +161 -0
  15. package/docs/api/cards.mdx +57 -1
  16. package/docs/api/channel.mdx +4 -1
  17. package/docs/api/chat.mdx +5 -0
  18. package/docs/api/markdown.mdx +42 -0
  19. package/docs/api/thread.mdx +4 -1
  20. package/docs/cards.mdx +44 -1
  21. package/docs/contributing/building.mdx +626 -0
  22. package/docs/contributing/documenting.mdx +218 -0
  23. package/docs/contributing/meta.json +4 -0
  24. package/docs/contributing/publishing.mdx +161 -0
  25. package/docs/contributing/testing.mdx +494 -0
  26. package/docs/error-handling.mdx +1 -1
  27. package/docs/getting-started.mdx +23 -1
  28. package/docs/handling-events.mdx +375 -0
  29. package/docs/index.mdx +4 -2
  30. package/docs/meta.json +6 -1
  31. package/docs/posting-messages.mdx +7 -7
  32. package/docs/slash-commands.mdx +23 -0
  33. package/docs/state/meta.json +1 -6
  34. package/docs/streaming.mdx +32 -0
  35. package/docs/threads-messages-channels.mdx +237 -0
  36. package/docs/usage.mdx +82 -276
  37. package/package.json +4 -3
  38. package/dist/chunk-THM4ACIE.js.map +0 -1
package/README.md CHANGED
@@ -27,6 +27,7 @@ const bot = new Chat({
27
27
  }),
28
28
  },
29
29
  state: createRedisState({ url: process.env.REDIS_URL! }),
30
+ dedupeTtlMs: 600_000, // 10 minutes (default: 5 min)
30
31
  });
31
32
 
32
33
  bot.onNewMention(async (thread) => {
@@ -39,6 +40,17 @@ bot.onSubscribedMessage(async (thread, message) => {
39
40
  });
40
41
  ```
41
42
 
43
+ ## Configuration
44
+
45
+ | Option | Type | Default | Description |
46
+ |--------|------|---------|-------------|
47
+ | `userName` | `string` | **required** | Default bot username across all adapters |
48
+ | `adapters` | `Record<string, Adapter>` | **required** | Map of adapter name to adapter instance |
49
+ | `state` | `StateAdapter` | **required** | State adapter for subscriptions, locking, and dedup |
50
+ | `logger` | `Logger \| LogLevel` | `"info"` | Logger instance or log level (`"silent"` to disable) |
51
+ | `streamingUpdateIntervalMs` | `number` | `500` | Update interval for fallback streaming (post + edit) in ms |
52
+ | `dedupeTtlMs` | `number` | `300000` | TTL for message deduplication entries in ms. Increase if webhook cold starts cause platform retries (e.g., Slack's `http_timeout` retry) that arrive after the default window |
53
+
42
54
  ## AI coding agent support
43
55
 
44
56
  If you use an AI coding agent like [Claude Code](https://docs.anthropic.com/en/docs/claude-code), you can teach it about Chat SDK:
@@ -1,3 +1,280 @@
1
+ // src/markdown.ts
2
+ import { toString as mdastToString } from "mdast-util-to-string";
3
+ import remarkGfm from "remark-gfm";
4
+ import remarkParse from "remark-parse";
5
+ import remarkStringify from "remark-stringify";
6
+ import { unified } from "unified";
7
+ function isTextNode(node) {
8
+ return node.type === "text";
9
+ }
10
+ function isParagraphNode(node) {
11
+ return node.type === "paragraph";
12
+ }
13
+ function isStrongNode(node) {
14
+ return node.type === "strong";
15
+ }
16
+ function isEmphasisNode(node) {
17
+ return node.type === "emphasis";
18
+ }
19
+ function isDeleteNode(node) {
20
+ return node.type === "delete";
21
+ }
22
+ function isInlineCodeNode(node) {
23
+ return node.type === "inlineCode";
24
+ }
25
+ function isCodeNode(node) {
26
+ return node.type === "code";
27
+ }
28
+ function isLinkNode(node) {
29
+ return node.type === "link";
30
+ }
31
+ function isBlockquoteNode(node) {
32
+ return node.type === "blockquote";
33
+ }
34
+ function isListNode(node) {
35
+ return node.type === "list";
36
+ }
37
+ function isListItemNode(node) {
38
+ return node.type === "listItem";
39
+ }
40
+ function isTableNode(node) {
41
+ return node.type === "table";
42
+ }
43
+ function isTableRowNode(node) {
44
+ return node.type === "tableRow";
45
+ }
46
+ function isTableCellNode(node) {
47
+ return node.type === "tableCell";
48
+ }
49
+ function tableToAscii(node) {
50
+ const rows = [];
51
+ for (const row of node.children) {
52
+ const cells = [];
53
+ for (const cell of row.children) {
54
+ cells.push(mdastToString(cell));
55
+ }
56
+ rows.push(cells);
57
+ }
58
+ if (rows.length === 0) {
59
+ return "";
60
+ }
61
+ const headers = rows[0];
62
+ const dataRows = rows.slice(1);
63
+ return tableElementToAscii(headers, dataRows);
64
+ }
65
+ function tableElementToAscii(headers, rows) {
66
+ const allRows = [headers, ...rows];
67
+ const colCount = Math.max(...allRows.map((r) => r.length));
68
+ if (colCount === 0) {
69
+ return "";
70
+ }
71
+ const colWidths = Array.from({ length: colCount }, () => 0);
72
+ for (const row of allRows) {
73
+ for (let i = 0; i < colCount; i++) {
74
+ const cellLen = (row[i] || "").length;
75
+ if (cellLen > colWidths[i]) {
76
+ colWidths[i] = cellLen;
77
+ }
78
+ }
79
+ }
80
+ const formatRow = (cells) => Array.from(
81
+ { length: colCount },
82
+ (_, i) => (cells[i] || "").padEnd(colWidths[i])
83
+ ).join(" | ").trimEnd();
84
+ const lines = [];
85
+ lines.push(formatRow(headers));
86
+ lines.push(colWidths.map((w) => "-".repeat(w)).join("-|-"));
87
+ for (const row of rows) {
88
+ lines.push(formatRow(row));
89
+ }
90
+ return lines.join("\n");
91
+ }
92
+ function getNodeChildren(node) {
93
+ if ("children" in node && Array.isArray(node.children)) {
94
+ return node.children;
95
+ }
96
+ return [];
97
+ }
98
+ function getNodeValue(node) {
99
+ if ("value" in node && typeof node.value === "string") {
100
+ return node.value;
101
+ }
102
+ return "";
103
+ }
104
+ function parseMarkdown(markdown) {
105
+ const processor = unified().use(remarkParse).use(remarkGfm);
106
+ return processor.parse(markdown);
107
+ }
108
+ function stringifyMarkdown(ast) {
109
+ const processor = unified().use(remarkStringify).use(remarkGfm);
110
+ return processor.stringify(ast);
111
+ }
112
+ function toPlainText(ast) {
113
+ return mdastToString(ast);
114
+ }
115
+ function markdownToPlainText(markdown) {
116
+ const ast = parseMarkdown(markdown);
117
+ return mdastToString(ast);
118
+ }
119
+ function walkAst(node, visitor) {
120
+ if ("children" in node && Array.isArray(node.children)) {
121
+ node.children = node.children.map((child) => {
122
+ const result = visitor(child);
123
+ if (result === null) {
124
+ return null;
125
+ }
126
+ return walkAst(result, visitor);
127
+ }).filter((n) => n !== null);
128
+ }
129
+ return node;
130
+ }
131
+ function text(value) {
132
+ return { type: "text", value };
133
+ }
134
+ function strong(children) {
135
+ return { type: "strong", children };
136
+ }
137
+ function emphasis(children) {
138
+ return { type: "emphasis", children };
139
+ }
140
+ function strikethrough(children) {
141
+ return { type: "delete", children };
142
+ }
143
+ function inlineCode(value) {
144
+ return { type: "inlineCode", value };
145
+ }
146
+ function codeBlock(value, lang) {
147
+ return { type: "code", value, lang };
148
+ }
149
+ function link(url, children, title) {
150
+ return { type: "link", url, children, title };
151
+ }
152
+ function blockquote(children) {
153
+ return { type: "blockquote", children };
154
+ }
155
+ function paragraph(children) {
156
+ return { type: "paragraph", children };
157
+ }
158
+ function root(children) {
159
+ return { type: "root", children };
160
+ }
161
+ var BaseFormatConverter = class {
162
+ /**
163
+ * Default fallback for converting an unknown mdast node to text.
164
+ * Recursively converts children if present, otherwise extracts the node value.
165
+ * Adapters should call this in their nodeToX() default case.
166
+ */
167
+ defaultNodeToText(node, nodeConverter) {
168
+ const children = getNodeChildren(node);
169
+ if (children.length > 0) {
170
+ return children.map(nodeConverter).join("");
171
+ }
172
+ return getNodeValue(node);
173
+ }
174
+ /**
175
+ * Template method for implementing fromAst with a node converter.
176
+ * Iterates through AST children and converts each using the provided function.
177
+ * Joins results with double newlines (standard paragraph separation).
178
+ *
179
+ * @param ast - The AST to convert
180
+ * @param nodeConverter - Function to convert each Content node to string
181
+ * @returns Platform-formatted string
182
+ */
183
+ fromAstWithNodeConverter(ast, nodeConverter) {
184
+ const parts = [];
185
+ for (const node of ast.children) {
186
+ parts.push(nodeConverter(node));
187
+ }
188
+ return parts.join("\n\n");
189
+ }
190
+ extractPlainText(platformText) {
191
+ return toPlainText(this.toAst(platformText));
192
+ }
193
+ // Convenience methods for markdown string I/O
194
+ fromMarkdown(markdown) {
195
+ return this.fromAst(parseMarkdown(markdown));
196
+ }
197
+ toMarkdown(platformText) {
198
+ return stringifyMarkdown(this.toAst(platformText));
199
+ }
200
+ /** @deprecated Use extractPlainText instead */
201
+ toPlainText(platformText) {
202
+ return this.extractPlainText(platformText);
203
+ }
204
+ /**
205
+ * Convert a PostableMessage to platform format (text only).
206
+ * - string: passed through as raw text (no conversion)
207
+ * - { raw: string }: passed through as raw text (no conversion)
208
+ * - { markdown: string }: converted from markdown to platform format
209
+ * - { ast: Root }: converted from AST to platform format
210
+ * - { card: CardElement }: returns fallback text (cards should be handled by adapter)
211
+ * - CardElement: returns fallback text (cards should be handled by adapter)
212
+ *
213
+ * Note: For cards, adapters should check for card content first and render
214
+ * them using platform-specific card APIs, using this method only for fallback.
215
+ */
216
+ renderPostable(message) {
217
+ if (typeof message === "string") {
218
+ return message;
219
+ }
220
+ if ("raw" in message) {
221
+ return message.raw;
222
+ }
223
+ if ("markdown" in message) {
224
+ return this.fromMarkdown(message.markdown);
225
+ }
226
+ if ("ast" in message) {
227
+ return this.fromAst(message.ast);
228
+ }
229
+ if ("card" in message) {
230
+ return message.fallbackText || this.cardToFallbackText(message.card);
231
+ }
232
+ if ("type" in message && message.type === "card") {
233
+ return this.cardToFallbackText(message);
234
+ }
235
+ throw new Error("Invalid PostableMessage format");
236
+ }
237
+ /**
238
+ * Generate fallback text from a card element.
239
+ * Override in subclasses for platform-specific formatting.
240
+ */
241
+ cardToFallbackText(card) {
242
+ const parts = [];
243
+ if (card.title) {
244
+ parts.push(`**${card.title}**`);
245
+ }
246
+ if (card.subtitle) {
247
+ parts.push(card.subtitle);
248
+ }
249
+ for (const child of card.children) {
250
+ const text2 = this.cardChildToFallbackText(child);
251
+ if (text2) {
252
+ parts.push(text2);
253
+ }
254
+ }
255
+ return parts.join("\n");
256
+ }
257
+ /**
258
+ * Convert card child element to fallback text.
259
+ */
260
+ cardChildToFallbackText(child) {
261
+ switch (child.type) {
262
+ case "text":
263
+ return child.content;
264
+ case "fields":
265
+ return child.children.map((f) => `**${f.label}**: ${f.value}`).join("\n");
266
+ case "actions":
267
+ return null;
268
+ case "table":
269
+ return tableElementToAscii(child.headers, child.rows);
270
+ case "section":
271
+ return child.children.map((c) => this.cardChildToFallbackText(c)).filter(Boolean).join("\n");
272
+ default:
273
+ return null;
274
+ }
275
+ }
276
+ };
277
+
1
278
  // src/cards.ts
2
279
  function isCardElement(value) {
3
280
  return typeof value === "object" && value !== null && "type" in value && value.type === "card";
@@ -47,7 +324,8 @@ function Button(options) {
47
324
  id: options.id,
48
325
  label: options.label,
49
326
  style: options.style,
50
- value: options.value
327
+ value: options.value,
328
+ disabled: options.disabled
51
329
  };
52
330
  }
53
331
  function LinkButton(options) {
@@ -71,6 +349,21 @@ function Fields(children) {
71
349
  children
72
350
  };
73
351
  }
352
+ function Table(options) {
353
+ return {
354
+ type: "table",
355
+ headers: options.headers,
356
+ rows: options.rows,
357
+ align: options.align
358
+ };
359
+ }
360
+ function CardLink(options) {
361
+ return {
362
+ type: "link",
363
+ url: options.url,
364
+ label: options.label
365
+ };
366
+ }
74
367
  function isReactElement(value) {
75
368
  if (typeof value !== "object" || value === null) {
76
369
  return false;
@@ -91,8 +384,10 @@ var componentMap = /* @__PURE__ */ new Map([
91
384
  [Actions, "Actions"],
92
385
  [Button, "Button"],
93
386
  [LinkButton, "LinkButton"],
387
+ [CardLink, "CardLink"],
94
388
  [Field, "Field"],
95
- [Fields, "Fields"]
389
+ [Fields, "Fields"],
390
+ [Table, "Table"]
96
391
  ]);
97
392
  function fromReactElement(element) {
98
393
  if (!isReactElement(element)) {
@@ -163,6 +458,13 @@ function fromReactElement(element) {
163
458
  style: props.style
164
459
  });
165
460
  }
461
+ case "CardLink": {
462
+ const label = extractTextContent(props.children);
463
+ return CardLink({
464
+ url: props.url,
465
+ label: props.label ?? label
466
+ });
467
+ }
166
468
  case "Field":
167
469
  return Field({
168
470
  label: props.label,
@@ -172,6 +474,12 @@ function fromReactElement(element) {
172
474
  return Fields(
173
475
  convertedChildren.filter((c) => c.type === "field")
174
476
  );
477
+ case "Table":
478
+ return Table({
479
+ headers: props.headers,
480
+ rows: props.rows,
481
+ align: props.align
482
+ });
175
483
  default:
176
484
  return null;
177
485
  }
@@ -207,29 +515,33 @@ function extractTextContent(children) {
207
515
  function cardToFallbackText(card) {
208
516
  const parts = [];
209
517
  if (card.title) {
210
- parts.push(card.title);
518
+ parts.push(`**${card.title}**`);
211
519
  }
212
520
  if (card.subtitle) {
213
521
  parts.push(card.subtitle);
214
522
  }
215
523
  for (const child of card.children) {
216
- const text = childToFallbackText(child);
217
- if (text) {
218
- parts.push(text);
524
+ const text2 = cardChildToFallbackText(child);
525
+ if (text2) {
526
+ parts.push(text2);
219
527
  }
220
528
  }
221
529
  return parts.join("\n");
222
530
  }
223
- function childToFallbackText(child) {
531
+ function cardChildToFallbackText(child) {
224
532
  switch (child.type) {
225
533
  case "text":
226
534
  return child.content;
535
+ case "link":
536
+ return `${child.label} (${child.url})`;
227
537
  case "fields":
228
538
  return child.children.map((f) => `${f.label}: ${f.value}`).join("\n");
229
539
  case "actions":
230
540
  return null;
541
+ case "table":
542
+ return tableElementToAscii(child.headers, child.rows);
231
543
  case "section":
232
- return child.children.map((c) => childToFallbackText(c)).filter(Boolean).join("\n");
544
+ return child.children.map((c) => cardChildToFallbackText(c)).filter(Boolean).join("\n");
233
545
  default:
234
546
  return null;
235
547
  }
@@ -459,6 +771,9 @@ function isButtonProps(props) {
459
771
  function isLinkButtonProps(props) {
460
772
  return "url" in props && typeof props.url === "string" && !("id" in props);
461
773
  }
774
+ function isCardLinkProps(props) {
775
+ return "url" in props && typeof props.url === "string" && !("id" in props) && !("alt" in props) && !("style" in props);
776
+ }
462
777
  function isImageProps(props) {
463
778
  return "url" in props && typeof props.url === "string";
464
779
  }
@@ -522,6 +837,16 @@ function resolveJSXElement(element) {
522
837
  style: props.style
523
838
  });
524
839
  }
840
+ if (type === CardLink) {
841
+ if (!isCardLinkProps(props)) {
842
+ throw new Error("CardLink requires a 'url' prop");
843
+ }
844
+ const label = processedChildren.length > 0 ? processedChildren.map(String).join("") : props.label ?? "";
845
+ return CardLink({
846
+ url: props.url,
847
+ label
848
+ });
849
+ }
525
850
  if (type === Image) {
526
851
  if (!isImageProps(props)) {
527
852
  throw new Error("Image requires a 'url' prop");
@@ -677,6 +1002,40 @@ function isJSX(value) {
677
1002
  }
678
1003
 
679
1004
  export {
1005
+ isTextNode,
1006
+ isParagraphNode,
1007
+ isStrongNode,
1008
+ isEmphasisNode,
1009
+ isDeleteNode,
1010
+ isInlineCodeNode,
1011
+ isCodeNode,
1012
+ isLinkNode,
1013
+ isBlockquoteNode,
1014
+ isListNode,
1015
+ isListItemNode,
1016
+ isTableNode,
1017
+ isTableRowNode,
1018
+ isTableCellNode,
1019
+ tableToAscii,
1020
+ tableElementToAscii,
1021
+ getNodeChildren,
1022
+ getNodeValue,
1023
+ parseMarkdown,
1024
+ stringifyMarkdown,
1025
+ toPlainText,
1026
+ markdownToPlainText,
1027
+ walkAst,
1028
+ text,
1029
+ strong,
1030
+ emphasis,
1031
+ strikethrough,
1032
+ inlineCode,
1033
+ codeBlock,
1034
+ link,
1035
+ blockquote,
1036
+ paragraph,
1037
+ root,
1038
+ BaseFormatConverter,
680
1039
  isCardElement,
681
1040
  Card,
682
1041
  CardText,
@@ -688,8 +1047,11 @@ export {
688
1047
  LinkButton,
689
1048
  Field,
690
1049
  Fields,
1050
+ Table,
1051
+ CardLink,
691
1052
  fromReactElement,
692
1053
  cardToFallbackText,
1054
+ cardChildToFallbackText,
693
1055
  isModalElement,
694
1056
  Modal,
695
1057
  TextInput,
@@ -697,6 +1059,7 @@ export {
697
1059
  SelectOption,
698
1060
  RadioSelect,
699
1061
  fromReactModalElement,
1062
+ isCardLinkProps,
700
1063
  jsx,
701
1064
  jsxs,
702
1065
  jsxDEV,
@@ -705,4 +1068,4 @@ export {
705
1068
  toModalElement,
706
1069
  isJSX
707
1070
  };
708
- //# sourceMappingURL=chunk-THM4ACIE.js.map
1071
+ //# sourceMappingURL=chunk-7S5DLTN2.js.map