chat 4.25.0 → 4.27.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 (37) hide show
  1. package/dist/{chunk-OPV5U4WG.js → chunk-AN7MRAVW.js} +39 -0
  2. package/dist/index.d.ts +235 -6
  3. package/dist/index.js +353 -76
  4. package/dist/{jsx-runtime-DxATbnrP.d.ts → jsx-runtime-Co9uV6l7.d.ts} +39 -5
  5. package/dist/jsx-runtime.d.ts +1 -1
  6. package/dist/jsx-runtime.js +1 -1
  7. package/docs/adapters.mdx +30 -30
  8. package/docs/api/cards.mdx +5 -0
  9. package/docs/api/chat.mdx +95 -1
  10. package/docs/api/message.mdx +5 -1
  11. package/docs/api/modals.mdx +1 -1
  12. package/docs/api/thread.mdx +23 -1
  13. package/docs/cards.mdx +6 -0
  14. package/docs/contributing/publishing.mdx +33 -0
  15. package/docs/files.mdx +1 -0
  16. package/docs/getting-started.mdx +2 -12
  17. package/docs/meta.json +0 -2
  18. package/docs/modals.mdx +74 -2
  19. package/docs/state.mdx +1 -1
  20. package/docs/streaming.mdx +13 -5
  21. package/docs/threads-messages-channels.mdx +34 -0
  22. package/docs/usage.mdx +2 -2
  23. package/package.json +3 -2
  24. package/resources/guides/create-a-discord-support-bot-with-nuxt-and-redis.md +180 -0
  25. package/resources/guides/how-to-build-a-slack-bot-with-next-js-and-redis.md +134 -0
  26. package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +220 -0
  27. package/resources/guides/run-and-track-deploys-from-slack.md +270 -0
  28. package/resources/guides/ship-a-github-code-review-bot-with-hono-and-redis.md +147 -0
  29. package/resources/guides/triage-form-submissions-with-chat-sdk.md +178 -0
  30. package/resources/templates.json +19 -0
  31. package/docs/adapters/whatsapp.mdx +0 -222
  32. package/docs/guides/code-review-hono.mdx +0 -241
  33. package/docs/guides/discord-nuxt.mdx +0 -227
  34. package/docs/guides/durable-chat-sessions-nextjs.mdx +0 -331
  35. package/docs/guides/meta.json +0 -10
  36. package/docs/guides/scheduled-posts-neon.mdx +0 -447
  37. package/docs/guides/slack-nextjs.mdx +0 -234
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  CardLink,
7
7
  CardText,
8
8
  Divider,
9
+ ExternalSelect,
9
10
  Field,
10
11
  Fields,
11
12
  Image,
@@ -59,7 +60,7 @@ import {
59
60
  toModalElement,
60
61
  toPlainText,
61
62
  walkAst
62
- } from "./chunk-OPV5U4WG.js";
63
+ } from "./chunk-AN7MRAVW.js";
63
64
 
64
65
  // src/ai.ts
65
66
  var TEXT_MIME_PREFIXES = [
@@ -330,7 +331,8 @@ var Message = class _Message {
330
331
  mimeType: att.mimeType,
331
332
  size: att.size,
332
333
  width: att.width,
333
- height: att.height
334
+ height: att.height,
335
+ fetchMetadata: att.fetchMetadata
334
336
  })),
335
337
  isMention: this.isMention,
