chat 4.26.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/{chunk-OPV5U4WG.js → chunk-V25FKIIL.js} +44 -1
- package/dist/index.d.ts +485 -33
- package/dist/index.js +862 -135
- package/dist/{jsx-runtime-DxATbnrP.d.ts → jsx-runtime-DxGwoLu2.d.ts} +49 -5
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/docs/actions.mdx +52 -1
- package/docs/adapters.mdx +43 -37
- package/docs/api/cards.mdx +4 -0
- package/docs/api/chat.mdx +172 -6
- package/docs/api/index.mdx +2 -0
- package/docs/api/markdown.mdx +28 -5
- package/docs/api/message.mdx +58 -1
- package/docs/api/meta.json +2 -0
- package/docs/api/modals.mdx +50 -0
- package/docs/api/postable-message.mdx +55 -1
- package/docs/api/thread.mdx +33 -3
- package/docs/api/transcripts.mdx +220 -0
- package/docs/cards.mdx +6 -0
- package/docs/concurrency.mdx +4 -0
- package/docs/contributing/building.mdx +73 -1
- package/docs/contributing/publishing.mdx +33 -0
- package/docs/conversation-history.mdx +137 -0
- package/docs/direct-messages.mdx +13 -4
- package/docs/ephemeral-messages.mdx +1 -1
- package/docs/error-handling.mdx +15 -3
- package/docs/files.mdx +2 -1
- package/docs/getting-started.mdx +1 -11
- package/docs/index.mdx +7 -5
- package/docs/meta.json +14 -5
- package/docs/modals.mdx +97 -1
- package/docs/posting-messages.mdx +7 -3
- package/docs/streaming.mdx +74 -18
- package/docs/subject.mdx +53 -0
- package/docs/threads-messages-channels.mdx +43 -0
- package/docs/usage.mdx +11 -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/guides/code-review-hono.mdx +0 -241
- package/docs/guides/discord-nuxt.mdx +0 -227
- package/docs/guides/durable-chat-sessions-nextjs.mdx +0 -337
- 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-V25FKIIL.js";
|
|
63
64
|
|
|
64
65
|
// src/ai.ts
|
|
65
66
|
var TEXT_MIME_PREFIXES = [
|
|
@@ -189,6 +190,127 @@ ${linkParts}`;
|
|
|
189
190
|
// src/channel.ts
|
|
190
191
|
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2 } from "@workflow/serde";
|
|
191
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
|
+
|
|
192
314
|
// src/chat-singleton.ts
|
|
193
315
|
var _singleton = null;
|
|
194
316
|
function setChatSingleton(chat) {
|
|
@@ -244,6 +366,10 @@ async function* fromFullStream(stream) {
|
|
|
244
366
|
|
|
245
367
|
// src/message.ts
|
|
246
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
|
+
}
|
|
247
373
|
var Message = class _Message {
|
|
248
374
|
/** Unique message ID */
|
|
249
375
|
id;
|
|
@@ -282,8 +408,31 @@ var Message = class _Message {
|
|
|
282
408
|
* ```
|
|
283
409
|
*/
|
|
284
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;
|
|
285
421
|
/** Links found in the message */
|
|
286
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
|
+
}
|
|
287
436
|
constructor(data) {
|
|
288
437
|
this.id = data.id;
|
|
289
438
|
this.threadId = data.threadId;
|
|
@@ -330,7 +479,8 @@ var Message = class _Message {
|
|
|
330
479
|
mimeType: att.mimeType,
|
|
331
480
|
size: att.size,
|
|
332
481
|
width: att.width,
|
|
333
|
-
height: att.height
|
|
482
|
+
height: att.height,
|
|
483
|
+
fetchMetadata: att.fetchMetadata
|
|
334
484
|
})),
|
|
335
485
|
isMention: this.isMention,
|
|
336
486
|
links: this.links.length > 0 ? this.links.map((link2) => ({
|
|
@@ -495,7 +645,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
495
645
|
_adapterName;
|
|
496
646
|
_stateAdapterInstance;
|
|
497
647
|
_name = null;
|
|
498
|
-
|
|
648
|
+
_threadHistory;
|
|
499
649
|
constructor(config) {
|
|
500
650
|
this.id = config.id;
|
|
501
651
|
this.isDM = config.isDM ?? false;
|
|
@@ -505,7 +655,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
505
655
|
} else {
|
|
506
656
|
this._adapter = config.adapter;
|
|
507
657
|
this._stateAdapterInstance = config.stateAdapter;
|
|
508
|
-
this.
|
|
658
|
+
this._threadHistory = config.threadHistory;
|
|
509
659
|
}
|
|
510
660
|
}
|
|
511
661
|
get adapter() {
|
|
@@ -559,7 +709,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
559
709
|
get messages() {
|
|
560
710
|
const adapter = this.adapter;
|
|
561
711
|
const channelId = this.id;
|
|
562
|
-
const
|
|
712
|
+
const threadHistory = this._threadHistory;
|
|
563
713
|
return {
|
|
564
714
|
async *[Symbol.asyncIterator]() {
|
|
565
715
|
let cursor;
|
|
@@ -577,8 +727,8 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
577
727
|
}
|
|
578
728
|
cursor = result.nextCursor;
|
|
579
729
|
}
|
|
580
|
-
if (!yieldedAny &&
|
|
581
|
-
const cached = await
|
|
730
|
+
if (!yieldedAny && threadHistory) {
|
|
731
|
+
const cached = await threadHistory.getMessages(channelId);
|
|
582
732
|
for (let i = cached.length - 1; i >= 0; i--) {
|
|
583
733
|
yield cached[i];
|
|
584
734
|
}
|
|
@@ -635,6 +785,8 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
635
785
|
for await (const chunk of fromFullStream(message)) {
|
|
636
786
|
if (typeof chunk === "string") {
|
|
637
787
|
accumulated += chunk;
|
|
788
|
+
} else if (chunk.type === "markdown_text") {
|
|
789
|
+
accumulated += chunk.text;
|
|
638
790
|
}
|
|
639
791
|
}
|
|
640
792
|
return this.postSingleMessage({ markdown: accumulated });
|
|
@@ -647,6 +799,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
647
799
|
}
|
|
648
800
|
postable = card;
|
|
649
801
|
}
|
|
802
|
+
postable = await this.processCallbackUrls(postable);
|
|
650
803
|
return this.postSingleMessage(postable);
|
|
651
804
|
}
|
|
652
805
|
async handlePostableObject(obj) {
|
|
@@ -664,8 +817,8 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
664
817
|
postable,
|
|
665
818
|
rawMessage.threadId
|
|
666
819
|
);
|
|
667
|
-
if (this.
|
|
668
|
-
await this.
|
|
820
|
+
if (this._threadHistory) {
|
|
821
|
+
await this._threadHistory.append(this.id, new Message(sent));
|
|
669
822
|
}
|
|
670
823
|
return sent;
|
|
671
824
|
}
|
|
@@ -682,6 +835,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
682
835
|
} else {
|
|
683
836
|
postable = message;
|
|
684
837
|
}
|
|
838
|
+
postable = await this.processCallbackUrls(postable);
|
|
685
839
|
if (this.adapter.postEphemeral) {
|
|
686
840
|
return this.adapter.postEphemeral(this.id, userId, postable);
|
|
687
841
|
}
|
|
@@ -711,6 +865,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
711
865
|
} else {
|
|
712
866
|
postable = message;
|
|
713
867
|
}
|
|
868
|
+
postable = await this.processCallbackUrls(postable);
|
|
714
869
|
if (!this.adapter.scheduleMessage) {
|
|
715
870
|
throw new NotImplementedError(
|
|
716
871
|
"Scheduled messages are not supported by this adapter",
|
|
@@ -719,6 +874,24 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
719
874
|
}
|
|
720
875
|
return this.adapter.scheduleMessage(this.id, postable, options);
|
|
721
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
|
+
}
|
|
722
895
|
async startTyping(status) {
|
|
723
896
|
await this.adapter.startTyping(this.id, status);
|
|
724
897
|
}
|
|
@@ -788,6 +961,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
788
961
|
}
|
|
789
962
|
editPostable = card;
|
|
790
963
|
}
|
|
964
|
+
editPostable = await self.processCallbackUrls(editPostable);
|
|
791
965
|
await adapter.editMessage(threadId, messageId, editPostable);
|
|
792
966
|
return self.createSentMessage(messageId, editPostable);
|
|
793
967
|
},
|
|
@@ -856,46 +1030,6 @@ function extractMessageContent(message) {
|
|
|
856
1030
|
throw new Error("Invalid PostableMessage format");
|
|
857
1031
|
}
|
|
858
1032
|
|
|
859
|
-
// src/message-history.ts
|
|
860
|
-
var DEFAULT_MAX_MESSAGES = 100;
|
|
861
|
-
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
862
|
-
var KEY_PREFIX = "msg-history:";
|
|
863
|
-
var MessageHistoryCache = class {
|
|
864
|
-
state;
|
|
865
|
-
maxMessages;
|
|
866
|
-
ttlMs;
|
|
867
|
-
constructor(state, config) {
|
|
868
|
-
this.state = state;
|
|
869
|
-
this.maxMessages = config?.maxMessages ?? DEFAULT_MAX_MESSAGES;
|
|
870
|
-
this.ttlMs = config?.ttlMs ?? DEFAULT_TTL_MS;
|
|
871
|
-
}
|
|
872
|
-
/**
|
|
873
|
-
* Atomically append a message to the history for a thread.
|
|
874
|
-
* Trims to maxMessages (keeps newest) and refreshes TTL.
|
|
875
|
-
*/
|
|
876
|
-
async append(threadId, message) {
|
|
877
|
-
const key = `${KEY_PREFIX}${threadId}`;
|
|
878
|
-
const serialized = message.toJSON();
|
|
879
|
-
serialized.raw = null;
|
|
880
|
-
await this.state.appendToList(key, serialized, {
|
|
881
|
-
maxLength: this.maxMessages,
|
|
882
|
-
ttlMs: this.ttlMs
|
|
883
|
-
});
|
|
884
|
-
}
|
|
885
|
-
/**
|
|
886
|
-
* Get messages for a thread in chronological order (oldest first).
|
|
887
|
-
*
|
|
888
|
-
* @param threadId - The thread ID
|
|
889
|
-
* @param limit - Optional limit on number of messages to return (returns newest N)
|
|
890
|
-
*/
|
|
891
|
-
async getMessages(threadId, limit) {
|
|
892
|
-
const key = `${KEY_PREFIX}${threadId}`;
|
|
893
|
-
const stored = await this.state.getList(key);
|
|
894
|
-
const sliced = limit && stored.length > limit ? stored.slice(stored.length - limit) : stored;
|
|
895
|
-
return sliced.map((s) => Message.fromJSON(s));
|
|
896
|
-
}
|
|
897
|
-
};
|
|
898
|
-
|
|
899
1033
|
// src/thread.ts
|
|
900
1034
|
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE3, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE3 } from "@workflow/serde";
|
|
901
1035
|
|
|
@@ -1166,8 +1300,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1166
1300
|
_fallbackStreamingPlaceholderText;
|
|
1167
1301
|
/** Cached channel instance */
|
|
1168
1302
|
_channel;
|
|
1169
|
-
/**
|
|
1170
|
-
|
|
1303
|
+
/** Thread history cache (set only for adapters with persistThreadHistory) */
|
|
1304
|
+
_threadHistory;
|
|
1171
1305
|
_logger;
|
|
1172
1306
|
constructor(config) {
|
|
1173
1307
|
this.id = config.id;
|
|
@@ -1184,7 +1318,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1184
1318
|
} else {
|
|
1185
1319
|
this._adapter = config.adapter;
|
|
1186
1320
|
this._stateAdapterInstance = config.stateAdapter;
|
|
1187
|
-
this.
|
|
1321
|
+
this._threadHistory = config.threadHistory;
|
|
1188
1322
|
}
|
|
1189
1323
|
if (config.initialMessage) {
|
|
1190
1324
|
this._recentMessages = [config.initialMessage];
|
|
@@ -1265,7 +1399,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1265
1399
|
stateAdapter: this._stateAdapter,
|
|
1266
1400
|
isDM: this.isDM,
|
|
1267
1401
|
channelVisibility: this.channelVisibility,
|
|
1268
|
-
|
|
1402
|
+
threadHistory: this._threadHistory
|
|
1269
1403
|
});
|
|
1270
1404
|
}
|
|
1271
1405
|
return this._channel;
|
|
@@ -1277,7 +1411,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1277
1411
|
get messages() {
|
|
1278
1412
|
const adapter = this.adapter;
|
|
1279
1413
|
const threadId = this.id;
|
|
1280
|
-
const
|
|
1414
|
+
const threadHistory = this._threadHistory;
|
|
1281
1415
|
return {
|
|
1282
1416
|
async *[Symbol.asyncIterator]() {
|
|
1283
1417
|
let cursor;
|
|
@@ -1297,8 +1431,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1297
1431
|
}
|
|
1298
1432
|
cursor = result.nextCursor;
|
|
1299
1433
|
}
|
|
1300
|
-
if (!yieldedAny &&
|
|
1301
|
-
const cached = await
|
|
1434
|
+
if (!yieldedAny && threadHistory) {
|
|
1435
|
+
const cached = await threadHistory.getMessages(threadId);
|
|
1302
1436
|
for (let i = cached.length - 1; i >= 0; i--) {
|
|
1303
1437
|
yield cached[i];
|
|
1304
1438
|
}
|
|
@@ -1309,7 +1443,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1309
1443
|
get allMessages() {
|
|
1310
1444
|
const adapter = this.adapter;
|
|
1311
1445
|
const threadId = this.id;
|
|
1312
|
-
const
|
|
1446
|
+
const threadHistory = this._threadHistory;
|
|
1313
1447
|
return {
|
|
1314
1448
|
async *[Symbol.asyncIterator]() {
|
|
1315
1449
|
let cursor;
|
|
@@ -1329,8 +1463,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1329
1463
|
}
|
|
1330
1464
|
cursor = result.nextCursor;
|
|
1331
1465
|
}
|
|
1332
|
-
if (!yieldedAny &&
|
|
1333
|
-
const cached = await
|
|
1466
|
+
if (!yieldedAny && threadHistory) {
|
|
1467
|
+
const cached = await threadHistory.getMessages(threadId);
|
|
1334
1468
|
for (const message of cached) {
|
|
1335
1469
|
yield message;
|
|
1336
1470
|
}
|
|
@@ -1338,6 +1472,19 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1338
1472
|
}
|
|
1339
1473
|
};
|
|
1340
1474
|
}
|
|
1475
|
+
async getParticipants() {
|
|
1476
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1477
|
+
if (this._currentMessage && !this._currentMessage.author.isMe && !this._currentMessage.author.isBot) {
|
|
1478
|
+
seen.set(this._currentMessage.author.userId, this._currentMessage.author);
|
|
1479
|
+
}
|
|
1480
|
+
for await (const message of this.allMessages) {
|
|
1481
|
+
if (message.author.isMe || message.author.isBot || seen.has(message.author.userId)) {
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
seen.set(message.author.userId, message.author);
|
|
1485
|
+
}
|
|
1486
|
+
return [...seen.values()];
|
|
1487
|
+
}
|
|
1341
1488
|
async isSubscribed() {
|
|
1342
1489
|
if (this._isSubscribedContext) {
|
|
1343
1490
|
return true;
|
|
@@ -1355,6 +1502,16 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1355
1502
|
}
|
|
1356
1503
|
async post(message) {
|
|
1357
1504
|
if (isPostableObject(message)) {
|
|
1505
|
+
if (message.kind === "stream") {
|
|
1506
|
+
const data = message.getPostData();
|
|
1507
|
+
const streamOptions = {
|
|
1508
|
+
...data.options.updateIntervalMs ? { updateIntervalMs: data.options.updateIntervalMs } : {},
|
|
1509
|
+
...data.options.groupTasks ? { taskDisplayMode: data.options.groupTasks } : {},
|
|
1510
|
+
...data.options.endWith ? { stopBlocks: data.options.endWith } : {}
|
|
1511
|
+
};
|
|
1512
|
+
await this.handleStream(data.stream, streamOptions);
|
|
1513
|
+
return message;
|
|
1514
|
+
}
|
|
1358
1515
|
await this.handlePostableObject(message);
|
|
1359
1516
|
return message;
|
|
1360
1517
|
}
|
|
@@ -1369,14 +1526,15 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1369
1526
|
}
|
|
1370
1527
|
postable = card;
|
|
1371
1528
|
}
|
|
1529
|
+
postable = await this.processCallbackUrls(postable);
|
|
1372
1530
|
const rawMessage = await this.adapter.postMessage(this.id, postable);
|
|
1373
1531
|
const result = this.createSentMessage(
|
|
1374
1532
|
rawMessage.id,
|
|
1375
1533
|
postable,
|
|
1376
1534
|
rawMessage.threadId
|
|
1377
1535
|
);
|
|
1378
|
-
if (this.
|
|
1379
|
-
await this.
|
|
1536
|
+
if (this._threadHistory) {
|
|
1537
|
+
await this._threadHistory.append(this.id, new Message(result));
|
|
1380
1538
|
}
|
|
1381
1539
|
return result;
|
|
1382
1540
|
}
|
|
@@ -1402,6 +1560,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1402
1560
|
} else {
|
|
1403
1561
|
postable = message;
|
|
1404
1562
|
}
|
|
1563
|
+
postable = await this.processCallbackUrls(postable);
|
|
1405
1564
|
if (this.adapter.postEphemeral) {
|
|
1406
1565
|
return this.adapter.postEphemeral(this.id, userId, postable);
|
|
1407
1566
|
}
|
|
@@ -1420,6 +1579,24 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1420
1579
|
}
|
|
1421
1580
|
return null;
|
|
1422
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
|
+
}
|
|
1423
1600
|
async schedule(message, options) {
|
|
1424
1601
|
let postable;
|
|
1425
1602
|
if (isJSX(message)) {
|
|
@@ -1431,6 +1608,9 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1431
1608
|
} else {
|
|
1432
1609
|
postable = message;
|
|
1433
1610
|
}
|
|
1611
|
+
postable = await this.processCallbackUrls(
|
|
1612
|
+
postable
|
|
1613
|
+
);
|
|
1434
1614
|
if (!this.adapter.scheduleMessage) {
|
|
1435
1615
|
throw new NotImplementedError(
|
|
1436
1616
|
"Scheduled messages are not supported by this adapter",
|
|
@@ -1442,15 +1622,16 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1442
1622
|
/**
|
|
1443
1623
|
* Handle streaming from an AsyncIterable.
|
|
1444
1624
|
* Normalizes the stream (supports both textStream and fullStream from AI SDK),
|
|
1445
|
-
* then uses adapter's
|
|
1625
|
+
* then uses the adapter's stream implementation if available, otherwise falls back to post+edit.
|
|
1446
1626
|
*/
|
|
1447
|
-
async handleStream(rawStream) {
|
|
1627
|
+
async handleStream(rawStream, callerOptions) {
|
|
1448
1628
|
const textStream = fromFullStream(rawStream);
|
|
1449
|
-
const options = {};
|
|
1629
|
+
const options = { ...callerOptions };
|
|
1450
1630
|
if (this._currentMessage) {
|
|
1451
1631
|
options.recipientUserId = this._currentMessage.author.userId;
|
|
1452
|
-
|
|
1453
|
-
|
|
1632
|
+
options.recipientTeamId = this.extractSlackRecipientTeamId(
|
|
1633
|
+
this._currentMessage.raw
|
|
1634
|
+
);
|
|
1454
1635
|
}
|
|
1455
1636
|
if (this.adapter.stream) {
|
|
1456
1637
|
let accumulated = "";
|
|
@@ -1479,8 +1660,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1479
1660
|
{ markdown: accumulated },
|
|
1480
1661
|
raw.threadId
|
|
1481
1662
|
);
|
|
1482
|
-
if (this.
|
|
1483
|
-
await this.
|
|
1663
|
+
if (this._threadHistory) {
|
|
1664
|
+
await this._threadHistory.append(this.id, new Message(sent));
|
|
1484
1665
|
}
|
|
1485
1666
|
return sent;
|
|
1486
1667
|
}
|
|
@@ -1508,6 +1689,31 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1508
1689
|
};
|
|
1509
1690
|
return this.fallbackStream(textOnlyStream, options);
|
|
1510
1691
|
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Slack payloads carry the workspace ID in a few different shapes depending on
|
|
1694
|
+
* the webhook type:
|
|
1695
|
+
* - Message events: `team_id` or `team` as a string
|
|
1696
|
+
* - `block_actions` payloads: `team.id` (object), with `user.team_id` as a fallback
|
|
1697
|
+
*/
|
|
1698
|
+
extractSlackRecipientTeamId(raw) {
|
|
1699
|
+
if (!raw || typeof raw !== "object") {
|
|
1700
|
+
return void 0;
|
|
1701
|
+
}
|
|
1702
|
+
const payload = raw;
|
|
1703
|
+
if (typeof payload.team_id === "string" && payload.team_id) {
|
|
1704
|
+
return payload.team_id;
|
|
1705
|
+
}
|
|
1706
|
+
if (typeof payload.team === "string" && payload.team) {
|
|
1707
|
+
return payload.team;
|
|
1708
|
+
}
|
|
1709
|
+
if (payload.team && typeof payload.team === "object" && typeof payload.team.id === "string" && payload.team.id) {
|
|
1710
|
+
return payload.team.id;
|
|
1711
|
+
}
|
|
1712
|
+
if (typeof payload.user?.team_id === "string" && payload.user.team_id) {
|
|
1713
|
+
return payload.user.team_id;
|
|
1714
|
+
}
|
|
1715
|
+
return void 0;
|
|
1716
|
+
}
|
|
1511
1717
|
async startTyping(status) {
|
|
1512
1718
|
await this.adapter.startTyping(this.id, status);
|
|
1513
1719
|
}
|
|
@@ -1602,8 +1808,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1602
1808
|
{ markdown: accumulated },
|
|
1603
1809
|
threadIdForEdits
|
|
1604
1810
|
);
|
|
1605
|
-
if (this.
|
|
1606
|
-
await this.
|
|
1811
|
+
if (this._threadHistory) {
|
|
1812
|
+
await this._threadHistory.append(this.id, new Message(sent));
|
|
1607
1813
|
}
|
|
1608
1814
|
return sent;
|
|
1609
1815
|
}
|
|
@@ -1611,11 +1817,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1611
1817
|
const result = await this.adapter.fetchMessages(this.id, { limit: 50 });
|
|
1612
1818
|
if (result.messages.length > 0) {
|
|
1613
1819
|
this._recentMessages = result.messages;
|
|
1614
|
-
} else if (this.
|
|
1615
|
-
this._recentMessages = await this.
|
|
1616
|
-
this.id,
|
|
1617
|
-
50
|
|
1618
|
-
);
|
|
1820
|
+
} else if (this._threadHistory) {
|
|
1821
|
+
this._recentMessages = await this._threadHistory.getMessages(this.id, 50);
|
|
1619
1822
|
} else {
|
|
1620
1823
|
this._recentMessages = [];
|
|
1621
1824
|
}
|
|
@@ -1727,6 +1930,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1727
1930
|
}
|
|
1728
1931
|
postable2 = card;
|
|
1729
1932
|
}
|
|
1933
|
+
postable2 = await self.processCallbackUrls(postable2);
|
|
1730
1934
|
await adapter.editMessage(threadId, messageId, postable2);
|
|
1731
1935
|
return self.createSentMessage(messageId, postable2);
|
|
1732
1936
|
},
|
|
@@ -1770,6 +1974,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1770
1974
|
}
|
|
1771
1975
|
postable = card;
|
|
1772
1976
|
}
|
|
1977
|
+
postable = await self.processCallbackUrls(postable);
|
|
1773
1978
|
await adapter.editMessage(threadId, messageId, postable);
|
|
1774
1979
|
return self.createSentMessage(messageId, postable, threadId);
|
|
1775
1980
|
},
|
|
@@ -1851,13 +2056,186 @@ function reviver(_key, value) {
|
|
|
1851
2056
|
return value;
|
|
1852
2057
|
}
|
|
1853
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
|
+
|
|
1854
2230
|
// src/chat.ts
|
|
1855
2231
|
var DEFAULT_LOCK_TTL_MS = 3e4;
|
|
1856
2232
|
function sleep(ms) {
|
|
1857
2233
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1858
2234
|
}
|
|
1859
|
-
var SLACK_USER_ID_REGEX = /^
|
|
2235
|
+
var SLACK_USER_ID_REGEX = /^[UW][A-Z0-9]+$/;
|
|
1860
2236
|
var DISCORD_SNOWFLAKE_REGEX = /^\d{17,19}$/;
|
|
2237
|
+
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;
|
|
2238
|
+
var NUMERIC_REGEX = /^\d+$/;
|
|
1861
2239
|
var DEDUPE_TTL_MS = 5 * 60 * 1e3;
|
|
1862
2240
|
var MODAL_CONTEXT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1863
2241
|
var Chat = class {
|
|
@@ -1878,6 +2256,21 @@ var Chat = class {
|
|
|
1878
2256
|
setChatSingleton(this);
|
|
1879
2257
|
return this;
|
|
1880
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
|
+
}
|
|
1881
2274
|
/**
|
|
1882
2275
|
* Get the registered singleton Chat instance.
|
|
1883
2276
|
* Throws if no singleton has been registered.
|
|
@@ -1899,9 +2292,12 @@ var Chat = class {
|
|
|
1899
2292
|
_fallbackStreamingPlaceholderText;
|
|
1900
2293
|
_dedupeTtlMs;
|
|
1901
2294
|
_onLockConflict;
|
|
1902
|
-
|
|
2295
|
+
_threadHistory;
|
|
2296
|
+
_identity;
|
|
2297
|
+
_transcripts;
|
|
1903
2298
|
_concurrencyStrategy;
|
|
1904
2299
|
_concurrencyConfig;
|
|
2300
|
+
_concurrentSlots = /* @__PURE__ */ new Map();
|
|
1905
2301
|
_lockScope;
|
|
1906
2302
|
mentionHandlers = [];
|
|
1907
2303
|
directMessageHandlers = [];
|
|
@@ -1909,6 +2305,7 @@ var Chat = class {
|
|
|
1909
2305
|
subscribedMessageHandlers = [];
|
|
1910
2306
|
reactionHandlers = [];
|
|
1911
2307
|
actionHandlers = [];
|
|
2308
|
+
optionsLoadHandlers = [];
|
|
1912
2309
|
modalSubmitHandlers = [];
|
|
1913
2310
|
modalCloseHandlers = [];
|
|
1914
2311
|
slashCommandHandlers = [];
|
|
@@ -1934,6 +2331,11 @@ var Chat = class {
|
|
|
1934
2331
|
this._dedupeTtlMs = config.dedupeTtlMs ?? DEDUPE_TTL_MS;
|
|
1935
2332
|
this._onLockConflict = config.onLockConflict;
|
|
1936
2333
|
this._lockScope = config.lockScope;
|
|
2334
|
+
if (typeof config.logger === "string") {
|
|
2335
|
+
this.logger = new ConsoleLogger(config.logger);
|
|
2336
|
+
} else {
|
|
2337
|
+
this.logger = config.logger || new ConsoleLogger("info");
|
|
2338
|
+
}
|
|
1937
2339
|
const concurrency = config.concurrency;
|
|
1938
2340
|
if (concurrency) {
|
|
1939
2341
|
if (typeof concurrency === "string") {
|
|
@@ -1946,6 +2348,16 @@ var Chat = class {
|
|
|
1946
2348
|
queueEntryTtlMs: 9e4
|
|
1947
2349
|
};
|
|
1948
2350
|
} else {
|
|
2351
|
+
if (concurrency.maxConcurrent !== void 0 && concurrency.maxConcurrent < 1) {
|
|
2352
|
+
throw new Error(
|
|
2353
|
+
`concurrency.maxConcurrent must be >= 1 (got ${concurrency.maxConcurrent})`
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
if (concurrency.maxConcurrent !== void 0 && concurrency.strategy !== "concurrent") {
|
|
2357
|
+
this.logger.warn(
|
|
2358
|
+
`concurrency.maxConcurrent has no effect when strategy is "${concurrency.strategy}" \u2014 it only applies to the "concurrent" strategy.`
|
|
2359
|
+
);
|
|
2360
|
+
}
|
|
1949
2361
|
this._concurrencyStrategy = concurrency.strategy;
|
|
1950
2362
|
this._concurrencyConfig = {
|
|
1951
2363
|
debounceMs: concurrency.debounceMs ?? 1500,
|
|
@@ -1965,14 +2377,23 @@ var Chat = class {
|
|
|
1965
2377
|
queueEntryTtlMs: 9e4
|
|
1966
2378
|
};
|
|
1967
2379
|
}
|
|
1968
|
-
this.
|
|
2380
|
+
this._threadHistory = new ThreadHistoryCache(
|
|
1969
2381
|
this._stateAdapter,
|
|
1970
|
-
config.messageHistory
|
|
2382
|
+
config.threadHistory ?? config.messageHistory
|
|
1971
2383
|
);
|
|
1972
|
-
if (
|
|
1973
|
-
|
|
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
|
+
);
|
|
1974
2395
|
} else {
|
|
1975
|
-
this.
|
|
2396
|
+
this._identity = config.identity;
|
|
1976
2397
|
}
|
|
1977
2398
|
const webhooks = {};
|
|
1978
2399
|
for (const [name, adapter] of Object.entries(config.adapters)) {
|
|
@@ -2180,6 +2601,19 @@ var Chat = class {
|
|
|
2180
2601
|
this.logger.debug("Registered action handler", { actionIds });
|
|
2181
2602
|
}
|
|
2182
2603
|
}
|
|
2604
|
+
onOptionsLoad(actionIdOrHandler, handler) {
|
|
2605
|
+
if (typeof actionIdOrHandler === "function") {
|
|
2606
|
+
this.optionsLoadHandlers.push({
|
|
2607
|
+
actionIds: [],
|
|
2608
|
+
handler: actionIdOrHandler
|
|
2609
|
+
});
|
|
2610
|
+
this.logger.debug("Registered options load handler for all action IDs");
|
|
2611
|
+
} else if (handler) {
|
|
2612
|
+
const actionIds = Array.isArray(actionIdOrHandler) ? actionIdOrHandler : [actionIdOrHandler];
|
|
2613
|
+
this.optionsLoadHandlers.push({ actionIds, handler });
|
|
2614
|
+
this.logger.debug("Registered options load handler", { actionIds });
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2183
2617
|
onModalSubmit(callbackIdOrHandler, handler) {
|
|
2184
2618
|
if (typeof callbackIdOrHandler === "function") {
|
|
2185
2619
|
this.modalSubmitHandlers.push({
|
|
@@ -2279,12 +2713,14 @@ var Chat = class {
|
|
|
2279
2713
|
const task = (async () => {
|
|
2280
2714
|
const message = typeof messageOrFactory === "function" ? await messageOrFactory() : messageOrFactory;
|
|
2281
2715
|
await this.handleIncomingMessage(adapter, threadId, message);
|
|
2282
|
-
})()
|
|
2716
|
+
})();
|
|
2717
|
+
const tracked = task.catch((err) => {
|
|
2283
2718
|
this.logger.error("Message processing error", { error: err, threadId });
|
|
2284
2719
|
});
|
|
2285
2720
|
if (options?.waitUntil) {
|
|
2286
|
-
options.waitUntil(
|
|
2721
|
+
options.waitUntil(tracked);
|
|
2287
2722
|
}
|
|
2723
|
+
return task;
|
|
2288
2724
|
}
|
|
2289
2725
|
/**
|
|
2290
2726
|
* Process an incoming reaction event from an adapter.
|
|
@@ -2319,20 +2755,45 @@ var Chat = class {
|
|
|
2319
2755
|
}
|
|
2320
2756
|
return task;
|
|
2321
2757
|
}
|
|
2322
|
-
async
|
|
2323
|
-
const
|
|
2758
|
+
async processOptionsLoad(event, _options) {
|
|
2759
|
+
const matchingHandlers = [
|
|
2760
|
+
...this.optionsLoadHandlers.filter(
|
|
2761
|
+
({ actionIds }) => actionIds.length > 0 && actionIds.includes(event.actionId)
|
|
2762
|
+
),
|
|
2763
|
+
...this.optionsLoadHandlers.filter(
|
|
2764
|
+
({ actionIds }) => actionIds.length === 0
|
|
2765
|
+
)
|
|
2766
|
+
];
|
|
2767
|
+
for (const { handler } of matchingHandlers) {
|
|
2768
|
+
try {
|
|
2769
|
+
const options = await handler(event);
|
|
2770
|
+
if (options) {
|
|
2771
|
+
return options;
|
|
2772
|
+
}
|
|
2773
|
+
} catch (err) {
|
|
2774
|
+
this.logger.error("Options load handler error", {
|
|
2775
|
+
error: err,
|
|
2776
|
+
actionId: event.actionId
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
async processModalSubmit(event, contextId, options) {
|
|
2782
|
+
const { callbackUrl, relatedThread, relatedMessage, relatedChannel } = await this.retrieveModalContext(event.adapter.name, contextId);
|
|
2324
2783
|
const fullEvent = {
|
|
2325
2784
|
...event,
|
|
2326
2785
|
relatedThread,
|
|
2327
2786
|
relatedMessage,
|
|
2328
2787
|
relatedChannel
|
|
2329
2788
|
};
|
|
2789
|
+
let result;
|
|
2330
2790
|
for (const { callbackIds, handler } of this.modalSubmitHandlers) {
|
|
2331
2791
|
if (callbackIds.length === 0 || callbackIds.includes(event.callbackId)) {
|
|
2332
2792
|
try {
|
|
2333
2793
|
const response = await handler(fullEvent);
|
|
2334
2794
|
if (response) {
|
|
2335
|
-
|
|
2795
|
+
result = response;
|
|
2796
|
+
break;
|
|
2336
2797
|
}
|
|
2337
2798
|
} catch (err) {
|
|
2338
2799
|
this.logger.error("Modal submit handler error", {
|
|
@@ -2342,6 +2803,30 @@ var Chat = class {
|
|
|
2342
2803
|
}
|
|
2343
2804
|
}
|
|
2344
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;
|
|
2345
2830
|
}
|
|
2346
2831
|
processModalClose(event, contextId, options) {
|
|
2347
2832
|
const task = (async () => {
|
|
@@ -2493,7 +2978,8 @@ var Chat = class {
|
|
|
2493
2978
|
contextId,
|
|
2494
2979
|
void 0,
|
|
2495
2980
|
void 0,
|
|
2496
|
-
channel
|
|
2981
|
+
channel,
|
|
2982
|
+
modalElement.callbackUrl
|
|
2497
2983
|
);
|
|
2498
2984
|
if (options?.onOpenModal) {
|
|
2499
2985
|
return options.onOpenModal(modalElement, contextId);
|
|
@@ -2530,12 +3016,13 @@ var Chat = class {
|
|
|
2530
3016
|
* Store modal context server-side with a context ID.
|
|
2531
3017
|
* Called when opening a modal to preserve thread/message/channel for the submit handler.
|
|
2532
3018
|
*/
|
|
2533
|
-
async storeModalContext(adapterName, contextId, thread, message, channel) {
|
|
3019
|
+
async storeModalContext(adapterName, contextId, thread, message, channel, callbackUrl) {
|
|
2534
3020
|
const key = `modal-context:${adapterName}:${contextId}`;
|
|
2535
3021
|
const context = {
|
|
2536
3022
|
thread: thread?.toJSON(),
|
|
2537
3023
|
message: message?.toJSON(),
|
|
2538
|
-
channel: channel?.toJSON()
|
|
3024
|
+
channel: channel?.toJSON(),
|
|
3025
|
+
callbackUrl
|
|
2539
3026
|
};
|
|
2540
3027
|
try {
|
|
2541
3028
|
await this._stateAdapter.set(key, context, MODAL_CONTEXT_TTL_MS);
|
|
@@ -2553,6 +3040,7 @@ var Chat = class {
|
|
|
2553
3040
|
async retrieveModalContext(adapterName, contextId) {
|
|
2554
3041
|
if (!contextId) {
|
|
2555
3042
|
return {
|
|
3043
|
+
callbackUrl: void 0,
|
|
2556
3044
|
relatedThread: void 0,
|
|
2557
3045
|
relatedMessage: void 0,
|
|
2558
3046
|
relatedChannel: void 0
|
|
@@ -2562,6 +3050,7 @@ var Chat = class {
|
|
|
2562
3050
|
const stored = await this._stateAdapter.get(key);
|
|
2563
3051
|
if (!stored) {
|
|
2564
3052
|
return {
|
|
3053
|
+
callbackUrl: void 0,
|
|
2565
3054
|
relatedThread: void 0,
|
|
2566
3055
|
relatedMessage: void 0,
|
|
2567
3056
|
relatedChannel: void 0
|
|
@@ -2582,7 +3071,12 @@ var Chat = class {
|
|
|
2582
3071
|
if (stored.channel) {
|
|
2583
3072
|
relatedChannel = ChannelImpl.fromJSON(stored.channel, adapter);
|
|
2584
3073
|
}
|
|
2585
|
-
return {
|
|
3074
|
+
return {
|
|
3075
|
+
callbackUrl: stored.callbackUrl,
|
|
3076
|
+
relatedThread,
|
|
3077
|
+
relatedMessage,
|
|
3078
|
+
relatedChannel
|
|
3079
|
+
};
|
|
2586
3080
|
}
|
|
2587
3081
|
/**
|
|
2588
3082
|
* Handle an action event internally.
|
|
@@ -2602,6 +3096,33 @@ var Chat = class {
|
|
|
2602
3096
|
});
|
|
2603
3097
|
return;
|
|
2604
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
|
+
}
|
|
2605
3126
|
const isSubscribed = false;
|
|
2606
3127
|
const messageForThread = event.messageId ? new Message({
|
|
2607
3128
|
id: event.messageId,
|
|
@@ -2620,7 +3141,7 @@ var Chat = class {
|
|
|
2620
3141
|
isSubscribed
|
|
2621
3142
|
) : null;
|
|
2622
3143
|
const fullEvent = {
|
|
2623
|
-
...
|
|
3144
|
+
...actionEvent,
|
|
2624
3145
|
thread,
|
|
2625
3146
|
openModal: async (modal) => {
|
|
2626
3147
|
if (!(event.triggerId || options?.onOpenModal)) {
|
|
@@ -2668,7 +3189,8 @@ var Chat = class {
|
|
|
2668
3189
|
contextId,
|
|
2669
3190
|
thread ? thread : void 0,
|
|
2670
3191
|
message,
|
|
2671
|
-
channel
|
|
3192
|
+
channel,
|
|
3193
|
+
modalElement.callbackUrl
|
|
2672
3194
|
);
|
|
2673
3195
|
if (options?.onOpenModal) {
|
|
2674
3196
|
return options.onOpenModal(modalElement, contextId);
|
|
@@ -2700,6 +3222,9 @@ var Chat = class {
|
|
|
2700
3222
|
await handler(fullEvent);
|
|
2701
3223
|
}
|
|
2702
3224
|
}
|
|
3225
|
+
if (callbackUrlPromise) {
|
|
3226
|
+
await callbackUrlPromise;
|
|
3227
|
+
}
|
|
2703
3228
|
}
|
|
2704
3229
|
/**
|
|
2705
3230
|
* Handle a reaction event internally.
|
|
@@ -2818,6 +3343,33 @@ var Chat = class {
|
|
|
2818
3343
|
const threadId = await adapter.openDM(userId);
|
|
2819
3344
|
return this.createThread(adapter, threadId, {}, false);
|
|
2820
3345
|
}
|
|
3346
|
+
/**
|
|
3347
|
+
* Look up user information by user ID.
|
|
3348
|
+
*
|
|
3349
|
+
* The adapter is automatically inferred from the user ID format.
|
|
3350
|
+
* Returns user details including email (where available — requires
|
|
3351
|
+
* appropriate scopes on some platforms, e.g. `users:read.email` on Slack).
|
|
3352
|
+
*
|
|
3353
|
+
* @param user - Platform-specific user ID string, or an Author object
|
|
3354
|
+
* @returns User info, or null if user not found
|
|
3355
|
+
*
|
|
3356
|
+
* @example
|
|
3357
|
+
* ```typescript
|
|
3358
|
+
* const user = await chat.getUser("U123456");
|
|
3359
|
+
* console.log(user?.email); // "alice@company.com"
|
|
3360
|
+
* ```
|
|
3361
|
+
*/
|
|
3362
|
+
async getUser(user) {
|
|
3363
|
+
const userId = typeof user === "string" ? user : user.userId;
|
|
3364
|
+
const adapter = this.inferAdapterFromUserId(userId);
|
|
3365
|
+
if (!adapter.getUser) {
|
|
3366
|
+
throw new ChatError(
|
|
3367
|
+
`Adapter "${adapter.name}" does not support getUser`,
|
|
3368
|
+
"NOT_SUPPORTED"
|
|
3369
|
+
);
|
|
3370
|
+
}
|
|
3371
|
+
return adapter.getUser(userId);
|
|
3372
|
+
}
|
|
2821
3373
|
/**
|
|
2822
3374
|
* Get a Channel by its channel ID.
|
|
2823
3375
|
*
|
|
@@ -2865,6 +3417,37 @@ var Chat = class {
|
|
|
2865
3417
|
stateAdapter: this._stateAdapter
|
|
2866
3418
|
});
|
|
2867
3419
|
}
|
|
3420
|
+
/**
|
|
3421
|
+
* Get a Thread handle by its thread ID.
|
|
3422
|
+
*
|
|
3423
|
+
* The adapter is automatically inferred from the thread ID prefix.
|
|
3424
|
+
*
|
|
3425
|
+
* @param threadId - Full thread ID (e.g., "slack:C123ABC:1234567890.123456")
|
|
3426
|
+
* @returns A Thread that can be used to post messages, subscribe, etc.
|
|
3427
|
+
*
|
|
3428
|
+
* @example
|
|
3429
|
+
* ```typescript
|
|
3430
|
+
* const thread = chat.thread("slack:C123ABC:1234567890.123456");
|
|
3431
|
+
* await thread.post("Hello from outside a webhook!");
|
|
3432
|
+
* ```
|
|
3433
|
+
*/
|
|
3434
|
+
thread(threadId) {
|
|
3435
|
+
const adapterName = threadId.split(":")[0];
|
|
3436
|
+
if (!adapterName) {
|
|
3437
|
+
throw new ChatError(
|
|
3438
|
+
`Invalid thread ID: ${threadId}`,
|
|
3439
|
+
"INVALID_THREAD_ID"
|
|
3440
|
+
);
|
|
3441
|
+
}
|
|
3442
|
+
const adapter = this.adapters.get(adapterName);
|
|
3443
|
+
if (!adapter) {
|
|
3444
|
+
throw new ChatError(
|
|
3445
|
+
`Adapter "${adapterName}" not found for thread ID "${threadId}"`,
|
|
3446
|
+
"ADAPTER_NOT_FOUND"
|
|
3447
|
+
);
|
|
3448
|
+
}
|
|
3449
|
+
return this.createThread(adapter, threadId, {}, false);
|
|
3450
|
+
}
|
|
2868
3451
|
/**
|
|
2869
3452
|
* Infer which adapter to use based on the userId format.
|
|
2870
3453
|
*/
|
|
@@ -2881,20 +3464,44 @@ var Chat = class {
|
|
|
2881
3464
|
return adapter;
|
|
2882
3465
|
}
|
|
2883
3466
|
}
|
|
2884
|
-
if (
|
|
2885
|
-
const adapter = this.adapters.get("
|
|
3467
|
+
if (LINEAR_UUID_REGEX.test(userId)) {
|
|
3468
|
+
const adapter = this.adapters.get("linear");
|
|
2886
3469
|
if (adapter) {
|
|
2887
3470
|
return adapter;
|
|
2888
3471
|
}
|
|
2889
3472
|
}
|
|
2890
|
-
if (
|
|
2891
|
-
const adapter = this.adapters.get("
|
|
3473
|
+
if (SLACK_USER_ID_REGEX.test(userId)) {
|
|
3474
|
+
const adapter = this.adapters.get("slack");
|
|
2892
3475
|
if (adapter) {
|
|
2893
3476
|
return adapter;
|
|
2894
3477
|
}
|
|
2895
3478
|
}
|
|
3479
|
+
if (NUMERIC_REGEX.test(userId)) {
|
|
3480
|
+
const candidates = [];
|
|
3481
|
+
if (DISCORD_SNOWFLAKE_REGEX.test(userId) && this.adapters.has("discord")) {
|
|
3482
|
+
candidates.push("discord");
|
|
3483
|
+
}
|
|
3484
|
+
if (this.adapters.has("telegram")) {
|
|
3485
|
+
candidates.push("telegram");
|
|
3486
|
+
}
|
|
3487
|
+
if (this.adapters.has("github")) {
|
|
3488
|
+
candidates.push("github");
|
|
3489
|
+
}
|
|
3490
|
+
if (candidates.length === 1) {
|
|
3491
|
+
const adapter = this.adapters.get(candidates[0]);
|
|
3492
|
+
if (adapter) {
|
|
3493
|
+
return adapter;
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
if (candidates.length > 1) {
|
|
3497
|
+
throw new ChatError(
|
|
3498
|
+
`Numeric userId "${userId}" is ambiguous between adapters: ${candidates.join(", ")}. Call the platform's adapter directly (e.g. \`adapter.getUser(userId)\`).`,
|
|
3499
|
+
"AMBIGUOUS_USER_ID"
|
|
3500
|
+
);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
2896
3503
|
throw new ChatError(
|
|
2897
|
-
`Cannot infer adapter from userId "${userId}". Expected
|
|
3504
|
+
`Cannot infer adapter from userId "${userId}". Expected: Slack ("U..."), Teams ("29:..."), Google Chat ("users/..."), Linear (UUID), or Discord/Telegram/GitHub (numeric).`,
|
|
2898
3505
|
"UNKNOWN_USER_ID_FORMAT"
|
|
2899
3506
|
);
|
|
2900
3507
|
}
|
|
@@ -2930,6 +3537,7 @@ var Chat = class {
|
|
|
2930
3537
|
* - Concurrency: Controlled by `concurrency` config (drop, queue, debounce, concurrent)
|
|
2931
3538
|
*/
|
|
2932
3539
|
async handleIncomingMessage(adapter, threadId, message) {
|
|
3540
|
+
setMessageAdapter(message, adapter);
|
|
2933
3541
|
this.logger.debug("Incoming message", {
|
|
2934
3542
|
adapter: adapter.name,
|
|
2935
3543
|
threadId,
|
|
@@ -2961,11 +3569,11 @@ var Chat = class {
|
|
|
2961
3569
|
});
|
|
2962
3570
|
return;
|
|
2963
3571
|
}
|
|
2964
|
-
if (adapter.persistMessageHistory) {
|
|
3572
|
+
if (adapter.persistThreadHistory || adapter.persistMessageHistory) {
|
|
2965
3573
|
const channelId = adapter.channelIdFromThreadId(threadId);
|
|
2966
|
-
const appends = [this.
|
|
3574
|
+
const appends = [this._threadHistory.append(threadId, message)];
|
|
2967
3575
|
if (channelId !== threadId) {
|
|
2968
|
-
appends.push(this.
|
|
3576
|
+
appends.push(this._threadHistory.append(channelId, message));
|
|
2969
3577
|
}
|
|
2970
3578
|
await Promise.all(appends);
|
|
2971
3579
|
}
|
|
@@ -3116,7 +3724,7 @@ var Chat = class {
|
|
|
3116
3724
|
if (!entry) {
|
|
3117
3725
|
break;
|
|
3118
3726
|
}
|
|
3119
|
-
const msg = this.rehydrateMessage(entry.message);
|
|
3727
|
+
const msg = this.rehydrateMessage(entry.message, adapter);
|
|
3120
3728
|
if (Date.now() > entry.expiresAt) {
|
|
3121
3729
|
this.logger.info("message-expired", {
|
|
3122
3730
|
threadId,
|
|
@@ -3155,7 +3763,7 @@ var Chat = class {
|
|
|
3155
3763
|
if (!entry) {
|
|
3156
3764
|
break;
|
|
3157
3765
|
}
|
|
3158
|
-
const msg = this.rehydrateMessage(entry.message);
|
|
3766
|
+
const msg = this.rehydrateMessage(entry.message, adapter);
|
|
3159
3767
|
if (Date.now() <= entry.expiresAt) {
|
|
3160
3768
|
pending.push({ message: msg, expiresAt: entry.expiresAt });
|
|
3161
3769
|
} else {
|
|
@@ -3190,10 +3798,50 @@ var Chat = class {
|
|
|
3190
3798
|
}
|
|
3191
3799
|
}
|
|
3192
3800
|
/**
|
|
3193
|
-
* Concurrent strategy: no locking, process immediately
|
|
3801
|
+
* Concurrent strategy: no locking, process immediately — but cap
|
|
3802
|
+
* simultaneous handlers per thread at `maxConcurrent` (default Infinity).
|
|
3194
3803
|
*/
|
|
3195
3804
|
async handleConcurrent(adapter, threadId, message) {
|
|
3196
|
-
|
|
3805
|
+
const { maxConcurrent } = this._concurrencyConfig;
|
|
3806
|
+
if (!Number.isFinite(maxConcurrent)) {
|
|
3807
|
+
await this.dispatchToHandlers(adapter, threadId, message);
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3810
|
+
await this.acquireConcurrentSlot(threadId, maxConcurrent);
|
|
3811
|
+
try {
|
|
3812
|
+
await this.dispatchToHandlers(adapter, threadId, message);
|
|
3813
|
+
} finally {
|
|
3814
|
+
this.releaseConcurrentSlot(threadId);
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
acquireConcurrentSlot(threadId, maxConcurrent) {
|
|
3818
|
+
let slot = this._concurrentSlots.get(threadId);
|
|
3819
|
+
if (!slot) {
|
|
3820
|
+
slot = { inFlight: 0, waiters: [] };
|
|
3821
|
+
this._concurrentSlots.set(threadId, slot);
|
|
3822
|
+
}
|
|
3823
|
+
if (slot.inFlight < maxConcurrent) {
|
|
3824
|
+
slot.inFlight++;
|
|
3825
|
+
return Promise.resolve();
|
|
3826
|
+
}
|
|
3827
|
+
return new Promise((resolve) => {
|
|
3828
|
+
slot.waiters.push(resolve);
|
|
3829
|
+
});
|
|
3830
|
+
}
|
|
3831
|
+
releaseConcurrentSlot(threadId) {
|
|
3832
|
+
const slot = this._concurrentSlots.get(threadId);
|
|
3833
|
+
if (!slot) {
|
|
3834
|
+
return;
|
|
3835
|
+
}
|
|
3836
|
+
const next = slot.waiters.shift();
|
|
3837
|
+
if (next) {
|
|
3838
|
+
next();
|
|
3839
|
+
return;
|
|
3840
|
+
}
|
|
3841
|
+
slot.inFlight--;
|
|
3842
|
+
if (slot.inFlight === 0 && slot.waiters.length === 0) {
|
|
3843
|
+
this._concurrentSlots.delete(threadId);
|
|
3844
|
+
}
|
|
3197
3845
|
}
|
|
3198
3846
|
/**
|
|
3199
3847
|
* Dispatch a message to the appropriate handler chain based on
|
|
@@ -3213,6 +3861,25 @@ var Chat = class {
|
|
|
3213
3861
|
message,
|
|
3214
3862
|
isSubscribed
|
|
3215
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
|
+
}
|
|
3216
3883
|
const isDM = adapter.isDM?.(threadId) ?? false;
|
|
3217
3884
|
if (isDM && this.directMessageHandlers.length > 0) {
|
|
3218
3885
|
this.logger.debug("Direct message received - calling handlers", {
|
|
@@ -3294,7 +3961,7 @@ var Chat = class {
|
|
|
3294
3961
|
logger: this.logger,
|
|
3295
3962
|
streamingUpdateIntervalMs: this._streamingUpdateIntervalMs,
|
|
3296
3963
|
fallbackStreamingPlaceholderText: this._fallbackStreamingPlaceholderText,
|
|
3297
|
-
|
|
3964
|
+
threadHistory: adapter.persistThreadHistory || adapter.persistMessageHistory ? this._threadHistory : void 0
|
|
3298
3965
|
});
|
|
3299
3966
|
}
|
|
3300
3967
|
/**
|
|
@@ -3338,35 +4005,50 @@ var Chat = class {
|
|
|
3338
4005
|
* object (not a Message instance). This restores class invariants like
|
|
3339
4006
|
* `links` defaulting to `[]` and `metadata.dateSent` being a Date.
|
|
3340
4007
|
*/
|
|
3341
|
-
rehydrateMessage(raw) {
|
|
4008
|
+
rehydrateMessage(raw, adapter) {
|
|
3342
4009
|
if (raw instanceof Message) {
|
|
4010
|
+
if (adapter) {
|
|
4011
|
+
setMessageAdapter(raw, adapter);
|
|
4012
|
+
}
|
|
3343
4013
|
return raw;
|
|
3344
4014
|
}
|
|
3345
4015
|
const obj = raw;
|
|
4016
|
+
let msg;
|
|
3346
4017
|
if (obj._type === "chat:Message") {
|
|
3347
|
-
|
|
3348
|
-
}
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
4018
|
+
msg = Message.fromJSON(obj);
|
|
4019
|
+
} else {
|
|
4020
|
+
const metadata = obj.metadata;
|
|
4021
|
+
const dateSent = metadata.dateSent;
|
|
4022
|
+
const editedAt = metadata.editedAt;
|
|
4023
|
+
msg = new Message({
|
|
4024
|
+
id: obj.id,
|
|
4025
|
+
threadId: obj.threadId,
|
|
4026
|
+
text: obj.text,
|
|
4027
|
+
formatted: obj.formatted,
|
|
4028
|
+
raw: obj.raw,
|
|
4029
|
+
author: obj.author,
|
|
4030
|
+
metadata: {
|
|
4031
|
+
dateSent: dateSent instanceof Date ? dateSent : new Date(dateSent),
|
|
4032
|
+
edited: metadata.edited,
|
|
4033
|
+
editedAt: editedAt ? new Date(
|
|
4034
|
+
editedAt instanceof Date ? editedAt.toISOString() : editedAt
|
|
4035
|
+
) : void 0
|
|
4036
|
+
},
|
|
4037
|
+
attachments: obj.attachments ?? [],
|
|
4038
|
+
isMention: obj.isMention,
|
|
4039
|
+
links: obj.links ?? []
|
|
4040
|
+
});
|
|
4041
|
+
}
|
|
4042
|
+
if (adapter) {
|
|
4043
|
+
setMessageAdapter(msg, adapter);
|
|
4044
|
+
}
|
|
4045
|
+
const rehydrate = adapter?.rehydrateAttachment?.bind(adapter);
|
|
4046
|
+
if (rehydrate && msg.attachments.length > 0) {
|
|
4047
|
+
msg.attachments = msg.attachments.map(
|
|
4048
|
+
(att) => att.fetchData ? att : rehydrate(att)
|
|
4049
|
+
);
|
|
4050
|
+
}
|
|
4051
|
+
return msg;
|
|
3370
4052
|
}
|
|
3371
4053
|
async runHandlers(handlers, thread, message, context) {
|
|
3372
4054
|
for (const handler of handlers) {
|
|
@@ -3375,6 +4057,9 @@ var Chat = class {
|
|
|
3375
4057
|
}
|
|
3376
4058
|
};
|
|
3377
4059
|
|
|
4060
|
+
// src/message-history.ts
|
|
4061
|
+
var MessageHistoryCache = ThreadHistoryCache;
|
|
4062
|
+
|
|
3378
4063
|
// src/plan.ts
|
|
3379
4064
|
function contentToPlainText(content) {
|
|
3380
4065
|
if (!content) {
|
|
@@ -3494,13 +4179,17 @@ var Plan = class {
|
|
|
3494
4179
|
return null;
|
|
3495
4180
|
}
|
|
3496
4181
|
let current;
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
4182
|
+
if (typeof update === "object" && update !== null && "id" in update && update.id) {
|
|
4183
|
+
current = this._model.tasks.find((t) => t.id === update.id);
|
|
4184
|
+
} else {
|
|
4185
|
+
for (let i = this._model.tasks.length - 1; i >= 0; i--) {
|
|
4186
|
+
if (this._model.tasks[i].status === "in_progress") {
|
|
4187
|
+
current = this._model.tasks[i];
|
|
4188
|
+
break;
|
|
4189
|
+
}
|
|
3501
4190
|
}
|
|
4191
|
+
current ??= this._model.tasks.at(-1);
|
|
3502
4192
|
}
|
|
3503
|
-
current ??= this._model.tasks.at(-1);
|
|
3504
4193
|
if (!current) {
|
|
3505
4194
|
return null;
|
|
3506
4195
|
}
|
|
@@ -3589,6 +4278,38 @@ var Plan = class {
|
|
|
3589
4278
|
}
|
|
3590
4279
|
};
|
|
3591
4280
|
|
|
4281
|
+
// src/streaming-plan.ts
|
|
4282
|
+
var StreamingPlan = class {
|
|
4283
|
+
$$typeof = POSTABLE_OBJECT;
|
|
4284
|
+
kind = "stream";
|
|
4285
|
+
_stream;
|
|
4286
|
+
_options;
|
|
4287
|
+
constructor(stream, options = {}) {
|
|
4288
|
+
this._stream = stream;
|
|
4289
|
+
this._options = options;
|
|
4290
|
+
}
|
|
4291
|
+
get stream() {
|
|
4292
|
+
return this._stream;
|
|
4293
|
+
}
|
|
4294
|
+
get options() {
|
|
4295
|
+
return this._options;
|
|
4296
|
+
}
|
|
4297
|
+
getFallbackText() {
|
|
4298
|
+
return "";
|
|
4299
|
+
}
|
|
4300
|
+
getPostData() {
|
|
4301
|
+
return {
|
|
4302
|
+
stream: this._stream,
|
|
4303
|
+
options: this._options
|
|
4304
|
+
};
|
|
4305
|
+
}
|
|
4306
|
+
isSupported(_adapter) {
|
|
4307
|
+
return true;
|
|
4308
|
+
}
|
|
4309
|
+
onPosted(_context) {
|
|
4310
|
+
}
|
|
4311
|
+
};
|
|
4312
|
+
|
|
3592
4313
|
// src/emoji.ts
|
|
3593
4314
|
var emojiRegistry = /* @__PURE__ */ new Map();
|
|
3594
4315
|
function getEmoji(name) {
|
|
@@ -3839,6 +4560,8 @@ function convertEmojiPlaceholders(text2, platform, resolver = defaultEmojiResolv
|
|
|
3839
4560
|
return resolver.toGChat(emojiName);
|
|
3840
4561
|
case "discord":
|
|
3841
4562
|
return resolver.toDiscord(emojiName);
|
|
4563
|
+
case "messenger":
|
|
4564
|
+
return resolver.toGChat(emojiName);
|
|
3842
4565
|
case "github":
|
|
3843
4566
|
return resolver.toGChat(emojiName);
|
|
3844
4567
|
case "linear":
|
|
@@ -3999,6 +4722,7 @@ var toCardElement2 = toCardElement;
|
|
|
3999
4722
|
var toModalElement2 = toModalElement;
|
|
4000
4723
|
var fromReactModalElement2 = fromReactModalElement;
|
|
4001
4724
|
var isModalElement2 = isModalElement;
|
|
4725
|
+
var ExternalSelect2 = ExternalSelect;
|
|
4002
4726
|
var Modal2 = Modal;
|
|
4003
4727
|
var RadioSelect2 = RadioSelect;
|
|
4004
4728
|
var Select2 = Select;
|
|
@@ -4018,6 +4742,7 @@ export {
|
|
|
4018
4742
|
DEFAULT_EMOJI_MAP,
|
|
4019
4743
|
Divider2 as Divider,
|
|
4020
4744
|
EmojiResolver,
|
|
4745
|
+
ExternalSelect2 as ExternalSelect,
|
|
4021
4746
|
Field2 as Field,
|
|
4022
4747
|
Fields2 as Fields,
|
|
4023
4748
|
Image2 as Image,
|
|
@@ -4034,9 +4759,11 @@ export {
|
|
|
4034
4759
|
Select2 as Select,
|
|
4035
4760
|
SelectOption2 as SelectOption,
|
|
4036
4761
|
StreamingMarkdownRenderer,
|
|
4762
|
+
StreamingPlan,
|
|
4037
4763
|
THREAD_STATE_TTL_MS,
|
|
4038
4764
|
Table2 as Table,
|
|
4039
4765
|
TextInput2 as TextInput,
|
|
4766
|
+
ThreadHistoryCache,
|
|
4040
4767
|
ThreadImpl,
|
|
4041
4768
|
blockquote,
|
|
4042
4769
|
cardChildToFallbackText2 as cardChildToFallbackText,
|