chat 4.27.0 → 4.28.1

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
@@ -60,7 +60,7 @@ import {
60
60
  toModalElement,
61
61
  toPlainText,
62
62
  walkAst
63
- } from "./chunk-AN7MRAVW.js";
63
+ } from "./chunk-V25FKIIL.js";
64
64
 
65
65
  // src/ai.ts
66
66
  var TEXT_MIME_PREFIXES = [
@@ -190,6 +190,127 @@ ${linkParts}`;
190
190
  // src/channel.ts
191
191
  import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2 } from "@workflow/serde";
192
192
 
193
+ // src/callback-url.ts
194
+ var CALLBACK_TOKEN_PREFIX = "__cb:";
195
+ var CALLBACK_CACHE_KEY_PREFIX = "chat:callback:";
196
+ var CALLBACK_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
197
+ function encodeCallbackValue(token) {
198
+ return `${CALLBACK_TOKEN_PREFIX}${token}`;
199
+ }
200
+ function decodeCallbackValue(value) {
201
+ if (!value?.startsWith(CALLBACK_TOKEN_PREFIX)) {
202
+ return { callbackToken: void 0 };
203
+ }
204
+ return { callbackToken: value.slice(CALLBACK_TOKEN_PREFIX.length) };
205
+ }
206
+ function generateToken() {
207
+ return crypto.randomUUID().replace(/-/g, "").slice(0, 16);
208
+ }
209
+ async function processActionsElement(actions, stateAdapter) {
210
+ return {
211
+ type: "actions",
212
+ children: await Promise.all(
213
+ actions.children.map(async (el) => {
214
+ if (el.type !== "button" || !el.callbackUrl) {
215
+ return el;
216
+ }
217
+ const token = generateToken();
218
+ const stored = {
219
+ url: el.callbackUrl,
220
+ originalValue: el.value
221
+ };
222
+ await stateAdapter.set(
223
+ `${CALLBACK_CACHE_KEY_PREFIX}${token}`,
224
+ stored,
225
+ CALLBACK_TTL_MS
226
+ );
227
+ const processed = {
228
+ type: "button",
229
+ id: el.id,
230
+ label: el.label,
231
+ style: el.style,
232
+ disabled: el.disabled,
233
+ value: encodeCallbackValue(token),
234
+ actionType: el.actionType
235
+ };
236
+ return processed;
237
+ })
238
+ )
239
+ };
240
+ }
241
+ function hasCallbackButtons(children) {
242
+ for (const child of children) {
243
+ if (child.type === "actions") {
244
+ for (const el of child.children) {
245
+ if (el.type === "button" && el.callbackUrl) {
246
+ return true;
247
+ }
248
+ }
249
+ }
250
+ if (child.type === "section" && "children" in child && hasCallbackButtons(child.children)) {
251
+ return true;
252
+ }
253
+ }
254
+ return false;
255
+ }
256
+ async function processChildren(children, stateAdapter) {
257
+ const result = [];
258
+ for (const child of children) {
259
+ if (child.type === "actions") {
260
+ result.push(await processActionsElement(child, stateAdapter));
261
+ } else if (child.type === "section" && "children" in child) {
262
+ result.push({
263
+ ...child,
264
+ children: await processChildren(child.children, stateAdapter)
265
+ });
266
+ } else {
267
+ result.push(child);
268
+ }
269
+ }
270
+ return result;
271
+ }
272
+ async function processCardCallbackUrls(card, stateAdapter) {
273
+ if (!hasCallbackButtons(card.children)) {
274
+ return card;
275
+ }
276
+ return {
277
+ ...card,
278
+ children: await processChildren(card.children, stateAdapter)
279
+ };
280
+ }
281
+ async function resolveCallbackUrl(token, stateAdapter) {
282
+ const stored = await stateAdapter.get(
283
+ `${CALLBACK_CACHE_KEY_PREFIX}${token}`
284
+ );
285
+ if (!stored) {
286
+ return null;
287
+ }
288
+ if (typeof stored === "string") {
289
+ return { url: stored };
290
+ }
291
+ return stored;
292
+ }
293
+ async function postToCallbackUrl(callbackUrl, payload) {
294
+ try {
295
+ const response = await fetch(callbackUrl, {
296
+ method: "POST",
297
+ headers: { "Content-Type": "application/json" },
298
+ body: JSON.stringify(payload)
299
+ });
300
+ if (!response.ok) {
301
+ return {
302
+ error: new Error(
303
+ `Callback URL returned ${response.status}: ${await response.text().catch(() => "")}`
304
+ ),
305
+ status: response.status
306
+ };
307
+ }
308
+ return { status: response.status };
309
+ } catch (error) {
310
+ return { error };
311
+ }
312
+ }
313
+
193
314
  // src/chat-singleton.ts
194
315
  var _singleton = null;
195
316
  function setChatSingleton(chat) {
@@ -245,6 +366,10 @@ async function* fromFullStream(stream) {
245
366
 
246
367
  // src/message.ts
247
368
  import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE } from "@workflow/serde";
369
+ var adapterMap = /* @__PURE__ */ new WeakMap();
370
+ function setMessageAdapter(message, adapter) {
371
+ adapterMap.set(message, adapter);
372
+ }
248
373
  var Message = class _Message {
249
374
  /** Unique message ID */
250
375
  id;
@@ -283,8 +408,31 @@ var Message = class _Message {
283
408
  * ```
284
409
  */
285
410
  isMention;
411
+ /**
412
+ * Cross-platform user key for this message's author.
413
+ *
414
+ * Set by the Chat SDK before passing the message to handlers, when
415
+ * `ChatConfig.identity` is configured. `undefined` if no resolver is
416
+ * configured; `undefined` (i.e. absent) when the resolver returned null.
417
+ *
418
+ * Used by the Transcripts API to look up / append per-user transcripts.
419
+ */
420
+ userKey;
286
421
  /** Links found in the message */
287
422
  links;
423
+ _subjectPromise;
424
+ get subject() {
425
+ if (this._subjectPromise) {
426
+ return this._subjectPromise;
427
+ }
428
+ const adapter = adapterMap.get(this);
429
+ if (!adapter?.fetchSubject) {
430
+ this._subjectPromise = Promise.resolve(null);
431
+ return this._subjectPromise;
432
+ }
433
+ this._subjectPromise = adapter.fetchSubject(this.raw).catch(() => null);
434
+ return this._subjectPromise;
435
+ }
288
436
  constructor(data) {
289
437
  this.id = data.id;
290
438
  this.threadId = data.threadId;
@@ -497,7 +645,7 @@ var ChannelImpl = class _ChannelImpl {
497
645
  _adapterName;
498
646
  _stateAdapterInstance;
499
647
  _name = null;
500
- _messageHistory;
648
+ _threadHistory;
501
649
  constructor(config) {
502
650
  this.id = config.id;
503
651
  this.isDM = config.isDM ?? false;
@@ -507,7 +655,7 @@ var ChannelImpl = class _ChannelImpl {
507
655
  } else {
508
656
  this._adapter = config.adapter;
509
657
  this._stateAdapterInstance = config.stateAdapter;
510
- this._messageHistory = config.messageHistory;
658
+ this._threadHistory = config.threadHistory;
511
659
  }
512
660
  }
513
661
  get adapter() {
@@ -561,7 +709,7 @@ var ChannelImpl = class _ChannelImpl {
561
709
  get messages() {
562
710
  const adapter = this.adapter;
563
711
  const channelId = this.id;
564
- const messageHistory = this._messageHistory;
712
+ const threadHistory = this._threadHistory;
565
713
  return {
566
714
  async *[Symbol.asyncIterator]() {
567
715
  let cursor;
@@ -579,8 +727,8 @@ var ChannelImpl = class _ChannelImpl {
579
727
  }
580
728
  cursor = result.nextCursor;
581
729
  }
582
- if (!yieldedAny && messageHistory) {
583
- const cached = await messageHistory.getMessages(channelId);
730
+ if (!yieldedAny && threadHistory) {
731
+ const cached = await threadHistory.getMessages(channelId);
584
732
  for (let i = cached.length - 1; i >= 0; i--) {
585
733
  yield cached[i];
586
734
  }
@@ -637,6 +785,8 @@ var ChannelImpl = class _ChannelImpl {
637
785
  for await (const chunk of fromFullStream(message)) {
638
786
  if (typeof chunk === "string") {
639
787
  accumulated += chunk;
788
+ } else if (chunk.type === "markdown_text") {
789
+ accumulated += chunk.text;
640
790
  }
641
791
  }
642
792
  return this.postSingleMessage({ markdown: accumulated });
@@ -649,6 +799,7 @@ var ChannelImpl = class _ChannelImpl {
649
799
  }
650
800
  postable = card;
651
801
  }
802
+ postable = await this.processCallbackUrls(postable);
652
803
  return this.postSingleMessage(postable);
653
804
  }
654
805
  async handlePostableObject(obj) {
@@ -666,8 +817,8 @@ var ChannelImpl = class _ChannelImpl {
666
817
  postable,
667
818
  rawMessage.threadId
668
819
  );
669
- if (this._messageHistory) {
670
- await this._messageHistory.append(this.id, new Message(sent));
820
+ if (this._threadHistory) {
821
+ await this._threadHistory.append(this.id, new Message(sent));
671
822
  }
672
823
  return sent;
673
824
  }
@@ -684,6 +835,7 @@ var ChannelImpl = class _ChannelImpl {
684
835
  } else {
685
836
  postable = message;
686
837
  }
838
+ postable = await this.processCallbackUrls(postable);
687
839
  if (this.adapter.postEphemeral) {
688
840
  return this.adapter.postEphemeral(this.id, userId, postable);
689
841
  }
@@ -713,6 +865,7 @@ var ChannelImpl = class _ChannelImpl {
713
865
  } else {
714
866
  postable = message;
715
867
  }
868
+ postable = await this.processCallbackUrls(postable);
716
869
  if (!this.adapter.scheduleMessage) {
717
870
  throw new NotImplementedError(
718
871
  "Scheduled messages are not supported by this adapter",
@@ -721,6 +874,24 @@ var ChannelImpl = class _ChannelImpl {
721
874
  }
722
875
  return this.adapter.scheduleMessage(this.id, postable, options);
723
876
  }
877
+ async processCallbackUrls(postable) {
878
+ if (typeof postable === "string") {
879
+ return postable;
880
+ }
881
+ if ("type" in postable && postable.type === "card") {
882
+ return processCardCallbackUrls(postable, this._stateAdapter);
883
+ }
884
+ if ("card" in postable && postable.card?.type === "card") {
885
+ const processed = await processCardCallbackUrls(
886
+ postable.card,
887
+ this._stateAdapter
888
+ );
889
+ if (processed !== postable.card) {
890
+ return { ...postable, card: processed };
891
+ }
892
+ }
893
+ return postable;
894
+ }
724
895
  async startTyping(status) {
725
896
  await this.adapter.startTyping(this.id, status);
726
897
  }
@@ -790,6 +961,7 @@ var ChannelImpl = class _ChannelImpl {
790
961
  }
791
962
  editPostable = card;
792
963
  }
964
+ editPostable = await self.processCallbackUrls(editPostable);
793
965
  await adapter.editMessage(threadId, messageId, editPostable);
794
966
  return self.createSentMessage(messageId, editPostable);
795
967
  },
@@ -858,46 +1030,6 @@ function extractMessageContent(message) {
858
1030
  throw new Error("Invalid PostableMessage format");
859
1031
  }
860
1032
 
861
- // src/message-history.ts
862
- var DEFAULT_MAX_MESSAGES = 100;
863
- var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
864
- var KEY_PREFIX = "msg-history:";
865
- var MessageHistoryCache = class {
866
- state;
867
- maxMessages;
868
- ttlMs;
869
- constructor(state, config) {
870
- this.state = state;
871
- this.maxMessages = config?.maxMessages ?? DEFAULT_MAX_MESSAGES;
872
- this.ttlMs = config?.ttlMs ?? DEFAULT_TTL_MS;
873
- }
874
- /**
875
- * Atomically append a message to the history for a thread.
876
- * Trims to maxMessages (keeps newest) and refreshes TTL.
877
- */
878
- async append(threadId, message) {
879
- const key = `${KEY_PREFIX}${threadId}`;
880
- const serialized = message.toJSON();
881
- serialized.raw = null;
882
- await this.state.appendToList(key, serialized, {
883
- maxLength: this.maxMessages,
884
- ttlMs: this.ttlMs
885
- });
886
- }
887
- /**
888
- * Get messages for a thread in chronological order (oldest first).
889
- *
890
- * @param threadId - The thread ID
891
- * @param limit - Optional limit on number of messages to return (returns newest N)
892
- */
893
- async getMessages(threadId, limit) {
894
- const key = `${KEY_PREFIX}${threadId}`;
895
- const stored = await this.state.getList(key);
896
- const sliced = limit && stored.length > limit ? stored.slice(stored.length - limit) : stored;
897
- return sliced.map((s) => Message.fromJSON(s));
898
- }
899
- };
900
-
901
1033
  // src/thread.ts
902
1034
  import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE3, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE3 } from "@workflow/serde";
903
1035
 
@@ -1168,8 +1300,8 @@ var ThreadImpl = class _ThreadImpl {
1168
1300
  _fallbackStreamingPlaceholderText;
1169
1301
  /** Cached channel instance */
1170
1302
  _channel;
1171
- /** Message history cache (set only for adapters with persistMessageHistory) */
1172
- _messageHistory;
1303
+ /** Thread history cache (set only for adapters with persistThreadHistory) */
1304
+ _threadHistory;
1173
1305
  _logger;
1174
1306
  constructor(config) {
1175
1307
  this.id = config.id;
@@ -1186,7 +1318,7 @@ var ThreadImpl = class _ThreadImpl {
1186
1318
  } else {
1187
1319
  this._adapter = config.adapter;
1188
1320
  this._stateAdapterInstance = config.stateAdapter;
1189
- this._messageHistory = config.messageHistory;
1321
+ this._threadHistory = config.threadHistory;
1190
1322
  }
1191
1323
  if (config.initialMessage) {
1192
1324
  this._recentMessages = [config.initialMessage];
@@ -1267,7 +1399,7 @@ var ThreadImpl = class _ThreadImpl {
1267
1399
  stateAdapter: this._stateAdapter,
1268
1400
  isDM: this.isDM,
1269
1401
  channelVisibility: this.channelVisibility,
1270
- messageHistory: this._messageHistory
1402
+ threadHistory: this._threadHistory
1271
1403
  });
1272
1404
  }
1273
1405
  return this._channel;
@@ -1279,7 +1411,7 @@ var ThreadImpl = class _ThreadImpl {
1279
1411
  get messages() {
1280
1412
  const adapter = this.adapter;
1281
1413
  const threadId = this.id;
1282
- const messageHistory = this._messageHistory;
1414
+ const threadHistory = this._threadHistory;
1283
1415
  return {
1284
1416
  async *[Symbol.asyncIterator]() {
1285
1417
  let cursor;
@@ -1299,8 +1431,8 @@ var ThreadImpl = class _ThreadImpl {
1299
1431
  }
1300
1432
  cursor = result.nextCursor;
1301
1433
  }
1302
- if (!yieldedAny && messageHistory) {
1303
- const cached = await messageHistory.getMessages(threadId);
1434
+ if (!yieldedAny && threadHistory) {
1435
+ const cached = await threadHistory.getMessages(threadId);
1304
1436
  for (let i = cached.length - 1; i >= 0; i--) {
1305
1437
  yield cached[i];
1306
1438
  }
@@ -1311,7 +1443,7 @@ var ThreadImpl = class _ThreadImpl {
1311
1443
  get allMessages() {
1312
1444
  const adapter = this.adapter;
1313
1445
  const threadId = this.id;
1314
- const messageHistory = this._messageHistory;
1446
+ const threadHistory = this._threadHistory;
1315
1447
  return {
1316
1448
  async *[Symbol.asyncIterator]() {
1317
1449
  let cursor;
@@ -1331,8 +1463,8 @@ var ThreadImpl = class _ThreadImpl {
1331
1463
  }
1332
1464
  cursor = result.nextCursor;
1333
1465
  }
1334
- if (!yieldedAny && messageHistory) {
1335
- const cached = await messageHistory.getMessages(threadId);
1466
+ if (!yieldedAny && threadHistory) {
1467
+ const cached = await threadHistory.getMessages(threadId);
1336
1468
  for (const message of cached) {
1337
1469
  yield message;
1338
1470
  }
@@ -1394,14 +1526,15 @@ var ThreadImpl = class _ThreadImpl {
1394
1526
  }
1395
1527
  postable = card;
1396
1528
  }
1529
+ postable = await this.processCallbackUrls(postable);
1397
1530
  const rawMessage = await this.adapter.postMessage(this.id, postable);
1398
1531
  const result = this.createSentMessage(
1399
1532
  rawMessage.id,
1400
1533
  postable,
1401
1534
  rawMessage.threadId
1402
1535
  );
1403
- if (this._messageHistory) {
1404
- await this._messageHistory.append(this.id, new Message(result));
1536
+ if (this._threadHistory) {
1537
+ await this._threadHistory.append(this.id, new Message(result));
1405
1538
  }
1406
1539
  return result;
1407
1540
  }
@@ -1427,6 +1560,7 @@ var ThreadImpl = class _ThreadImpl {
1427
1560
  } else {
1428
1561
  postable = message;
1429
1562
  }
1563
+ postable = await this.processCallbackUrls(postable);
1430
1564
  if (this.adapter.postEphemeral) {
1431
1565
  return this.adapter.postEphemeral(this.id, userId, postable);
1432
1566
  }
@@ -1445,6 +1579,24 @@ var ThreadImpl = class _ThreadImpl {
1445
1579
  }
1446
1580
  return null;
1447
1581
  }
1582
+ async processCallbackUrls(postable) {
1583
+ if (typeof postable === "string") {
1584
+ return postable;
1585
+ }
1586
+ if ("type" in postable && postable.type === "card") {
1587
+ return processCardCallbackUrls(postable, this._stateAdapter);
1588
+ }
1589
+ if ("card" in postable && postable.card?.type === "card") {
1590
+ const processed = await processCardCallbackUrls(
1591
+ postable.card,
1592
+ this._stateAdapter
1593
+ );
1594
+ if (processed !== postable.card) {
1595
+ return { ...postable, card: processed };
1596
+ }
1597
+ }
1598
+ return postable;
1599
+ }
1448
1600
  async schedule(message, options) {
1449
1601
  let postable;
1450
1602
  if (isJSX(message)) {
@@ -1456,6 +1608,9 @@ var ThreadImpl = class _ThreadImpl {
1456
1608
  } else {
1457
1609
  postable = message;
1458
1610
  }
1611
+ postable = await this.processCallbackUrls(
1612
+ postable
1613
+ );
1459
1614
  if (!this.adapter.scheduleMessage) {
1460
1615
  throw new NotImplementedError(
1461
1616
  "Scheduled messages are not supported by this adapter",
@@ -1467,7 +1622,7 @@ var ThreadImpl = class _ThreadImpl {
1467
1622
  /**
1468
1623
  * Handle streaming from an AsyncIterable.
1469
1624
  * Normalizes the stream (supports both textStream and fullStream from AI SDK),
1470
- * then uses adapter's native streaming if available, otherwise falls back to post+edit.
1625
+ * then uses the adapter's stream implementation if available, otherwise falls back to post+edit.
1471
1626
  */
1472
1627
  async handleStream(rawStream, callerOptions) {
1473
1628
  const textStream = fromFullStream(rawStream);
@@ -1505,8 +1660,8 @@ var ThreadImpl = class _ThreadImpl {
1505
1660
  { markdown: accumulated },
1506
1661
  raw.threadId
1507
1662
  );
1508
- if (this._messageHistory) {
1509
- await this._messageHistory.append(this.id, new Message(sent));
1663
+ if (this._threadHistory) {
1664
+ await this._threadHistory.append(this.id, new Message(sent));
1510
1665
  }
1511
1666
  return sent;
1512
1667
  }
@@ -1653,8 +1808,8 @@ var ThreadImpl = class _ThreadImpl {
1653
1808
  { markdown: accumulated },
1654
1809
  threadIdForEdits
1655
1810
  );
1656
- if (this._messageHistory) {
1657
- await this._messageHistory.append(this.id, new Message(sent));
1811
+ if (this._threadHistory) {
1812
+ await this._threadHistory.append(this.id, new Message(sent));
1658
1813
  }
1659
1814
  return sent;
1660
1815
  }
@@ -1662,11 +1817,8 @@ var ThreadImpl = class _ThreadImpl {
1662
1817
  const result = await this.adapter.fetchMessages(this.id, { limit: 50 });
1663
1818
  if (result.messages.length > 0) {
1664
1819
  this._recentMessages = result.messages;
1665
- } else if (this._messageHistory) {
1666
- this._recentMessages = await this._messageHistory.getMessages(
1667
- this.id,
1668
- 50
1669
- );
1820
+ } else if (this._threadHistory) {
1821
+ this._recentMessages = await this._threadHistory.getMessages(this.id, 50);
1670
1822
  } else {
1671
1823
  this._recentMessages = [];
1672
1824
  }
@@ -1778,6 +1930,7 @@ var ThreadImpl = class _ThreadImpl {
1778
1930
  }
1779
1931
  postable2 = card;
1780
1932
  }
1933
+ postable2 = await self.processCallbackUrls(postable2);
1781
1934
  await adapter.editMessage(threadId, messageId, postable2);
1782
1935
  return self.createSentMessage(messageId, postable2);
1783
1936
  },
@@ -1821,6 +1974,7 @@ var ThreadImpl = class _ThreadImpl {
1821
1974
  }
1822
1975
  postable = card;
1823
1976
  }
1977
+ postable = await self.processCallbackUrls(postable);
1824
1978
  await adapter.editMessage(threadId, messageId, postable);
1825
1979
  return self.createSentMessage(messageId, postable, threadId);
1826
1980
  },
@@ -1902,6 +2056,177 @@ function reviver(_key, value) {
1902
2056
  return value;
1903
2057
  }
1904
2058
 
2059
+ // src/thread-history.ts
2060
+ var DEFAULT_MAX_MESSAGES = 100;
2061
+ var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
2062
+ var KEY_PREFIX = "msg-history:";
2063
+ var ThreadHistoryCache = class {
2064
+ state;
2065
+ maxMessages;
2066
+ ttlMs;
2067
+ constructor(state, config) {
2068
+ this.state = state;
2069
+ this.maxMessages = config?.maxMessages ?? DEFAULT_MAX_MESSAGES;
2070
+ this.ttlMs = config?.ttlMs ?? DEFAULT_TTL_MS;
2071
+ }
2072
+ /**
2073
+ * Atomically append a message to the history for a thread.
2074
+ * Trims to maxMessages (keeps newest) and refreshes TTL.
2075
+ */
2076
+ async append(threadId, message) {
2077
+ const key = `${KEY_PREFIX}${threadId}`;
2078
+ const serialized = message.toJSON();
2079
+ serialized.raw = null;
2080
+ await this.state.appendToList(key, serialized, {
2081
+ maxLength: this.maxMessages,
2082
+ ttlMs: this.ttlMs
2083
+ });
2084
+ }
2085
+ /**
2086
+ * Get messages for a thread in chronological order (oldest first).
2087
+ *
2088
+ * @param threadId - The thread ID
2089
+ * @param limit - Optional limit on number of messages to return (returns newest N)
2090
+ */
2091
+ async getMessages(threadId, limit) {
2092
+ const key = `${KEY_PREFIX}${threadId}`;
2093
+ const stored = await this.state.getList(key);
2094
+ const sliced = limit && stored.length > limit ? stored.slice(stored.length - limit) : stored;
2095
+ return sliced.map((s) => Message.fromJSON(s));
2096
+ }
2097
+ };
2098
+
2099
+ // src/transcripts.ts
2100
+ var KEY_PREFIX2 = "transcripts:user:";
2101
+ var DEFAULT_MAX_PER_USER = 200;
2102
+ var DEFAULT_LIST_LIMIT = 50;
2103
+ var DURATION_RE = /^(\d+)([smhd])$/;
2104
+ var TOMBSTONE_MARKER = "__chatSdkTombstone";
2105
+ function isTombstone(value) {
2106
+ return typeof value === "object" && value !== null && value[TOMBSTONE_MARKER] === true;
2107
+ }
2108
+ var MS_PER_UNIT = {
2109
+ s: 1e3,
2110
+ m: 6e4,
2111
+ h: 36e5,
2112
+ d: 864e5
2113
+ };
2114
+ var TranscriptsApiImpl = class {
2115
+ state;
2116
+ maxPerUser;
2117
+ retentionMs;
2118
+ storeFormatted;
2119
+ constructor(state, config) {
2120
+ this.state = state;
2121
+ this.maxPerUser = config.maxPerUser ?? DEFAULT_MAX_PER_USER;
2122
+ this.retentionMs = parseDuration(config.retention);
2123
+ this.storeFormatted = config.storeFormatted ?? false;
2124
+ }
2125
+ async append(thread, message, options) {
2126
+ const isMessage = message instanceof Message;
2127
+ let userKey;
2128
+ let role;
2129
+ let platformMessageId;
2130
+ if (isMessage) {
2131
+ userKey = message.userKey;
2132
+ role = "user";
2133
+ platformMessageId = message.id;
2134
+ if (!userKey) {
2135
+ return null;
2136
+ }
2137
+ } else {
2138
+ userKey = options?.userKey;
2139
+ role = message.role;
2140
+ platformMessageId = message.platformMessageId;
2141
+ if (!userKey) {
2142
+ throw new Error(
2143
+ "transcripts.append: options.userKey is required when appending an AppendInput"
2144
+ );
2145
+ }
2146
+ }
2147
+ const entry = {
2148
+ id: crypto.randomUUID(),
2149
+ userKey,
2150
+ role,
2151
+ text: message.text,
2152
+ platform: thread.adapter.name,
2153
+ threadId: thread.id,
2154
+ timestamp: Date.now()
2155
+ };
2156
+ if (this.storeFormatted && message.formatted) {
2157
+ entry.formatted = message.formatted;
2158
+ }
2159
+ if (platformMessageId !== void 0) {
2160
+ entry.platformMessageId = platformMessageId;
2161
+ }
2162
+ await this.state.appendToList(keyFor(userKey), entry, {
2163
+ maxLength: this.maxPerUser,
2164
+ ttlMs: this.retentionMs
2165
+ });
2166
+ return entry;
2167
+ }
2168
+ async list(query) {
2169
+ const raw = await this.state.getList(
2170
+ keyFor(query.userKey)
2171
+ );
2172
+ let filtered = raw.filter(
2173
+ (entry) => !isTombstone(entry)
2174
+ );
2175
+ if (query.platforms && query.platforms.length > 0) {
2176
+ const platforms = new Set(query.platforms);
2177
+ filtered = filtered.filter((m) => platforms.has(m.platform));
2178
+ }
2179
+ if (query.threadId !== void 0) {
2180
+ const tid = query.threadId;
2181
+ filtered = filtered.filter((m) => m.threadId === tid);
2182
+ }
2183
+ if (query.roles && query.roles.length > 0) {
2184
+ const roles = new Set(query.roles);
2185
+ filtered = filtered.filter((m) => roles.has(m.role));
2186
+ }
2187
+ const limit = query.limit ?? DEFAULT_LIST_LIMIT;
2188
+ if (filtered.length > limit) {
2189
+ filtered = filtered.slice(filtered.length - limit);
2190
+ }
2191
+ return filtered;
2192
+ }
2193
+ async count(query) {
2194
+ const raw = await this.state.getList(keyFor(query.userKey));
2195
+ return raw.filter((entry) => !isTombstone(entry)).length;
2196
+ }
2197
+ async delete(target) {
2198
+ const key = keyFor(target.userKey);
2199
+ const existing = await this.state.getList(key);
2200
+ const previous = existing.filter((entry) => !isTombstone(entry)).length;
2201
+ const tombstone = { [TOMBSTONE_MARKER]: true };
2202
+ await this.state.appendToList(key, tombstone, {
2203
+ maxLength: 1,
2204
+ ttlMs: this.retentionMs
2205
+ });
2206
+ return { deleted: previous };
2207
+ }
2208
+ };
2209
+ function keyFor(userKey) {
2210
+ return `${KEY_PREFIX2}${userKey}`;
2211
+ }
2212
+ function parseDuration(value) {
2213
+ if (value === void 0) {
2214
+ return void 0;
2215
+ }
2216
+ if (typeof value === "number") {
2217
+ return value;
2218
+ }
2219
+ const match = DURATION_RE.exec(value);
2220
+ if (!match) {
2221
+ throw new Error(
2222
+ `Invalid duration: ${value} (expected number of ms, or "<n>[smhd]")`
2223
+ );
2224
+ }
2225
+ const n = Number.parseInt(match[1], 10);
2226
+ const unit = match[2];
2227
+ return n * MS_PER_UNIT[unit];
2228
+ }
2229
+
1905
2230
  // src/chat.ts
1906
2231
  var DEFAULT_LOCK_TTL_MS = 3e4;
1907
2232
  function sleep(ms) {
@@ -1931,6 +2256,21 @@ var Chat = class {
1931
2256
  setChatSingleton(this);
1932
2257
  return this;
1933
2258
  }
2259
+ /**
2260
+ * Cross-platform per-user transcript store.
2261
+ *
2262
+ * Available only when `transcripts` is configured on the Chat instance
2263
+ * (and an `identity` resolver is set). Throws on access otherwise so
2264
+ * callers fail loudly rather than silently no-op'ing.
2265
+ */
2266
+ get transcripts() {
2267
+ if (!this._transcripts) {
2268
+ throw new Error(
2269
+ "chat.transcripts is not configured \u2014 pass `transcripts` and `identity` to ChatConfig to enable it"
2270
+ );
2271
+ }
2272
+ return this._transcripts;
2273
+ }
1934
2274
  /**
1935
2275
  * Get the registered singleton Chat instance.
1936
2276
  * Throws if no singleton has been registered.
@@ -1952,7 +2292,9 @@ var Chat = class {
1952
2292
  _fallbackStreamingPlaceholderText;
1953
2293
  _dedupeTtlMs;
1954
2294
  _onLockConflict;
1955
- _messageHistory;
2295
+ _threadHistory;
2296
+ _identity;
2297
+ _transcripts;
1956
2298
  _concurrencyStrategy;
1957
2299
  _concurrencyConfig;
1958
2300
  _concurrentSlots = /* @__PURE__ */ new Map();
@@ -2035,10 +2377,24 @@ var Chat = class {
2035
2377
  queueEntryTtlMs: 9e4
2036
2378
  };
2037
2379
  }
2038
- this._messageHistory = new MessageHistoryCache(
2380
+ this._threadHistory = new ThreadHistoryCache(
2039
2381
  this._stateAdapter,
2040
- config.messageHistory
2382
+ config.threadHistory ?? config.messageHistory
2041
2383
  );
2384
+ if (config.transcripts) {
2385
+ if (!config.identity) {
2386
+ throw new Error(
2387
+ "ChatConfig.transcripts requires ChatConfig.identity to be set \u2014 the cross-platform user key must be resolvable"
2388
+ );
2389
+ }
2390
+ this._identity = config.identity;
2391
+ this._transcripts = new TranscriptsApiImpl(
2392
+ this._stateAdapter,
2393
+ config.transcripts
2394
+ );
2395
+ } else {
2396
+ this._identity = config.identity;
2397
+ }
2042
2398
  const webhooks = {};
2043
2399
  for (const [name, adapter] of Object.entries(config.adapters)) {
2044
2400
  this.adapters.set(name, adapter);
@@ -2357,12 +2713,14 @@ var Chat = class {
2357
2713
  const task = (async () => {
2358
2714
  const message = typeof messageOrFactory === "function" ? await messageOrFactory() : messageOrFactory;
2359
2715
  await this.handleIncomingMessage(adapter, threadId, message);
2360
- })().catch((err) => {
2716
+ })();
2717
+ const tracked = task.catch((err) => {
2361
2718
  this.logger.error("Message processing error", { error: err, threadId });
2362
2719
  });
2363
2720
  if (options?.waitUntil) {
2364
- options.waitUntil(task);
2721
+ options.waitUntil(tracked);
2365
2722
  }
2723
+ return task;
2366
2724
  }
2367
2725
  /**
2368
2726
  * Process an incoming reaction event from an adapter.
@@ -2420,20 +2778,22 @@ var Chat = class {
2420
2778
  }
2421
2779
  }
2422
2780
  }
2423
- async processModalSubmit(event, contextId, _options) {
2424
- const { relatedThread, relatedMessage, relatedChannel } = await this.retrieveModalContext(event.adapter.name, contextId);
2781
+ async processModalSubmit(event, contextId, options) {
2782
+ const { callbackUrl, relatedThread, relatedMessage, relatedChannel } = await this.retrieveModalContext(event.adapter.name, contextId);
2425
2783
  const fullEvent = {
2426
2784
  ...event,
2427
2785
  relatedThread,
2428
2786
  relatedMessage,
2429
2787
  relatedChannel
2430
2788
  };
2789
+ let result;
2431
2790
  for (const { callbackIds, handler } of this.modalSubmitHandlers) {
2432
2791
  if (callbackIds.length === 0 || callbackIds.includes(event.callbackId)) {
2433
2792
  try {
2434
2793
  const response = await handler(fullEvent);
2435
2794
  if (response) {
2436
- return response;
2795
+ result = response;
2796
+ break;
2437
2797
  }
2438
2798
  } catch (err) {
2439
2799
  this.logger.error("Modal submit handler error", {
@@ -2443,6 +2803,30 @@ var Chat = class {
2443
2803
  }
2444
2804
  }
2445
2805
  }
2806
+ if (callbackUrl && result?.action !== "errors") {
2807
+ const task = postToCallbackUrl(callbackUrl, {
2808
+ type: "modal_submit",
2809
+ callbackId: event.callbackId,
2810
+ values: event.values,
2811
+ user: { id: event.user.userId, name: event.user.userName }
2812
+ }).then(({ error }) => {
2813
+ if (error) {
2814
+ this.logger.error("Modal callbackUrl POST failed", {
2815
+ callbackUrl,
2816
+ error
2817
+ });
2818
+ }
2819
+ }).catch((error) => {
2820
+ this.logger.error("Modal callbackUrl POST failed", {
2821
+ callbackUrl,
2822
+ error
2823
+ });
2824
+ });
2825
+ if (options?.waitUntil) {
2826
+ options.waitUntil(task);
2827
+ }
2828
+ }
2829
+ return result;
2446
2830
  }
2447
2831
  processModalClose(event, contextId, options) {
2448
2832
  const task = (async () => {
@@ -2594,7 +2978,8 @@ var Chat = class {
2594
2978
  contextId,
2595
2979
  void 0,
2596
2980
  void 0,
2597
- channel
2981
+ channel,
2982
+ modalElement.callbackUrl
2598
2983
  );
2599
2984
  if (options?.onOpenModal) {
2600
2985
  return options.onOpenModal(modalElement, contextId);
@@ -2631,12 +3016,13 @@ var Chat = class {
2631
3016
  * Store modal context server-side with a context ID.
2632
3017
  * Called when opening a modal to preserve thread/message/channel for the submit handler.
2633
3018
  */
2634
- async storeModalContext(adapterName, contextId, thread, message, channel) {
3019
+ async storeModalContext(adapterName, contextId, thread, message, channel, callbackUrl) {
2635
3020
  const key = `modal-context:${adapterName}:${contextId}`;
2636
3021
  const context = {
2637
3022
  thread: thread?.toJSON(),
2638
3023
  message: message?.toJSON(),
2639
- channel: channel?.toJSON()
3024
+ channel: channel?.toJSON(),
3025
+ callbackUrl
2640
3026
  };
2641
3027
  try {
2642
3028
  await this._stateAdapter.set(key, context, MODAL_CONTEXT_TTL_MS);
@@ -2654,6 +3040,7 @@ var Chat = class {
2654
3040
  async retrieveModalContext(adapterName, contextId) {
2655
3041
  if (!contextId) {
2656
3042
  return {
3043
+ callbackUrl: void 0,
2657
3044
  relatedThread: void 0,
2658
3045
  relatedMessage: void 0,
2659
3046
  relatedChannel: void 0
@@ -2663,6 +3050,7 @@ var Chat = class {
2663
3050
  const stored = await this._stateAdapter.get(key);
2664
3051
  if (!stored) {
2665
3052
  return {
3053
+ callbackUrl: void 0,
2666
3054
  relatedThread: void 0,
2667
3055
  relatedMessage: void 0,
2668
3056
  relatedChannel: void 0
@@ -2683,7 +3071,12 @@ var Chat = class {
2683
3071
  if (stored.channel) {
2684
3072
  relatedChannel = ChannelImpl.fromJSON(stored.channel, adapter);
2685
3073
  }
2686
- return { relatedThread, relatedMessage, relatedChannel };
3074
+ return {
3075
+ callbackUrl: stored.callbackUrl,
3076
+ relatedThread,
3077
+ relatedMessage,
3078
+ relatedChannel
3079
+ };
2687
3080
  }
2688
3081
  /**
2689
3082
  * Handle an action event internally.
@@ -2703,6 +3096,33 @@ var Chat = class {
2703
3096
  });
2704
3097
  return;
2705
3098
  }
3099
+ const { callbackToken } = decodeCallbackValue(event.value);
3100
+ let resolved = null;
3101
+ if (callbackToken) {
3102
+ resolved = await resolveCallbackUrl(callbackToken, this._stateAdapter);
3103
+ }
3104
+ const actionEvent = resolved ? { ...event, value: resolved.originalValue } : event;
3105
+ let callbackUrlPromise;
3106
+ if (resolved) {
3107
+ const callbackUrl = resolved.url;
3108
+ callbackUrlPromise = (async () => {
3109
+ const { error } = await postToCallbackUrl(callbackUrl, {
3110
+ type: "action",
3111
+ actionId: event.actionId,
3112
+ value: resolved.originalValue,
3113
+ user: { id: event.user.userId, name: event.user.userName },
3114
+ threadId: event.threadId,
3115
+ messageId: event.messageId
3116
+ });
3117
+ if (error) {
3118
+ this.logger.error("Button callbackUrl POST failed", {
3119
+ callbackUrl,
3120
+ actionId: event.actionId,
3121
+ error
3122
+ });
3123
+ }
3124
+ })();
3125
+ }
2706
3126
  const isSubscribed = false;
2707
3127
  const messageForThread = event.messageId ? new Message({
2708
3128
  id: event.messageId,
@@ -2721,7 +3141,7 @@ var Chat = class {
2721
3141
  isSubscribed
2722
3142
  ) : null;
2723
3143
  const fullEvent = {
2724
- ...event,
3144
+ ...actionEvent,
2725
3145
  thread,
2726
3146
  openModal: async (modal) => {
2727
3147
  if (!(event.triggerId || options?.onOpenModal)) {
@@ -2769,7 +3189,8 @@ var Chat = class {
2769
3189
  contextId,
2770
3190
  thread ? thread : void 0,
2771
3191
  message,
2772
- channel
3192
+ channel,
3193
+ modalElement.callbackUrl
2773
3194
  );
2774
3195
  if (options?.onOpenModal) {
2775
3196
  return options.onOpenModal(modalElement, contextId);
@@ -2801,6 +3222,9 @@ var Chat = class {
2801
3222
  await handler(fullEvent);
2802
3223
  }
2803
3224
  }
3225
+ if (callbackUrlPromise) {
3226
+ await callbackUrlPromise;
3227
+ }
2804
3228
  }
2805
3229
  /**
2806
3230
  * Handle a reaction event internally.
@@ -3113,6 +3537,7 @@ var Chat = class {
3113
3537
  * - Concurrency: Controlled by `concurrency` config (drop, queue, debounce, concurrent)
3114
3538
  */
3115
3539
  async handleIncomingMessage(adapter, threadId, message) {
3540
+ setMessageAdapter(message, adapter);
3116
3541
  this.logger.debug("Incoming message", {
3117
3542
  adapter: adapter.name,
3118
3543
  threadId,
@@ -3144,11 +3569,11 @@ var Chat = class {
3144
3569
  });
3145
3570
  return;
3146
3571
  }
3147
- if (adapter.persistMessageHistory) {
3572
+ if (adapter.persistThreadHistory || adapter.persistMessageHistory) {
3148
3573
  const channelId = adapter.channelIdFromThreadId(threadId);
3149
- const appends = [this._messageHistory.append(threadId, message)];
3574
+ const appends = [this._threadHistory.append(threadId, message)];
3150
3575
  if (channelId !== threadId) {
3151
- appends.push(this._messageHistory.append(channelId, message));
3576
+ appends.push(this._threadHistory.append(channelId, message));
3152
3577
  }
3153
3578
  await Promise.all(appends);
3154
3579
  }
@@ -3436,6 +3861,25 @@ var Chat = class {
3436
3861
  message,
3437
3862
  isSubscribed
3438
3863
  );
3864
+ if (this._identity && message.userKey === void 0) {
3865
+ try {
3866
+ const resolved = await this._identity({
3867
+ adapter: adapter.name,
3868
+ author: message.author,
3869
+ message
3870
+ });
3871
+ if (resolved) {
3872
+ message.userKey = resolved;
3873
+ }
3874
+ } catch (err) {
3875
+ this.logger.warn("Identity resolver threw; skipping userKey", {
3876
+ error: err,
3877
+ adapter: adapter.name,
3878
+ threadId,
3879
+ authorUserId: message.author.userId
3880
+ });
3881
+ }
3882
+ }
3439
3883
  const isDM = adapter.isDM?.(threadId) ?? false;
3440
3884
  if (isDM && this.directMessageHandlers.length > 0) {
3441
3885
  this.logger.debug("Direct message received - calling handlers", {
@@ -3517,7 +3961,7 @@ var Chat = class {
3517
3961
  logger: this.logger,
3518
3962
  streamingUpdateIntervalMs: this._streamingUpdateIntervalMs,
3519
3963
  fallbackStreamingPlaceholderText: this._fallbackStreamingPlaceholderText,
3520
- messageHistory: adapter.persistMessageHistory ? this._messageHistory : void 0
3964
+ threadHistory: adapter.persistThreadHistory || adapter.persistMessageHistory ? this._threadHistory : void 0
3521
3965
  });
3522
3966
  }
3523
3967
  /**
@@ -3563,6 +4007,9 @@ var Chat = class {
3563
4007
  */
3564
4008
  rehydrateMessage(raw, adapter) {
3565
4009
  if (raw instanceof Message) {
4010
+ if (adapter) {
4011
+ setMessageAdapter(raw, adapter);
4012
+ }
3566
4013
  return raw;
3567
4014
  }
3568
4015
  const obj = raw;
@@ -3592,6 +4039,9 @@ var Chat = class {
3592
4039
  links: obj.links ?? []
3593
4040
  });
3594
4041
  }
4042
+ if (adapter) {
4043
+ setMessageAdapter(msg, adapter);
4044
+ }
3595
4045
  const rehydrate = adapter?.rehydrateAttachment?.bind(adapter);
3596
4046
  if (rehydrate && msg.attachments.length > 0) {
3597
4047
  msg.attachments = msg.attachments.map(
@@ -3607,6 +4057,9 @@ var Chat = class {
3607
4057
  }
3608
4058
  };
3609
4059
 
4060
+ // src/message-history.ts
4061
+ var MessageHistoryCache = ThreadHistoryCache;
4062
+
3610
4063
  // src/plan.ts
3611
4064
  function contentToPlainText(content) {
3612
4065
  if (!content) {
@@ -4107,6 +4560,8 @@ function convertEmojiPlaceholders(text2, platform, resolver = defaultEmojiResolv
4107
4560
  return resolver.toGChat(emojiName);
4108
4561
  case "discord":
4109
4562
  return resolver.toDiscord(emojiName);
4563
+ case "messenger":
4564
+ return resolver.toGChat(emojiName);
4110
4565
  case "github":
4111
4566
  return resolver.toGChat(emojiName);
4112
4567
  case "linear":
@@ -4308,6 +4763,7 @@ export {
4308
4763
  THREAD_STATE_TTL_MS,
4309
4764
  Table2 as Table,
4310
4765
  TextInput2 as TextInput,
4766
+ ThreadHistoryCache,
4311
4767
  ThreadImpl,
4312
4768
  blockquote,
4313
4769
  cardChildToFallbackText2 as cardChildToFallbackText,