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.
- package/dist/{chunk-OPV5U4WG.js → chunk-AN7MRAVW.js} +39 -0
- package/dist/index.d.ts +235 -6
- package/dist/index.js +353 -76
- package/dist/{jsx-runtime-DxATbnrP.d.ts → jsx-runtime-Co9uV6l7.d.ts} +39 -5
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/docs/adapters.mdx +30 -30
- package/docs/api/cards.mdx +5 -0
- package/docs/api/chat.mdx +95 -1
- package/docs/api/message.mdx +5 -1
- package/docs/api/modals.mdx +1 -1
- package/docs/api/thread.mdx +23 -1
- package/docs/cards.mdx +6 -0
- package/docs/contributing/publishing.mdx +33 -0
- package/docs/files.mdx +1 -0
- package/docs/getting-started.mdx +2 -12
- package/docs/meta.json +0 -2
- package/docs/modals.mdx +74 -2
- package/docs/state.mdx +1 -1
- package/docs/streaming.mdx +13 -5
- package/docs/threads-messages-channels.mdx +34 -0
- package/docs/usage.mdx +2 -2
- package/package.json +3 -2
- package/resources/guides/create-a-discord-support-bot-with-nuxt-and-redis.md +180 -0
- package/resources/guides/how-to-build-a-slack-bot-with-next-js-and-redis.md +134 -0
- package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +220 -0
- package/resources/guides/run-and-track-deploys-from-slack.md +270 -0
- package/resources/guides/ship-a-github-code-review-bot-with-hono-and-redis.md +147 -0
- package/resources/guides/triage-form-submissions-with-chat-sdk.md +178 -0
- package/resources/templates.json +19 -0
- package/docs/adapters/whatsapp.mdx +0 -222
- package/docs/guides/code-review-hono.mdx +0 -241
- package/docs/guides/discord-nuxt.mdx +0 -227
- package/docs/guides/durable-chat-sessions-nextjs.mdx +0 -331
- package/docs/guides/meta.json +0 -10
- package/docs/guides/scheduled-posts-neon.mdx +0 -447
- 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-
|
|
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
|
-
|
|
1453
|
-
|
|
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
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
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 = /^
|
|
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
|
|
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 (
|
|
2880
|
-
const adapter = this.adapters.get("
|
|
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 (
|
|
2886
|
-
const adapter = this.adapters.get("
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
3343
|
-
}
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
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
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
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,
|