336
338
  links: this.links.length > 0 ? this.links.map((link2) => ({
@@ -729,7 +731,7 @@ var ChannelImpl = class _ChannelImpl {
729
731
  return {
730
732
  _type: "chat:Channel",
731
733
  id: this.id,
732
- adapterName: this.adapter.name,
734
+ adapterName: this._adapterName ?? this.adapter.name,
733
735
  channelVisibility: this.channelVisibility,
734
736
  isDM: this.isDM
735
737
  };
@@ -1338,6 +1340,19 @@ var ThreadImpl = class _ThreadImpl {
1338
1340
  }
1339
1341
  };
1340
1342
  }
1343
+ async getParticipants() {
1344
+ const seen = /* @__PURE__ */ new Map();
1345
+ if (this._currentMessage && !this._currentMessage.author.isMe && !this._currentMessage.author.isBot) {
1346
+ seen.set(this._currentMessage.author.userId, this._currentMessage.author);
1347
+ }
1348
+ for await (const message of this.allMessages) {
1349
+ if (message.author.isMe || message.author.isBot || seen.has(message.author.userId)) {
1350
+ continue;
1351
+ }
1352
+ seen.set(message.author.userId, message.author);
1353
+ }
1354
+ return [...seen.values()];
1355
+ }
1341
1356
  async isSubscribed() {
1342
1357
  if (this._isSubscribedContext) {
1343
1358
  return true;
@@ -1355,6 +1370,16 @@ var ThreadImpl = class _ThreadImpl {
1355
1370
  }
1356
1371
  async post(message) {
1357
1372
  if (isPostableObject(message)) {
1373
+ if (message.kind === "stream") {
1374
+ const data = message.getPostData();
1375
+ const streamOptions = {
1376
+ ...data.options.updateIntervalMs ? { updateIntervalMs: data.options.updateIntervalMs } : {},
1377
+ ...data.options.groupTasks ? { taskDisplayMode: data.options.groupTasks } : {},
1378
+ ...data.options.endWith ? { stopBlocks: data.options.endWith } : {}
1379
+ };
1380
+ await this.handleStream(data.stream, streamOptions);
1381
+ return message;
1382
+ }
1358
1383
  await this.handlePostableObject(message);
1359
1384
  return message;
1360
1385
  }
@@ -1444,13 +1469,14 @@ var ThreadImpl = class _ThreadImpl {
1444
1469
  * Normalizes the stream (supports both textStream and fullStream from AI SDK),
1445
1470
  * then uses adapter's native streaming if available, otherwise falls back to post+edit.
1446
1471
  */
1447
- async handleStream(rawStream) {
1472
+ async handleStream(rawStream, callerOptions) {
1448
1473
  const textStream = fromFullStream(rawStream);
1449
- const options = {};
1474
+ const options = { ...callerOptions };
1450
1475
  if (this._currentMessage) {
1451
1476
  options.recipientUserId = this._currentMessage.author.userId;
1452
- const raw = this._currentMessage.raw;
1453
- options.recipientTeamId = raw?.team_id ?? raw?.team;
1477
+ options.recipientTeamId = this.extractSlackRecipientTeamId(
1478
+ this._currentMessage.raw
1479
+ );
1454
1480
  }
1455
1481
  if (this.adapter.stream) {
1456
1482
  let accumulated = "";
@@ -1508,6 +1534,31 @@ var ThreadImpl = class _ThreadImpl {
1508
1534
  };
1509
1535
  return this.fallbackStream(textOnlyStream, options);
1510
1536
  }
1537
+ /**
1538
+ * Slack payloads carry the workspace ID in a few different shapes depending on
1539
+ * the webhook type:
1540
+ * - Message events: `team_id` or `team` as a string
1541
+ * - `block_actions` payloads: `team.id` (object), with `user.team_id` as a fallback
1542
+ */
1543
+ extractSlackRecipientTeamId(raw) {
1544
+ if (!raw || typeof raw !== "object") {
1545
+ return void 0;
1546
+ }
1547
+ const payload = raw;
1548
+ if (typeof payload.team_id === "string" && payload.team_id) {
1549
+ return payload.team_id;
1550
+ }
1551
+ if (typeof payload.team === "string" && payload.team) {
1552
+ return payload.team;
1553
+ }
1554
+ if (payload.team && typeof payload.team === "object" && typeof payload.team.id === "string" && payload.team.id) {
1555
+ return payload.team.id;
1556
+ }
1557
+ if (typeof payload.user?.team_id === "string" && payload.user.team_id) {
1558
+ return payload.user.team_id;
1559
+ }
1560
+ return void 0;
1561
+ }
1511
1562
  async startTyping(status) {
1512
1563
  await this.adapter.startTyping(this.id, status);
1513
1564
  }
@@ -1541,7 +1592,7 @@ var ThreadImpl = class _ThreadImpl {
1541
1592
  return;
1542
1593
  }
1543
1594
  const content = renderer.render();
1544
- if (content !== lastEditContent) {
1595
+ if (content.trim() && content !== lastEditContent) {
1545
1596
  try {
1546
1597
  await this.adapter.editMessage(threadIdForEdits, msg.id, {
1547
1598
  markdown: content
@@ -1563,12 +1614,14 @@ var ThreadImpl = class _ThreadImpl {
1563
1614
  renderer.push(chunk);
1564
1615
  if (!msg) {
1565
1616
  const content = renderer.render();
1566
- msg = await this.adapter.postMessage(this.id, {
1567
- markdown: content
1568
- });
1569
- threadIdForEdits = msg.threadId || this.id;
1570
- lastEditContent = content;
1571
- scheduleNextEdit();
1617
+ if (content.trim()) {
1618
+ msg = await this.adapter.postMessage(this.id, {
1619
+ markdown: content
1620
+ });
1621
+ threadIdForEdits = msg.threadId || this.id;
1622
+ lastEditContent = content;
1623
+ scheduleNextEdit();
1624
+ }
1572
1625
  }
1573
1626
  }
1574
1627
  } finally {
@@ -1585,12 +1638,12 @@ var ThreadImpl = class _ThreadImpl {
1585
1638
  const finalContent = renderer.finish();
1586
1639
  if (!msg) {
1587
1640
  msg = await this.adapter.postMessage(this.id, {
1588
- markdown: accumulated
1641
+ markdown: accumulated.trim() ? accumulated : " "
1589
1642
  });
1590
1643
  threadIdForEdits = msg.threadId || this.id;
1591
1644
  lastEditContent = accumulated;
1592
1645
  }
1593
- if (finalContent !== lastEditContent) {
1646
+ if (finalContent.trim() && finalContent !== lastEditContent) {
1594
1647
  await this.adapter.editMessage(threadIdForEdits, msg.id, {
1595
1648
  markdown: accumulated
1596
1649
  });
@@ -1642,7 +1695,7 @@ var ThreadImpl = class _ThreadImpl {
1642
1695
  channelVisibility: this.channelVisibility,
1643
1696
  currentMessage: this._currentMessage?.toJSON(),
1644
1697
  isDM: this.isDM,
1645
- adapterName: this.adapter.name
1698
+ adapterName: this._adapterName ?? this.adapter.name
1646
1699
  };
1647
1700
  }
1648
1701
  /**
@@ -1832,13 +1885,32 @@ function extractMessageContent2(message) {
1832
1885
  throw new Error("Invalid PostableMessage format");
1833
1886
  }
1834
1887
 
1888
+ // src/reviver.ts
1889
+ function reviver(_key, value) {
1890
+ if (value && typeof value === "object" && "_type" in value) {
1891
+ const typed = value;
1892
+ if (typed._type === "chat:Thread") {
1893
+ return ThreadImpl.fromJSON(value);
1894
+ }
1895
+ if (typed._type === "chat:Channel") {
1896
+ return ChannelImpl.fromJSON(value);
1897
+ }
1898
+ if (typed._type === "chat:Message") {
1899
+ return Message.fromJSON(value);
1900
+ }
1901
+ }
1902
+ return value;
1903
+ }
1904
+
1835
1905
  // src/chat.ts
1836
1906
  var DEFAULT_LOCK_TTL_MS = 3e4;
1837
1907
  function sleep(ms) {
1838
1908
  return new Promise((resolve) => setTimeout(resolve, ms));
1839
1909
  }
1840
- var SLACK_USER_ID_REGEX = /^U[A-Z0-9]+$/i;
1910
+ var SLACK_USER_ID_REGEX = /^[UW][A-Z0-9]+$/;
1841
1911
  var DISCORD_SNOWFLAKE_REGEX = /^\d{17,19}$/;
1912
+ var LINEAR_UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1913
+ var NUMERIC_REGEX = /^\d+$/;
1842
1914
  var DEDUPE_TTL_MS = 5 * 60 * 1e3;
1843
1915
  var MODAL_CONTEXT_TTL_MS = 24 * 60 * 60 * 1e3;
1844
1916
  var Chat = class {
@@ -1883,6 +1955,7 @@ var Chat = class {
1883
1955
  _messageHistory;
1884
1956
  _concurrencyStrategy;
1885
1957
  _concurrencyConfig;
1958
+ _concurrentSlots = /* @__PURE__ */ new Map();
1886
1959
  _lockScope;
1887
1960
  mentionHandlers = [];
1888
1961
  directMessageHandlers = [];
@@ -1890,6 +1963,7 @@ var Chat = class {
1890
1963
  subscribedMessageHandlers = [];
1891
1964
  reactionHandlers = [];
1892
1965
  actionHandlers = [];
1966
+ optionsLoadHandlers = [];
1893
1967
  modalSubmitHandlers = [];
1894
1968
  modalCloseHandlers = [];
1895
1969
  slashCommandHandlers = [];
@@ -1915,6 +1989,11 @@ var Chat = class {
1915
1989
  this._dedupeTtlMs = config.dedupeTtlMs ?? DEDUPE_TTL_MS;
1916
1990
  this._onLockConflict = config.onLockConflict;
1917
1991
  this._lockScope = config.lockScope;
1992
+ if (typeof config.logger === "string") {
1993
+ this.logger = new ConsoleLogger(config.logger);
1994
+ } else {
1995
+ this.logger = config.logger || new ConsoleLogger("info");
1996
+ }
1918
1997
  const concurrency = config.concurrency;
1919
1998
  if (concurrency) {
1920
1999
  if (typeof concurrency === "string") {
@@ -1927,6 +2006,16 @@ var Chat = class {
1927
2006
  queueEntryTtlMs: 9e4
1928
2007
  };
1929
2008
  } else {
2009
+ if (concurrency.maxConcurrent !== void 0 && concurrency.maxConcurrent < 1) {
2010
+ throw new Error(
2011
+ `concurrency.maxConcurrent must be >= 1 (got ${concurrency.maxConcurrent})`
2012
+ );
2013
+ }
2014
+ if (concurrency.maxConcurrent !== void 0 && concurrency.strategy !== "concurrent") {
2015
+ this.logger.warn(
2016
+ `concurrency.maxConcurrent has no effect when strategy is "${concurrency.strategy}" \u2014 it only applies to the "concurrent" strategy.`
2017
+ );
2018
+ }
1930
2019
  this._concurrencyStrategy = concurrency.strategy;
1931
2020
  this._concurrencyConfig = {
1932
2021
  debounceMs: concurrency.debounceMs ?? 1500,
@@ -1950,11 +2039,6 @@ var Chat = class {
1950
2039
  this._stateAdapter,
1951
2040
  config.messageHistory
1952
2041
  );
1953
- if (typeof config.logger === "string") {
1954
- this.logger = new ConsoleLogger(config.logger);
1955
- } else {
1956
- this.logger = config.logger || new ConsoleLogger("info");
1957
- }
1958
2042
  const webhooks = {};
1959
2043
  for (const [name, adapter] of Object.entries(config.adapters)) {
1960
2044
  this.adapters.set(name, adapter);
@@ -2161,6 +2245,19 @@ var Chat = class {
2161
2245
  this.logger.debug("Registered action handler", { actionIds });
2162
2246
  }
2163
2247
  }
2248
+ onOptionsLoad(actionIdOrHandler, handler) {
2249
+ if (typeof actionIdOrHandler === "function") {
2250
+ this.optionsLoadHandlers.push({
2251
+ actionIds: [],
2252
+ handler: actionIdOrHandler
2253
+ });
2254
+ this.logger.debug("Registered options load handler for all action IDs");
2255
+ } else if (handler) {
2256
+ const actionIds = Array.isArray(actionIdOrHandler) ? actionIdOrHandler : [actionIdOrHandler];
2257
+ this.optionsLoadHandlers.push({ actionIds, handler });
2258
+ this.logger.debug("Registered options load handler", { actionIds });
2259
+ }
2260
+ }
2164
2261
  onModalSubmit(callbackIdOrHandler, handler) {
2165
2262
  if (typeof callbackIdOrHandler === "function") {
2166
2263
  this.modalSubmitHandlers.push({
@@ -2248,21 +2345,7 @@ var Chat = class {
2248
2345
  */
2249
2346
  reviver() {
2250
2347
  this.registerSingleton();
2251
- return function reviver(_key, value) {
2252
- if (value && typeof value === "object" && "_type" in value) {
2253
- const typed = value;
2254
- if (typed._type === "chat:Thread") {
2255
- return ThreadImpl.fromJSON(value);
2256
- }
2257
- if (typed._type === "chat:Channel") {
2258
- return ChannelImpl.fromJSON(value);
2259
- }
2260
- if (typed._type === "chat:Message") {
2261
- return Message.fromJSON(value);
2262
- }
2263
- }
2264
- return value;
2265
- };
2348
+ return reviver;
2266
2349
  }
2267
2350
  // ChatInstance interface implementations
2268
2351
  /**
@@ -2314,6 +2397,29 @@ var Chat = class {
2314
2397
  }
2315
2398
  return task;
2316
2399
  }
2400
+ async processOptionsLoad(event, _options) {
2401
+ const matchingHandlers = [
2402
+ ...this.optionsLoadHandlers.filter(
2403
+ ({ actionIds }) => actionIds.length > 0 && actionIds.includes(event.actionId)
2404
+ ),
2405
+ ...this.optionsLoadHandlers.filter(
2406
+ ({ actionIds }) => actionIds.length === 0
2407
+ )
2408
+ ];
2409
+ for (const { handler } of matchingHandlers) {
2410
+ try {
2411
+ const options = await handler(event);
2412
+ if (options) {
2413
+ return options;
2414
+ }
2415
+ } catch (err) {
2416
+ this.logger.error("Options load handler error", {
2417
+ error: err,
2418
+ actionId: event.actionId
2419
+ });
2420
+ }
2421
+ }
2422
+ }
2317
2423
  async processModalSubmit(event, contextId, _options) {
2318
2424
  const { relatedThread, relatedMessage, relatedChannel } = await this.retrieveModalContext(event.adapter.name, contextId);
2319
2425
  const fullEvent = {
@@ -2813,6 +2919,33 @@ var Chat = class {
2813
2919
  const threadId = await adapter.openDM(userId);
2814
2920
  return this.createThread(adapter, threadId, {}, false);
2815
2921
  }
2922
+ /**
2923
+ * Look up user information by user ID.
2924
+ *
2925
+ * The adapter is automatically inferred from the user ID format.
2926
+ * Returns user details including email (where available — requires
2927
+ * appropriate scopes on some platforms, e.g. `users:read.email` on Slack).
2928
+ *
2929
+ * @param user - Platform-specific user ID string, or an Author object
2930
+ * @returns User info, or null if user not found
2931
+ *
2932
+ * @example
2933
+ * ```typescript
2934
+ * const user = await chat.getUser("U123456");
2935
+ * console.log(user?.email); // "alice@company.com"
2936
+ * ```
2937
+ */
2938
+ async getUser(user) {
2939
+ const userId = typeof user === "string" ? user : user.userId;
2940
+ const adapter = this.inferAdapterFromUserId(userId);
2941
+ if (!adapter.getUser) {
2942
+ throw new ChatError(
2943
+ `Adapter "${adapter.name}" does not support getUser`,
2944
+ "NOT_SUPPORTED"
2945
+ );
2946
+ }
2947
+ return adapter.getUser(userId);
2948
+ }
2816
2949
  /**
2817
2950
  * Get a Channel by its channel ID.
2818
2951
  *
@@ -2860,6 +2993,37 @@ var Chat = class {
2860
2993
  stateAdapter: this._stateAdapter
2861
2994
  });
2862
2995
  }
2996
+ /**
2997
+ * Get a Thread handle by its thread ID.
2998
+ *
2999
+ * The adapter is automatically inferred from the thread ID prefix.
3000
+ *
3001
+ * @param threadId - Full thread ID (e.g., "slack:C123ABC:1234567890.123456")
3002
+ * @returns A Thread that can be used to post messages, subscribe, etc.
3003
+ *
3004
+ * @example
3005
+ * ```typescript
3006
+ * const thread = chat.thread("slack:C123ABC:1234567890.123456");
3007
+ * await thread.post("Hello from outside a webhook!");
3008
+ * ```
3009
+ */
3010
+ thread(threadId) {
3011
+ const adapterName = threadId.split(":")[0];
3012
+ if (!adapterName) {
3013
+ throw new ChatError(
3014
+ `Invalid thread ID: ${threadId}`,
3015
+ "INVALID_THREAD_ID"
3016
+ );
3017
+ }
3018
+ const adapter = this.adapters.get(adapterName);
3019
+ if (!adapter) {
3020
+ throw new ChatError(
3021
+ `Adapter "${adapterName}" not found for thread ID "${threadId}"`,
3022
+ "ADAPTER_NOT_FOUND"
3023
+ );
3024
+ }
3025
+ return this.createThread(adapter, threadId, {}, false);
3026
+ }
2863
3027
  /**
2864
3028
  * Infer which adapter to use based on the userId format.
2865
3029
  */
@@ -2876,20 +3040,44 @@ var Chat = class {
2876
3040
  return adapter;
2877
3041
  }
2878
3042
  }
2879
- if (SLACK_USER_ID_REGEX.test(userId)) {
2880
- const adapter = this.adapters.get("slack");
3043
+ if (LINEAR_UUID_REGEX.test(userId)) {
3044
+ const adapter = this.adapters.get("linear");
2881
3045
  if (adapter) {
2882
3046
  return adapter;
2883
3047
  }
2884
3048
  }
2885
- if (DISCORD_SNOWFLAKE_REGEX.test(userId)) {
2886
- const adapter = this.adapters.get("discord");
3049
+ if (SLACK_USER_ID_REGEX.test(userId)) {
3050
+ const adapter = this.adapters.get("slack");
2887
3051
  if (adapter) {
2888
3052
  return adapter;
2889
3053
  }
2890
3054
  }
3055
+ if (NUMERIC_REGEX.test(userId)) {
3056
+ const candidates = [];
3057
+ if (DISCORD_SNOWFLAKE_REGEX.test(userId) && this.adapters.has("discord")) {
3058
+ candidates.push("discord");
3059
+ }
3060
+ if (this.adapters.has("telegram")) {
3061
+ candidates.push("telegram");
3062
+ }
3063
+ if (this.adapters.has("github")) {
3064
+ candidates.push("github");
3065
+ }
3066
+ if (candidates.length === 1) {
3067
+ const adapter = this.adapters.get(candidates[0]);
3068
+ if (adapter) {
3069
+ return adapter;
3070
+ }
3071
+ }
3072
+ if (candidates.length > 1) {
3073
+ throw new ChatError(
3074
+ `Numeric userId "${userId}" is ambiguous between adapters: ${candidates.join(", ")}. Call the platform's adapter directly (e.g. \`adapter.getUser(userId)\`).`,
3075
+ "AMBIGUOUS_USER_ID"
3076
+ );
3077
+ }
3078
+ }
2891
3079
  throw new ChatError(
2892
- `Cannot infer adapter from userId "${userId}". Expected format: Slack (U...), Teams (29:...), Google Chat (users/...), or Discord (numeric snowflake).`,
3080
+ `Cannot infer adapter from userId "${userId}". Expected: Slack ("U..."), Teams ("29:..."), Google Chat ("users/..."), Linear (UUID), or Discord/Telegram/GitHub (numeric).`,
2893
3081
  "UNKNOWN_USER_ID_FORMAT"
2894
3082
  );
2895
3083
  }
@@ -3111,7 +3299,7 @@ var Chat = class {
3111
3299
  if (!entry) {
3112
3300
  break;
3113
3301
  }
3114
- const msg = this.rehydrateMessage(entry.message);
3302
+ const msg = this.rehydrateMessage(entry.message, adapter);
3115
3303
  if (Date.now() > entry.expiresAt) {
3116
3304
  this.logger.info("message-expired", {
3117
3305
  threadId,
@@ -3150,7 +3338,7 @@ var Chat = class {
3150
3338
  if (!entry) {
3151
3339
  break;
3152
3340
  }
3153
- const msg = this.rehydrateMessage(entry.message);
3341
+ const msg = this.rehydrateMessage(entry.message, adapter);
3154
3342
  if (Date.now() <= entry.expiresAt) {
3155
3343
  pending.push({ message: msg, expiresAt: entry.expiresAt });
3156
3344
  } else {
@@ -3185,10 +3373,50 @@ var Chat = class {
3185
3373
  }
3186
3374
  }
3187
3375
  /**
3188
- * Concurrent strategy: no locking, process immediately.
3376
+ * Concurrent strategy: no locking, process immediately — but cap
3377
+ * simultaneous handlers per thread at `maxConcurrent` (default Infinity).
3189
3378
  */
3190
3379
  async handleConcurrent(adapter, threadId, message) {
3191
- await this.dispatchToHandlers(adapter, threadId, message);
3380
+ const { maxConcurrent } = this._concurrencyConfig;
3381
+ if (!Number.isFinite(maxConcurrent)) {
3382
+ await this.dispatchToHandlers(adapter, threadId, message);
3383
+ return;
3384
+ }
3385
+ await this.acquireConcurrentSlot(threadId, maxConcurrent);
3386
+ try {
3387
+ await this.dispatchToHandlers(adapter, threadId, message);
3388
+ } finally {
3389
+ this.releaseConcurrentSlot(threadId);
3390
+ }
3391
+ }
3392
+ acquireConcurrentSlot(threadId, maxConcurrent) {
3393
+ let slot = this._concurrentSlots.get(threadId);
3394
+ if (!slot) {
3395
+ slot = { inFlight: 0, waiters: [] };
3396
+ this._concurrentSlots.set(threadId, slot);
3397
+ }
3398
+ if (slot.inFlight < maxConcurrent) {
3399
+ slot.inFlight++;
3400
+ return Promise.resolve();
3401
+ }
3402
+ return new Promise((resolve) => {
3403
+ slot.waiters.push(resolve);
3404
+ });
3405
+ }
3406
+ releaseConcurrentSlot(threadId) {
3407
+ const slot = this._concurrentSlots.get(threadId);
3408
+ if (!slot) {
3409
+ return;
3410
+ }
3411
+ const next = slot.waiters.shift();
3412
+ if (next) {
3413
+ next();
3414
+ return;
3415
+ }
3416
+ slot.inFlight--;
3417
+ if (slot.inFlight === 0 && slot.waiters.length === 0) {
3418
+ this._concurrentSlots.delete(threadId);
3419
+ }
3192
3420
  }
3193
3421
  /**
3194
3422
  * Dispatch a message to the appropriate handler chain based on
@@ -3333,35 +3561,44 @@ var Chat = class {
3333
3561
  * object (not a Message instance). This restores class invariants like
3334
3562
  * `links` defaulting to `[]` and `metadata.dateSent` being a Date.
3335
3563
  */
3336
- rehydrateMessage(raw) {
3564
+ rehydrateMessage(raw, adapter) {
3337
3565
  if (raw instanceof Message) {
3338
3566
  return raw;
3339
3567
  }
3340
3568
  const obj = raw;
3569
+ let msg;
3341
3570
  if (obj._type === "chat:Message") {
3342
- return Message.fromJSON(obj);
3343
- }
3344
- const metadata = obj.metadata;
3345
- const dateSent = metadata.dateSent;
3346
- const editedAt = metadata.editedAt;
3347
- return new Message({
3348
- id: obj.id,
3349
- threadId: obj.threadId,
3350
- text: obj.text,
3351
- formatted: obj.formatted,
3352
- raw: obj.raw,
3353
- author: obj.author,
3354
- metadata: {
3355
- dateSent: dateSent instanceof Date ? dateSent : new Date(dateSent),
3356
- edited: metadata.edited,
3357
- editedAt: editedAt ? new Date(
3358
- editedAt instanceof Date ? editedAt.toISOString() : editedAt
3359
- ) : void 0
3360
- },
3361
- attachments: obj.attachments ?? [],
3362
- isMention: obj.isMention,
3363
- links: obj.links ?? []
3364
- });
3571
+ msg = Message.fromJSON(obj);
3572
+ } else {
3573
+ const metadata = obj.metadata;
3574
+ const dateSent = metadata.dateSent;
3575
+ const editedAt = metadata.editedAt;
3576
+ msg = new Message({
3577
+ id: obj.id,
3578
+ threadId: obj.threadId,
3579
+ text: obj.text,
3580
+ formatted: obj.formatted,
3581
+ raw: obj.raw,
3582
+ author: obj.author,
3583
+ metadata: {
3584
+ dateSent: dateSent instanceof Date ? dateSent : new Date(dateSent),
3585
+ edited: metadata.edited,
3586
+ editedAt: editedAt ? new Date(
3587
+ editedAt instanceof Date ? editedAt.toISOString() : editedAt
3588
+ ) : void 0
3589
+ },
3590
+ attachments: obj.attachments ?? [],
3591
+ isMention: obj.isMention,
3592
+ links: obj.links ?? []
3593
+ });
3594
+ }
3595
+ const rehydrate = adapter?.rehydrateAttachment?.bind(adapter);
3596
+ if (rehydrate && msg.attachments.length > 0) {
3597
+ msg.attachments = msg.attachments.map(
3598
+ (att) => att.fetchData ? att : rehydrate(att)
3599
+ );
3600
+ }
3601
+ return msg;
3365
3602
  }
3366
3603
  async runHandlers(handlers, thread, message, context) {
3367
3604
  for (const handler of handlers) {
@@ -3489,13 +3726,17 @@ var Plan = class {
3489
3726
  return null;
3490
3727
  }
3491
3728
  let current;
3492
- for (let i = this._model.tasks.length - 1; i >= 0; i--) {
3493
- if (this._model.tasks[i].status === "in_progress") {
3494
- current = this._model.tasks[i];
3495
- break;
3729
+ if (typeof update === "object" && update !== null && "id" in update && update.id) {
3730
+ current = this._model.tasks.find((t) => t.id === update.id);
3731
+ } else {
3732
+ for (let i = this._model.tasks.length - 1; i >= 0; i--) {
3733
+ if (this._model.tasks[i].status === "in_progress") {
3734
+ current = this._model.tasks[i];
3735
+ break;
3736
+ }
3496
3737
  }
3738
+ current ??= this._model.tasks.at(-1);
3497
3739
  }
3498
- current ??= this._model.tasks.at(-1);
3499
3740
  if (!current) {
3500
3741
  return null;
3501
3742
  }
@@ -3584,6 +3825,38 @@ var Plan = class {
3584
3825
  }
3585
3826
  };
3586
3827
 
3828
+ // src/streaming-plan.ts
3829
+ var StreamingPlan = class {
3830
+ $$typeof = POSTABLE_OBJECT;
3831
+ kind = "stream";
3832
+ _stream;
3833
+ _options;
3834
+ constructor(stream, options = {}) {
3835
+ this._stream = stream;
3836
+ this._options = options;
3837
+ }
3838
+ get stream() {
3839
+ return this._stream;
3840
+ }
3841
+ get options() {
3842
+ return this._options;
3843
+ }
3844
+ getFallbackText() {
3845
+ return "";
3846
+ }
3847
+ getPostData() {
3848
+ return {
3849
+ stream: this._stream,
3850
+ options: this._options
3851
+ };
3852
+ }
3853
+ isSupported(_adapter) {
3854
+ return true;
3855
+ }
3856
+ onPosted(_context) {
3857
+ }
3858
+ };
3859
+
3587
3860
  // src/emoji.ts
3588
3861
  var emojiRegistry = /* @__PURE__ */ new Map();
3589
3862
  function getEmoji(name) {
@@ -3994,6 +4267,7 @@ var toCardElement2 = toCardElement;
3994
4267
  var toModalElement2 = toModalElement;
3995
4268
  var fromReactModalElement2 = fromReactModalElement;
3996
4269
  var isModalElement2 = isModalElement;
4270
+ var ExternalSelect2 = ExternalSelect;
3997
4271
  var Modal2 = Modal;
3998
4272
  var RadioSelect2 = RadioSelect;
3999
4273
  var Select2 = Select;
@@ -4013,6 +4287,7 @@ export {
4013
4287
  DEFAULT_EMOJI_MAP,
4014
4288
  Divider2 as Divider,
4015
4289
  EmojiResolver,
4290
+ ExternalSelect2 as ExternalSelect,
4016
4291
  Field2 as Field,
4017
4292
  Fields2 as Fields,
4018
4293
  Image2 as Image,
@@ -4029,6 +4304,7 @@ export {
4029
4304
  Select2 as Select,
4030
4305
  SelectOption2 as SelectOption,
4031
4306
  StreamingMarkdownRenderer,
4307
+ StreamingPlan,
4032
4308
  THREAD_STATE_TTL_MS,
4033
4309
  Table2 as Table,
4034
4310
  TextInput2 as TextInput,
@@ -4071,6 +4347,7 @@ export {
4071
4347
  markdownToPlainText,
4072
4348
  paragraph,
4073
4349
  parseMarkdown,
4350
+ reviver,
4074
4351
  root,
4075
4352
  strikethrough,
4076
4353
  stringifyMarkdown,