chat 4.11.0 → 4.13.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/index.d.ts +447 -43
- package/dist/index.js +801 -194
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,6 +24,9 @@ import {
|
|
|
24
24
|
toModalElement
|
|
25
25
|
} from "./chunk-WKJEG4FE.js";
|
|
26
26
|
|
|
27
|
+
// src/channel.ts
|
|
28
|
+
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2 } from "@workflow/serde";
|
|
29
|
+
|
|
27
30
|
// src/chat-singleton.ts
|
|
28
31
|
var _singleton = null;
|
|
29
32
|
function setChatSingleton(chat) {
|
|
@@ -41,136 +44,6 @@ function hasChatSingleton() {
|
|
|
41
44
|
return _singleton !== null;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
// src/message.ts
|
|
45
|
-
import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE } from "@workflow/serde";
|
|
46
|
-
var Message = class _Message {
|
|
47
|
-
/** Unique message ID */
|
|
48
|
-
id;
|
|
49
|
-
/** Thread this message belongs to */
|
|
50
|
-
threadId;
|
|
51
|
-
/** Plain text content (all formatting stripped) */
|
|
52
|
-
text;
|
|
53
|
-
/**
|
|
54
|
-
* Structured formatting as an AST (mdast Root).
|
|
55
|
-
* This is the canonical representation - use this for processing.
|
|
56
|
-
* Use `stringifyMarkdown(message.formatted)` to get markdown string.
|
|
57
|
-
*/
|
|
58
|
-
formatted;
|
|
59
|
-
/** Platform-specific raw payload (escape hatch) */
|
|
60
|
-
raw;
|
|
61
|
-
/** Message author */
|
|
62
|
-
author;
|
|
63
|
-
/** Message metadata */
|
|
64
|
-
metadata;
|
|
65
|
-
/** Attachments */
|
|
66
|
-
attachments;
|
|
67
|
-
/**
|
|
68
|
-
* Whether the bot is @-mentioned in this message.
|
|
69
|
-
*
|
|
70
|
-
* This is set by the Chat SDK before passing the message to handlers.
|
|
71
|
-
* It checks for `@username` in the message text using the adapter's
|
|
72
|
-
* configured `userName` and optional `botUserId`.
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* ```typescript
|
|
76
|
-
* chat.onSubscribedMessage(async (thread, message) => {
|
|
77
|
-
* if (message.isMention) {
|
|
78
|
-
* await thread.post("You mentioned me!");
|
|
79
|
-
* }
|
|
80
|
-
* });
|
|
81
|
-
* ```
|
|
82
|
-
*/
|
|
83
|
-
isMention;
|
|
84
|
-
constructor(data) {
|
|
85
|
-
this.id = data.id;
|
|
86
|
-
this.threadId = data.threadId;
|
|
87
|
-
this.text = data.text;
|
|
88
|
-
this.formatted = data.formatted;
|
|
89
|
-
this.raw = data.raw;
|
|
90
|
-
this.author = data.author;
|
|
91
|
-
this.metadata = data.metadata;
|
|
92
|
-
this.attachments = data.attachments;
|
|
93
|
-
this.isMention = data.isMention;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Serialize the message to a plain JSON object.
|
|
97
|
-
* Use this to pass message data to external systems like workflow engines.
|
|
98
|
-
*
|
|
99
|
-
* Note: Attachment `data` (Buffer) and `fetchData` (function) are omitted
|
|
100
|
-
* as they're not serializable.
|
|
101
|
-
*/
|
|
102
|
-
toJSON() {
|
|
103
|
-
return {
|
|
104
|
-
_type: "chat:Message",
|
|
105
|
-
id: this.id,
|
|
106
|
-
threadId: this.threadId,
|
|
107
|
-
text: this.text,
|
|
108
|
-
formatted: this.formatted,
|
|
109
|
-
raw: this.raw,
|
|
110
|
-
author: {
|
|
111
|
-
userId: this.author.userId,
|
|
112
|
-
userName: this.author.userName,
|
|
113
|
-
fullName: this.author.fullName,
|
|
114
|
-
isBot: this.author.isBot,
|
|
115
|
-
isMe: this.author.isMe
|
|
116
|
-
},
|
|
117
|
-
metadata: {
|
|
118
|
-
dateSent: this.metadata.dateSent.toISOString(),
|
|
119
|
-
edited: this.metadata.edited,
|
|
120
|
-
editedAt: this.metadata.editedAt?.toISOString()
|
|
121
|
-
},
|
|
122
|
-
attachments: this.attachments.map((att) => ({
|
|
123
|
-
type: att.type,
|
|
124
|
-
url: att.url,
|
|
125
|
-
name: att.name,
|
|
126
|
-
mimeType: att.mimeType,
|
|
127
|
-
size: att.size,
|
|
128
|
-
width: att.width,
|
|
129
|
-
height: att.height
|
|
130
|
-
})),
|
|
131
|
-
isMention: this.isMention
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Reconstruct a Message from serialized JSON data.
|
|
136
|
-
* Converts ISO date strings back to Date objects.
|
|
137
|
-
*/
|
|
138
|
-
static fromJSON(json) {
|
|
139
|
-
return new _Message({
|
|
140
|
-
id: json.id,
|
|
141
|
-
threadId: json.threadId,
|
|
142
|
-
text: json.text,
|
|
143
|
-
formatted: json.formatted,
|
|
144
|
-
raw: json.raw,
|
|
145
|
-
author: json.author,
|
|
146
|
-
metadata: {
|
|
147
|
-
dateSent: new Date(json.metadata.dateSent),
|
|
148
|
-
edited: json.metadata.edited,
|
|
149
|
-
editedAt: json.metadata.editedAt ? new Date(json.metadata.editedAt) : void 0
|
|
150
|
-
},
|
|
151
|
-
attachments: json.attachments,
|
|
152
|
-
isMention: json.isMention
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Serialize a Message instance for @workflow/serde.
|
|
157
|
-
* This static method is automatically called by workflow serialization.
|
|
158
|
-
*/
|
|
159
|
-
static [WORKFLOW_SERIALIZE](instance) {
|
|
160
|
-
return instance.toJSON();
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Deserialize a Message from @workflow/serde.
|
|
164
|
-
* This static method is automatically called by workflow deserialization.
|
|
165
|
-
*/
|
|
166
|
-
static [WORKFLOW_DESERIALIZE](data) {
|
|
167
|
-
return _Message.fromJSON(data);
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
// src/thread.ts
|
|
172
|
-
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2 } from "@workflow/serde";
|
|
173
|
-
|
|
174
47
|
// src/markdown.ts
|
|
175
48
|
import { toString } from "mdast-util-to-string";
|
|
176
49
|
import remarkGfm from "remark-gfm";
|
|
@@ -380,6 +253,133 @@ var BaseFormatConverter = class {
|
|
|
380
253
|
}
|
|
381
254
|
};
|
|
382
255
|
|
|
256
|
+
// src/message.ts
|
|
257
|
+
import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE } from "@workflow/serde";
|
|
258
|
+
var Message = class _Message {
|
|
259
|
+
/** Unique message ID */
|
|
260
|
+
id;
|
|
261
|
+
/** Thread this message belongs to */
|
|
262
|
+
threadId;
|
|
263
|
+
/** Plain text content (all formatting stripped) */
|
|
264
|
+
text;
|
|
265
|
+
/**
|
|
266
|
+
* Structured formatting as an AST (mdast Root).
|
|
267
|
+
* This is the canonical representation - use this for processing.
|
|
268
|
+
* Use `stringifyMarkdown(message.formatted)` to get markdown string.
|
|
269
|
+
*/
|
|
270
|
+
formatted;
|
|
271
|
+
/** Platform-specific raw payload (escape hatch) */
|
|
272
|
+
raw;
|
|
273
|
+
/** Message author */
|
|
274
|
+
author;
|
|
275
|
+
/** Message metadata */
|
|
276
|
+
metadata;
|
|
277
|
+
/** Attachments */
|
|
278
|
+
attachments;
|
|
279
|
+
/**
|
|
280
|
+
* Whether the bot is @-mentioned in this message.
|
|
281
|
+
*
|
|
282
|
+
* This is set by the Chat SDK before passing the message to handlers.
|
|
283
|
+
* It checks for `@username` in the message text using the adapter's
|
|
284
|
+
* configured `userName` and optional `botUserId`.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```typescript
|
|
288
|
+
* chat.onSubscribedMessage(async (thread, message) => {
|
|
289
|
+
* if (message.isMention) {
|
|
290
|
+
* await thread.post("You mentioned me!");
|
|
291
|
+
* }
|
|
292
|
+
* });
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
isMention;
|
|
296
|
+
constructor(data) {
|
|
297
|
+
this.id = data.id;
|
|
298
|
+
this.threadId = data.threadId;
|
|
299
|
+
this.text = data.text;
|
|
300
|
+
this.formatted = data.formatted;
|
|
301
|
+
this.raw = data.raw;
|
|
302
|
+
this.author = data.author;
|
|
303
|
+
this.metadata = data.metadata;
|
|
304
|
+
this.attachments = data.attachments;
|
|
305
|
+
this.isMention = data.isMention;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Serialize the message to a plain JSON object.
|
|
309
|
+
* Use this to pass message data to external systems like workflow engines.
|
|
310
|
+
*
|
|
311
|
+
* Note: Attachment `data` (Buffer) and `fetchData` (function) are omitted
|
|
312
|
+
* as they're not serializable.
|
|
313
|
+
*/
|
|
314
|
+
toJSON() {
|
|
315
|
+
return {
|
|
316
|
+
_type: "chat:Message",
|
|
317
|
+
id: this.id,
|
|
318
|
+
threadId: this.threadId,
|
|
319
|
+
text: this.text,
|
|
320
|
+
formatted: this.formatted,
|
|
321
|
+
raw: this.raw,
|
|
322
|
+
author: {
|
|
323
|
+
userId: this.author.userId,
|
|
324
|
+
userName: this.author.userName,
|
|
325
|
+
fullName: this.author.fullName,
|
|
326
|
+
isBot: this.author.isBot,
|
|
327
|
+
isMe: this.author.isMe
|
|
328
|
+
},
|
|
329
|
+
metadata: {
|
|
330
|
+
dateSent: this.metadata.dateSent.toISOString(),
|
|
331
|
+
edited: this.metadata.edited,
|
|
332
|
+
editedAt: this.metadata.editedAt?.toISOString()
|
|
333
|
+
},
|
|
334
|
+
attachments: this.attachments.map((att) => ({
|
|
335
|
+
type: att.type,
|
|
336
|
+
url: att.url,
|
|
337
|
+
name: att.name,
|
|
338
|
+
mimeType: att.mimeType,
|
|
339
|
+
size: att.size,
|
|
340
|
+
width: att.width,
|
|
341
|
+
height: att.height
|
|
342
|
+
})),
|
|
343
|
+
isMention: this.isMention
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Reconstruct a Message from serialized JSON data.
|
|
348
|
+
* Converts ISO date strings back to Date objects.
|
|
349
|
+
*/
|
|
350
|
+
static fromJSON(json) {
|
|
351
|
+
return new _Message({
|
|
352
|
+
id: json.id,
|
|
353
|
+
threadId: json.threadId,
|
|
354
|
+
text: json.text,
|
|
355
|
+
formatted: json.formatted,
|
|
356
|
+
raw: json.raw,
|
|
357
|
+
author: json.author,
|
|
358
|
+
metadata: {
|
|
359
|
+
dateSent: new Date(json.metadata.dateSent),
|
|
360
|
+
edited: json.metadata.edited,
|
|
361
|
+
editedAt: json.metadata.editedAt ? new Date(json.metadata.editedAt) : void 0
|
|
362
|
+
},
|
|
363
|
+
attachments: json.attachments,
|
|
364
|
+
isMention: json.isMention
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Serialize a Message instance for @workflow/serde.
|
|
369
|
+
* This static method is automatically called by workflow serialization.
|
|
370
|
+
*/
|
|
371
|
+
static [WORKFLOW_SERIALIZE](instance) {
|
|
372
|
+
return instance.toJSON();
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Deserialize a Message from @workflow/serde.
|
|
376
|
+
* This static method is automatically called by workflow deserialization.
|
|
377
|
+
*/
|
|
378
|
+
static [WORKFLOW_DESERIALIZE](data) {
|
|
379
|
+
return _Message.fromJSON(data);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
383
|
// src/errors.ts
|
|
384
384
|
var ChatError = class extends Error {
|
|
385
385
|
constructor(message, code, cause) {
|
|
@@ -409,52 +409,377 @@ var NotImplementedError = class extends ChatError {
|
|
|
409
409
|
this.name = "NotImplementedError";
|
|
410
410
|
}
|
|
411
411
|
};
|
|
412
|
-
|
|
413
|
-
// src/logger.ts
|
|
414
|
-
var ConsoleLogger = class _ConsoleLogger {
|
|
415
|
-
constructor(level = "info", prefix = "chat-sdk") {
|
|
416
|
-
this.level = level;
|
|
417
|
-
this.prefix = prefix;
|
|
412
|
+
|
|
413
|
+
// src/logger.ts
|
|
414
|
+
var ConsoleLogger = class _ConsoleLogger {
|
|
415
|
+
constructor(level = "info", prefix = "chat-sdk") {
|
|
416
|
+
this.level = level;
|
|
417
|
+
this.prefix = prefix;
|
|
418
|
+
}
|
|
419
|
+
prefix;
|
|
420
|
+
shouldLog(level) {
|
|
421
|
+
const levels = ["debug", "info", "warn", "error", "silent"];
|
|
422
|
+
return levels.indexOf(level) >= levels.indexOf(this.level);
|
|
423
|
+
}
|
|
424
|
+
child(prefix) {
|
|
425
|
+
return new _ConsoleLogger(this.level, `${this.prefix}:${prefix}`);
|
|
426
|
+
}
|
|
427
|
+
// eslint-disable-next-line no-console
|
|
428
|
+
debug(message, ...args) {
|
|
429
|
+
if (this.shouldLog("debug"))
|
|
430
|
+
console.debug(`[${this.prefix}] ${message}`, ...args);
|
|
431
|
+
}
|
|
432
|
+
// eslint-disable-next-line no-console
|
|
433
|
+
info(message, ...args) {
|
|
434
|
+
if (this.shouldLog("info"))
|
|
435
|
+
console.info(`[${this.prefix}] ${message}`, ...args);
|
|
436
|
+
}
|
|
437
|
+
// eslint-disable-next-line no-console
|
|
438
|
+
warn(message, ...args) {
|
|
439
|
+
if (this.shouldLog("warn"))
|
|
440
|
+
console.warn(`[${this.prefix}] ${message}`, ...args);
|
|
441
|
+
}
|
|
442
|
+
// eslint-disable-next-line no-console
|
|
443
|
+
error(message, ...args) {
|
|
444
|
+
if (this.shouldLog("error"))
|
|
445
|
+
console.error(`[${this.prefix}] ${message}`, ...args);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// src/types.ts
|
|
450
|
+
var THREAD_STATE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
451
|
+
|
|
452
|
+
// src/channel.ts
|
|
453
|
+
var CHANNEL_STATE_KEY_PREFIX = "channel-state:";
|
|
454
|
+
function isLazyConfig(config) {
|
|
455
|
+
return "adapterName" in config && !("adapter" in config);
|
|
456
|
+
}
|
|
457
|
+
function isAsyncIterable(value) {
|
|
458
|
+
return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
459
|
+
}
|
|
460
|
+
var ChannelImpl = class _ChannelImpl {
|
|
461
|
+
id;
|
|
462
|
+
isDM;
|
|
463
|
+
_adapter;
|
|
464
|
+
_adapterName;
|
|
465
|
+
_stateAdapterInstance;
|
|
466
|
+
_name = null;
|
|
467
|
+
constructor(config) {
|
|
468
|
+
this.id = config.id;
|
|
469
|
+
this.isDM = config.isDM ?? false;
|
|
470
|
+
if (isLazyConfig(config)) {
|
|
471
|
+
this._adapterName = config.adapterName;
|
|
472
|
+
} else {
|
|
473
|
+
this._adapter = config.adapter;
|
|
474
|
+
this._stateAdapterInstance = config.stateAdapter;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
get adapter() {
|
|
478
|
+
if (this._adapter) {
|
|
479
|
+
return this._adapter;
|
|
480
|
+
}
|
|
481
|
+
if (!this._adapterName) {
|
|
482
|
+
throw new Error("Channel has no adapter configured");
|
|
483
|
+
}
|
|
484
|
+
const chat = getChatSingleton();
|
|
485
|
+
const adapter = chat.getAdapter(this._adapterName);
|
|
486
|
+
if (!adapter) {
|
|
487
|
+
throw new Error(
|
|
488
|
+
`Adapter "${this._adapterName}" not found in Chat singleton`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
this._adapter = adapter;
|
|
492
|
+
return adapter;
|
|
493
|
+
}
|
|
494
|
+
get _stateAdapter() {
|
|
495
|
+
if (this._stateAdapterInstance) {
|
|
496
|
+
return this._stateAdapterInstance;
|
|
497
|
+
}
|
|
498
|
+
const chat = getChatSingleton();
|
|
499
|
+
this._stateAdapterInstance = chat.getState();
|
|
500
|
+
return this._stateAdapterInstance;
|
|
501
|
+
}
|
|
502
|
+
get name() {
|
|
503
|
+
return this._name;
|
|
504
|
+
}
|
|
505
|
+
get state() {
|
|
506
|
+
return this._stateAdapter.get(
|
|
507
|
+
`${CHANNEL_STATE_KEY_PREFIX}${this.id}`
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
async setState(newState, options) {
|
|
511
|
+
const key = `${CHANNEL_STATE_KEY_PREFIX}${this.id}`;
|
|
512
|
+
if (options?.replace) {
|
|
513
|
+
await this._stateAdapter.set(key, newState, THREAD_STATE_TTL_MS);
|
|
514
|
+
} else {
|
|
515
|
+
const existing = await this._stateAdapter.get(key);
|
|
516
|
+
const merged = { ...existing, ...newState };
|
|
517
|
+
await this._stateAdapter.set(key, merged, THREAD_STATE_TTL_MS);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Iterate messages newest first (backward from most recent).
|
|
522
|
+
* Uses adapter.fetchChannelMessages if available, otherwise falls back
|
|
523
|
+
* to adapter.fetchMessages with the channel ID.
|
|
524
|
+
*/
|
|
525
|
+
get messages() {
|
|
526
|
+
const adapter = this.adapter;
|
|
527
|
+
const channelId = this.id;
|
|
528
|
+
return {
|
|
529
|
+
async *[Symbol.asyncIterator]() {
|
|
530
|
+
let cursor;
|
|
531
|
+
while (true) {
|
|
532
|
+
const fetchOptions = { cursor, direction: "backward" };
|
|
533
|
+
const result = adapter.fetchChannelMessages ? await adapter.fetchChannelMessages(channelId, fetchOptions) : await adapter.fetchMessages(channelId, fetchOptions);
|
|
534
|
+
const reversed = [...result.messages].reverse();
|
|
535
|
+
for (const message of reversed) {
|
|
536
|
+
yield message;
|
|
537
|
+
}
|
|
538
|
+
if (!result.nextCursor || result.messages.length === 0) {
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
cursor = result.nextCursor;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Iterate threads in this channel, most recently active first.
|
|
548
|
+
*/
|
|
549
|
+
threads() {
|
|
550
|
+
const adapter = this.adapter;
|
|
551
|
+
const channelId = this.id;
|
|
552
|
+
return {
|
|
553
|
+
async *[Symbol.asyncIterator]() {
|
|
554
|
+
if (!adapter.listThreads) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
let cursor;
|
|
558
|
+
while (true) {
|
|
559
|
+
const result = await adapter.listThreads(channelId, {
|
|
560
|
+
cursor
|
|
561
|
+
});
|
|
562
|
+
for (const thread of result.threads) {
|
|
563
|
+
yield thread;
|
|
564
|
+
}
|
|
565
|
+
if (!result.nextCursor || result.threads.length === 0) {
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
cursor = result.nextCursor;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
async fetchMetadata() {
|
|
574
|
+
if (this.adapter.fetchChannelInfo) {
|
|
575
|
+
const info = await this.adapter.fetchChannelInfo(this.id);
|
|
576
|
+
this._name = info.name ?? null;
|
|
577
|
+
return info;
|
|
578
|
+
}
|
|
579
|
+
return {
|
|
580
|
+
id: this.id,
|
|
581
|
+
isDM: this.isDM,
|
|
582
|
+
metadata: {}
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
async post(message) {
|
|
586
|
+
if (isAsyncIterable(message)) {
|
|
587
|
+
let accumulated = "";
|
|
588
|
+
for await (const chunk of message) {
|
|
589
|
+
accumulated += chunk;
|
|
590
|
+
}
|
|
591
|
+
return this.postSingleMessage(accumulated);
|
|
592
|
+
}
|
|
593
|
+
let postable = message;
|
|
594
|
+
if (isJSX(message)) {
|
|
595
|
+
const card = toCardElement(message);
|
|
596
|
+
if (!card) {
|
|
597
|
+
throw new Error("Invalid JSX element: must be a Card element");
|
|
598
|
+
}
|
|
599
|
+
postable = card;
|
|
600
|
+
}
|
|
601
|
+
return this.postSingleMessage(postable);
|
|
602
|
+
}
|
|
603
|
+
async postSingleMessage(postable) {
|
|
604
|
+
const rawMessage = this.adapter.postChannelMessage ? await this.adapter.postChannelMessage(this.id, postable) : await this.adapter.postMessage(this.id, postable);
|
|
605
|
+
return this.createSentMessage(rawMessage.id, postable, rawMessage.threadId);
|
|
606
|
+
}
|
|
607
|
+
async postEphemeral(user, message, options) {
|
|
608
|
+
const { fallbackToDM } = options;
|
|
609
|
+
const userId = typeof user === "string" ? user : user.userId;
|
|
610
|
+
let postable;
|
|
611
|
+
if (isJSX(message)) {
|
|
612
|
+
const card = toCardElement(message);
|
|
613
|
+
if (!card) {
|
|
614
|
+
throw new Error("Invalid JSX element: must be a Card element");
|
|
615
|
+
}
|
|
616
|
+
postable = card;
|
|
617
|
+
} else {
|
|
618
|
+
postable = message;
|
|
619
|
+
}
|
|
620
|
+
if (this.adapter.postEphemeral) {
|
|
621
|
+
return this.adapter.postEphemeral(this.id, userId, postable);
|
|
622
|
+
}
|
|
623
|
+
if (!fallbackToDM) {
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
if (this.adapter.openDM) {
|
|
627
|
+
const dmThreadId = await this.adapter.openDM(userId);
|
|
628
|
+
const result = await this.adapter.postMessage(dmThreadId, postable);
|
|
629
|
+
return {
|
|
630
|
+
id: result.id,
|
|
631
|
+
threadId: dmThreadId,
|
|
632
|
+
usedFallback: true,
|
|
633
|
+
raw: result.raw
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
async startTyping() {
|
|
639
|
+
await this.adapter.startTyping(this.id);
|
|
640
|
+
}
|
|
641
|
+
mentionUser(userId) {
|
|
642
|
+
return `<@${userId}>`;
|
|
643
|
+
}
|
|
644
|
+
toJSON() {
|
|
645
|
+
return {
|
|
646
|
+
_type: "chat:Channel",
|
|
647
|
+
id: this.id,
|
|
648
|
+
adapterName: this.adapter.name,
|
|
649
|
+
isDM: this.isDM
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
static fromJSON(json, adapter) {
|
|
653
|
+
const channel = new _ChannelImpl({
|
|
654
|
+
id: json.id,
|
|
655
|
+
adapterName: json.adapterName,
|
|
656
|
+
isDM: json.isDM
|
|
657
|
+
});
|
|
658
|
+
if (adapter) {
|
|
659
|
+
channel._adapter = adapter;
|
|
660
|
+
}
|
|
661
|
+
return channel;
|
|
662
|
+
}
|
|
663
|
+
static [WORKFLOW_SERIALIZE2](instance) {
|
|
664
|
+
return instance.toJSON();
|
|
665
|
+
}
|
|
666
|
+
static [WORKFLOW_DESERIALIZE2](data) {
|
|
667
|
+
return _ChannelImpl.fromJSON(data);
|
|
668
|
+
}
|
|
669
|
+
createSentMessage(messageId, postable, threadIdOverride) {
|
|
670
|
+
const adapter = this.adapter;
|
|
671
|
+
const threadId = threadIdOverride || this.id;
|
|
672
|
+
const self = this;
|
|
673
|
+
const { plainText, formatted, attachments } = extractMessageContent(postable);
|
|
674
|
+
const sentMessage = {
|
|
675
|
+
id: messageId,
|
|
676
|
+
threadId,
|
|
677
|
+
text: plainText,
|
|
678
|
+
formatted,
|
|
679
|
+
raw: null,
|
|
680
|
+
author: {
|
|
681
|
+
userId: "self",
|
|
682
|
+
userName: adapter.userName,
|
|
683
|
+
fullName: adapter.userName,
|
|
684
|
+
isBot: true,
|
|
685
|
+
isMe: true
|
|
686
|
+
},
|
|
687
|
+
metadata: {
|
|
688
|
+
dateSent: /* @__PURE__ */ new Date(),
|
|
689
|
+
edited: false
|
|
690
|
+
},
|
|
691
|
+
attachments,
|
|
692
|
+
toJSON() {
|
|
693
|
+
return new Message(this).toJSON();
|
|
694
|
+
},
|
|
695
|
+
async edit(newContent) {
|
|
696
|
+
let editPostable = newContent;
|
|
697
|
+
if (isJSX(newContent)) {
|
|
698
|
+
const card = toCardElement(newContent);
|
|
699
|
+
if (!card) {
|
|
700
|
+
throw new Error("Invalid JSX element: must be a Card element");
|
|
701
|
+
}
|
|
702
|
+
editPostable = card;
|
|
703
|
+
}
|
|
704
|
+
await adapter.editMessage(threadId, messageId, editPostable);
|
|
705
|
+
return self.createSentMessage(messageId, editPostable);
|
|
706
|
+
},
|
|
707
|
+
async delete() {
|
|
708
|
+
await adapter.deleteMessage(threadId, messageId);
|
|
709
|
+
},
|
|
710
|
+
async addReaction(emoji2) {
|
|
711
|
+
await adapter.addReaction(threadId, messageId, emoji2);
|
|
712
|
+
},
|
|
713
|
+
async removeReaction(emoji2) {
|
|
714
|
+
await adapter.removeReaction(threadId, messageId, emoji2);
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
return sentMessage;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
function deriveChannelId(adapter, threadId) {
|
|
721
|
+
if (adapter.channelIdFromThreadId) {
|
|
722
|
+
return adapter.channelIdFromThreadId(threadId);
|
|
418
723
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
724
|
+
const parts = threadId.split(":");
|
|
725
|
+
return parts.slice(0, 2).join(":");
|
|
726
|
+
}
|
|
727
|
+
function extractMessageContent(message) {
|
|
728
|
+
if (typeof message === "string") {
|
|
729
|
+
return {
|
|
730
|
+
plainText: message,
|
|
731
|
+
formatted: root([paragraph([text(message)])]),
|
|
732
|
+
attachments: []
|
|
733
|
+
};
|
|
423
734
|
}
|
|
424
|
-
|
|
425
|
-
return
|
|
735
|
+
if ("raw" in message) {
|
|
736
|
+
return {
|
|
737
|
+
plainText: message.raw,
|
|
738
|
+
formatted: root([paragraph([text(message.raw)])]),
|
|
739
|
+
attachments: message.attachments || []
|
|
740
|
+
};
|
|
426
741
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
742
|
+
if ("markdown" in message) {
|
|
743
|
+
const ast = parseMarkdown(message.markdown);
|
|
744
|
+
return {
|
|
745
|
+
plainText: toPlainText(ast),
|
|
746
|
+
formatted: ast,
|
|
747
|
+
attachments: message.attachments || []
|
|
748
|
+
};
|
|
431
749
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
750
|
+
if ("ast" in message) {
|
|
751
|
+
return {
|
|
752
|
+
plainText: toPlainText(message.ast),
|
|
753
|
+
formatted: message.ast,
|
|
754
|
+
attachments: message.attachments || []
|
|
755
|
+
};
|
|
436
756
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
757
|
+
if ("card" in message) {
|
|
758
|
+
const fallbackText = message.fallbackText || cardToFallbackText(message.card);
|
|
759
|
+
return {
|
|
760
|
+
plainText: fallbackText,
|
|
761
|
+
formatted: root([paragraph([text(fallbackText)])]),
|
|
762
|
+
attachments: []
|
|
763
|
+
};
|
|
441
764
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
765
|
+
if ("type" in message && message.type === "card") {
|
|
766
|
+
const fallbackText = cardToFallbackText(message);
|
|
767
|
+
return {
|
|
768
|
+
plainText: fallbackText,
|
|
769
|
+
formatted: root([paragraph([text(fallbackText)])]),
|
|
770
|
+
attachments: []
|
|
771
|
+
};
|
|
446
772
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
// src/types.ts
|
|
450
|
-
var THREAD_STATE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
773
|
+
throw new Error("Invalid PostableMessage format");
|
|
774
|
+
}
|
|
451
775
|
|
|
452
776
|
// src/thread.ts
|
|
453
|
-
|
|
777
|
+
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE3, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE3 } from "@workflow/serde";
|
|
778
|
+
function isLazyConfig2(config) {
|
|
454
779
|
return "adapterName" in config && !("adapter" in config);
|
|
455
780
|
}
|
|
456
781
|
var THREAD_STATE_KEY_PREFIX = "thread-state:";
|
|
457
|
-
function
|
|
782
|
+
function isAsyncIterable2(value) {
|
|
458
783
|
return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
459
784
|
}
|
|
460
785
|
var ThreadImpl = class _ThreadImpl {
|
|
@@ -473,6 +798,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
473
798
|
_currentMessage;
|
|
474
799
|
/** Update interval for fallback streaming */
|
|
475
800
|
_streamingUpdateIntervalMs;
|
|
801
|
+
/** Cached channel instance */
|
|
802
|
+
_channel;
|
|
476
803
|
constructor(config) {
|
|
477
804
|
this.id = config.id;
|
|
478
805
|
this.channelId = config.channelId;
|
|
@@ -480,7 +807,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
480
807
|
this._isSubscribedContext = config.isSubscribedContext ?? false;
|
|
481
808
|
this._currentMessage = config.currentMessage;
|
|
482
809
|
this._streamingUpdateIntervalMs = config.streamingUpdateIntervalMs ?? 500;
|
|
483
|
-
if (
|
|
810
|
+
if (isLazyConfig2(config)) {
|
|
484
811
|
this._adapterName = config.adapterName;
|
|
485
812
|
} else {
|
|
486
813
|
this._adapter = config.adapter;
|
|
@@ -552,6 +879,49 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
552
879
|
await this._stateAdapter.set(key, merged, THREAD_STATE_TTL_MS);
|
|
553
880
|
}
|
|
554
881
|
}
|
|
882
|
+
/**
|
|
883
|
+
* Get the Channel containing this thread.
|
|
884
|
+
* Lazy-created and cached.
|
|
885
|
+
*/
|
|
886
|
+
get channel() {
|
|
887
|
+
if (!this._channel) {
|
|
888
|
+
const channelId = deriveChannelId(this.adapter, this.id);
|
|
889
|
+
this._channel = new ChannelImpl({
|
|
890
|
+
id: channelId,
|
|
891
|
+
adapter: this.adapter,
|
|
892
|
+
stateAdapter: this._stateAdapter,
|
|
893
|
+
isDM: this.isDM
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
return this._channel;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Iterate messages newest first (backward from most recent).
|
|
900
|
+
* Auto-paginates lazily.
|
|
901
|
+
*/
|
|
902
|
+
get messages() {
|
|
903
|
+
const adapter = this.adapter;
|
|
904
|
+
const threadId = this.id;
|
|
905
|
+
return {
|
|
906
|
+
async *[Symbol.asyncIterator]() {
|
|
907
|
+
let cursor;
|
|
908
|
+
while (true) {
|
|
909
|
+
const result = await adapter.fetchMessages(threadId, {
|
|
910
|
+
cursor,
|
|
911
|
+
direction: "backward"
|
|
912
|
+
});
|
|
913
|
+
const reversed = [...result.messages].reverse();
|
|
914
|
+
for (const message of reversed) {
|
|
915
|
+
yield message;
|
|
916
|
+
}
|
|
917
|
+
if (!result.nextCursor || result.messages.length === 0) {
|
|
918
|
+
break;
|
|
919
|
+
}
|
|
920
|
+
cursor = result.nextCursor;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
}
|
|
555
925
|
get allMessages() {
|
|
556
926
|
const adapter = this.adapter;
|
|
557
927
|
const threadId = this.id;
|
|
@@ -591,7 +961,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
591
961
|
await this._stateAdapter.unsubscribe(this.id);
|
|
592
962
|
}
|
|
593
963
|
async post(message) {
|
|
594
|
-
if (
|
|
964
|
+
if (isAsyncIterable2(message)) {
|
|
595
965
|
return this.handleStream(message);
|
|
596
966
|
}
|
|
597
967
|
let postable = message;
|
|
@@ -788,7 +1158,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
788
1158
|
* Serialize a ThreadImpl instance for @workflow/serde.
|
|
789
1159
|
* This static method is automatically called by workflow serialization.
|
|
790
1160
|
*/
|
|
791
|
-
static [
|
|
1161
|
+
static [WORKFLOW_SERIALIZE3](instance) {
|
|
792
1162
|
return instance.toJSON();
|
|
793
1163
|
}
|
|
794
1164
|
/**
|
|
@@ -796,14 +1166,14 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
796
1166
|
* Uses lazy adapter resolution from Chat.getSingleton().
|
|
797
1167
|
* Requires chat.registerSingleton() to have been called.
|
|
798
1168
|
*/
|
|
799
|
-
static [
|
|
1169
|
+
static [WORKFLOW_DESERIALIZE3](data) {
|
|
800
1170
|
return _ThreadImpl.fromJSON(data);
|
|
801
1171
|
}
|
|
802
1172
|
createSentMessage(messageId, postable, threadIdOverride) {
|
|
803
1173
|
const adapter = this.adapter;
|
|
804
1174
|
const threadId = threadIdOverride || this.id;
|
|
805
1175
|
const self = this;
|
|
806
|
-
const { plainText, formatted, attachments } =
|
|
1176
|
+
const { plainText, formatted, attachments } = extractMessageContent2(postable);
|
|
807
1177
|
const sentMessage = {
|
|
808
1178
|
id: messageId,
|
|
809
1179
|
threadId,
|
|
@@ -891,7 +1261,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
891
1261
|
};
|
|
892
1262
|
}
|
|
893
1263
|
};
|
|
894
|
-
function
|
|
1264
|
+
function extractMessageContent2(message) {
|
|
895
1265
|
if (typeof message === "string") {
|
|
896
1266
|
return {
|
|
897
1267
|
plainText: message,
|
|
@@ -987,6 +1357,10 @@ var Chat = class {
|
|
|
987
1357
|
actionHandlers = [];
|
|
988
1358
|
modalSubmitHandlers = [];
|
|
989
1359
|
modalCloseHandlers = [];
|
|
1360
|
+
slashCommandHandlers = [];
|
|
1361
|
+
assistantThreadStartedHandlers = [];
|
|
1362
|
+
assistantContextChangedHandlers = [];
|
|
1363
|
+
appHomeOpenedHandlers = [];
|
|
990
1364
|
/** Initialization state */
|
|
991
1365
|
initPromise = null;
|
|
992
1366
|
initialized = false;
|
|
@@ -1201,6 +1575,36 @@ var Chat = class {
|
|
|
1201
1575
|
this.logger.debug("Registered modal close handler", { callbackIds });
|
|
1202
1576
|
}
|
|
1203
1577
|
}
|
|
1578
|
+
onSlashCommand(commandOrHandler, handler) {
|
|
1579
|
+
if (typeof commandOrHandler === "function") {
|
|
1580
|
+
this.slashCommandHandlers.push({
|
|
1581
|
+
commands: [],
|
|
1582
|
+
handler: commandOrHandler
|
|
1583
|
+
});
|
|
1584
|
+
this.logger.debug("Registered slash command handler for all commands");
|
|
1585
|
+
} else if (handler) {
|
|
1586
|
+
const commands = Array.isArray(commandOrHandler) ? commandOrHandler : [commandOrHandler];
|
|
1587
|
+
const normalizedCommands = commands.map(
|
|
1588
|
+
(cmd) => cmd.startsWith("/") ? cmd : `/${cmd}`
|
|
1589
|
+
);
|
|
1590
|
+
this.slashCommandHandlers.push({ commands: normalizedCommands, handler });
|
|
1591
|
+
this.logger.debug("Registered slash command handler", {
|
|
1592
|
+
commands: normalizedCommands
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
onAssistantThreadStarted(handler) {
|
|
1597
|
+
this.assistantThreadStartedHandlers.push(handler);
|
|
1598
|
+
this.logger.debug("Registered assistant thread started handler");
|
|
1599
|
+
}
|
|
1600
|
+
onAssistantContextChanged(handler) {
|
|
1601
|
+
this.assistantContextChangedHandlers.push(handler);
|
|
1602
|
+
this.logger.debug("Registered assistant context changed handler");
|
|
1603
|
+
}
|
|
1604
|
+
onAppHomeOpened(handler) {
|
|
1605
|
+
this.appHomeOpenedHandlers.push(handler);
|
|
1606
|
+
this.logger.debug("Registered app home opened handler");
|
|
1607
|
+
}
|
|
1204
1608
|
/**
|
|
1205
1609
|
* Get an adapter by name with type safety.
|
|
1206
1610
|
*/
|
|
@@ -1234,6 +1638,9 @@ var Chat = class {
|
|
|
1234
1638
|
if (typed._type === "chat:Thread") {
|
|
1235
1639
|
return ThreadImpl.fromJSON(value);
|
|
1236
1640
|
}
|
|
1641
|
+
if (typed._type === "chat:Channel") {
|
|
1642
|
+
return ChannelImpl.fromJSON(value);
|
|
1643
|
+
}
|
|
1237
1644
|
if (typed._type === "chat:Message") {
|
|
1238
1645
|
return Message.fromJSON(value);
|
|
1239
1646
|
}
|
|
@@ -1291,14 +1698,12 @@ var Chat = class {
|
|
|
1291
1698
|
}
|
|
1292
1699
|
}
|
|
1293
1700
|
async processModalSubmit(event, contextId, _options) {
|
|
1294
|
-
const { relatedThread, relatedMessage } = await this.retrieveModalContext(
|
|
1295
|
-
event.adapter.name,
|
|
1296
|
-
contextId
|
|
1297
|
-
);
|
|
1701
|
+
const { relatedThread, relatedMessage, relatedChannel } = await this.retrieveModalContext(event.adapter.name, contextId);
|
|
1298
1702
|
const fullEvent = {
|
|
1299
1703
|
...event,
|
|
1300
1704
|
relatedThread,
|
|
1301
|
-
relatedMessage
|
|
1705
|
+
relatedMessage,
|
|
1706
|
+
relatedChannel
|
|
1302
1707
|
};
|
|
1303
1708
|
for (const { callbackIds, handler } of this.modalSubmitHandlers) {
|
|
1304
1709
|
if (callbackIds.length === 0 || callbackIds.includes(event.callbackId)) {
|
|
@@ -1316,14 +1721,12 @@ var Chat = class {
|
|
|
1316
1721
|
}
|
|
1317
1722
|
processModalClose(event, contextId, options) {
|
|
1318
1723
|
const task = (async () => {
|
|
1319
|
-
const { relatedThread, relatedMessage } = await this.retrieveModalContext(
|
|
1320
|
-
event.adapter.name,
|
|
1321
|
-
contextId
|
|
1322
|
-
);
|
|
1724
|
+
const { relatedThread, relatedMessage, relatedChannel } = await this.retrieveModalContext(event.adapter.name, contextId);
|
|
1323
1725
|
const fullEvent = {
|
|
1324
1726
|
...event,
|
|
1325
1727
|
relatedThread,
|
|
1326
|
-
relatedMessage
|
|
1728
|
+
relatedMessage,
|
|
1729
|
+
relatedChannel
|
|
1327
1730
|
};
|
|
1328
1731
|
for (const { callbackIds, handler } of this.modalCloseHandlers) {
|
|
1329
1732
|
if (callbackIds.length === 0 || callbackIds.includes(event.callbackId)) {
|
|
@@ -1340,15 +1743,153 @@ var Chat = class {
|
|
|
1340
1743
|
options.waitUntil(task);
|
|
1341
1744
|
}
|
|
1342
1745
|
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Process an incoming slash command from an adapter.
|
|
1748
|
+
* Handles waitUntil registration and error catching internally.
|
|
1749
|
+
*/
|
|
1750
|
+
processSlashCommand(event, options) {
|
|
1751
|
+
const task = this.handleSlashCommandEvent(event).catch((err) => {
|
|
1752
|
+
this.logger.error("Slash command processing error", {
|
|
1753
|
+
error: err,
|
|
1754
|
+
command: event.command,
|
|
1755
|
+
text: event.text
|
|
1756
|
+
});
|
|
1757
|
+
});
|
|
1758
|
+
if (options?.waitUntil) {
|
|
1759
|
+
options.waitUntil(task);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
processAssistantThreadStarted(event, options) {
|
|
1763
|
+
const task = (async () => {
|
|
1764
|
+
for (const handler of this.assistantThreadStartedHandlers) {
|
|
1765
|
+
await handler(event);
|
|
1766
|
+
}
|
|
1767
|
+
})().catch((err) => {
|
|
1768
|
+
this.logger.error("Assistant thread started handler error", {
|
|
1769
|
+
error: err,
|
|
1770
|
+
threadId: event.threadId
|
|
1771
|
+
});
|
|
1772
|
+
});
|
|
1773
|
+
if (options?.waitUntil) {
|
|
1774
|
+
options.waitUntil(task);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
processAssistantContextChanged(event, options) {
|
|
1778
|
+
const task = (async () => {
|
|
1779
|
+
for (const handler of this.assistantContextChangedHandlers) {
|
|
1780
|
+
await handler(event);
|
|
1781
|
+
}
|
|
1782
|
+
})().catch((err) => {
|
|
1783
|
+
this.logger.error("Assistant context changed handler error", {
|
|
1784
|
+
error: err,
|
|
1785
|
+
threadId: event.threadId
|
|
1786
|
+
});
|
|
1787
|
+
});
|
|
1788
|
+
if (options?.waitUntil) {
|
|
1789
|
+
options.waitUntil(task);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
processAppHomeOpened(event, options) {
|
|
1793
|
+
const task = (async () => {
|
|
1794
|
+
for (const handler of this.appHomeOpenedHandlers) {
|
|
1795
|
+
await handler(event);
|
|
1796
|
+
}
|
|
1797
|
+
})().catch((err) => {
|
|
1798
|
+
this.logger.error("App home opened handler error", {
|
|
1799
|
+
error: err,
|
|
1800
|
+
userId: event.userId
|
|
1801
|
+
});
|
|
1802
|
+
});
|
|
1803
|
+
if (options?.waitUntil) {
|
|
1804
|
+
options.waitUntil(task);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Handle a slash command event internally.
|
|
1809
|
+
*/
|
|
1810
|
+
async handleSlashCommandEvent(event) {
|
|
1811
|
+
this.logger.debug("Incoming slash command", {
|
|
1812
|
+
adapter: event.adapter.name,
|
|
1813
|
+
command: event.command,
|
|
1814
|
+
text: event.text,
|
|
1815
|
+
user: event.user.userName
|
|
1816
|
+
});
|
|
1817
|
+
if (event.user.isMe) {
|
|
1818
|
+
this.logger.debug("Skipping slash command from self", {
|
|
1819
|
+
command: event.command
|
|
1820
|
+
});
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
const channel = new ChannelImpl({
|
|
1824
|
+
id: event.channelId,
|
|
1825
|
+
adapter: event.adapter,
|
|
1826
|
+
stateAdapter: this._stateAdapter
|
|
1827
|
+
});
|
|
1828
|
+
const fullEvent = {
|
|
1829
|
+
...event,
|
|
1830
|
+
channel,
|
|
1831
|
+
openModal: async (modal) => {
|
|
1832
|
+
if (!event.triggerId) {
|
|
1833
|
+
this.logger.warn("Cannot open modal: no triggerId available");
|
|
1834
|
+
return void 0;
|
|
1835
|
+
}
|
|
1836
|
+
if (!event.adapter.openModal) {
|
|
1837
|
+
this.logger.warn(
|
|
1838
|
+
`Cannot open modal: ${event.adapter.name} does not support modals`
|
|
1839
|
+
);
|
|
1840
|
+
return void 0;
|
|
1841
|
+
}
|
|
1842
|
+
let modalElement = modal;
|
|
1843
|
+
if (isJSX(modal)) {
|
|
1844
|
+
const converted = toModalElement(modal);
|
|
1845
|
+
if (!converted) {
|
|
1846
|
+
throw new Error("Invalid JSX element: must be a Modal element");
|
|
1847
|
+
}
|
|
1848
|
+
modalElement = converted;
|
|
1849
|
+
}
|
|
1850
|
+
const contextId = crypto.randomUUID();
|
|
1851
|
+
this.storeModalContext(
|
|
1852
|
+
event.adapter.name,
|
|
1853
|
+
contextId,
|
|
1854
|
+
void 0,
|
|
1855
|
+
void 0,
|
|
1856
|
+
channel
|
|
1857
|
+
);
|
|
1858
|
+
return event.adapter.openModal(
|
|
1859
|
+
event.triggerId,
|
|
1860
|
+
modalElement,
|
|
1861
|
+
contextId
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1865
|
+
this.logger.debug("Checking slash command handlers", {
|
|
1866
|
+
handlerCount: this.slashCommandHandlers.length,
|
|
1867
|
+
command: event.command
|
|
1868
|
+
});
|
|
1869
|
+
for (const { commands, handler } of this.slashCommandHandlers) {
|
|
1870
|
+
if (commands.length === 0) {
|
|
1871
|
+
this.logger.debug("Running catch-all slash command handler");
|
|
1872
|
+
await handler(fullEvent);
|
|
1873
|
+
continue;
|
|
1874
|
+
}
|
|
1875
|
+
if (commands.includes(event.command)) {
|
|
1876
|
+
this.logger.debug("Running matched slash command handler", {
|
|
1877
|
+
command: event.command
|
|
1878
|
+
});
|
|
1879
|
+
await handler(fullEvent);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1343
1883
|
/**
|
|
1344
1884
|
* Store modal context server-side with a context ID.
|
|
1345
|
-
* Called when opening a modal to preserve thread/message for the submit handler.
|
|
1885
|
+
* Called when opening a modal to preserve thread/message/channel for the submit handler.
|
|
1346
1886
|
*/
|
|
1347
|
-
storeModalContext(adapterName, contextId, thread, message) {
|
|
1887
|
+
storeModalContext(adapterName, contextId, thread, message, channel) {
|
|
1348
1888
|
const key = `modal-context:${adapterName}:${contextId}`;
|
|
1349
1889
|
const context = {
|
|
1350
|
-
thread: thread
|
|
1351
|
-
message: message?.toJSON()
|
|
1890
|
+
thread: thread?.toJSON(),
|
|
1891
|
+
message: message?.toJSON(),
|
|
1892
|
+
channel: channel?.toJSON()
|
|
1352
1893
|
};
|
|
1353
1894
|
this._stateAdapter.set(key, context, MODAL_CONTEXT_TTL_MS).catch((err) => {
|
|
1354
1895
|
this.logger.error("Failed to store modal context", {
|
|
@@ -1359,25 +1900,40 @@ var Chat = class {
|
|
|
1359
1900
|
}
|
|
1360
1901
|
/**
|
|
1361
1902
|
* Retrieve and delete modal context from server-side storage.
|
|
1362
|
-
* Called when processing modal submit/close to reconstruct thread/message.
|
|
1903
|
+
* Called when processing modal submit/close to reconstruct thread/message/channel.
|
|
1363
1904
|
*/
|
|
1364
1905
|
async retrieveModalContext(adapterName, contextId) {
|
|
1365
1906
|
if (!contextId) {
|
|
1366
|
-
return {
|
|
1907
|
+
return {
|
|
1908
|
+
relatedThread: void 0,
|
|
1909
|
+
relatedMessage: void 0,
|
|
1910
|
+
relatedChannel: void 0
|
|
1911
|
+
};
|
|
1367
1912
|
}
|
|
1368
1913
|
const key = `modal-context:${adapterName}:${contextId}`;
|
|
1369
1914
|
const stored = await this._stateAdapter.get(key);
|
|
1370
1915
|
if (!stored) {
|
|
1371
|
-
return {
|
|
1916
|
+
return {
|
|
1917
|
+
relatedThread: void 0,
|
|
1918
|
+
relatedMessage: void 0,
|
|
1919
|
+
relatedChannel: void 0
|
|
1920
|
+
};
|
|
1372
1921
|
}
|
|
1373
1922
|
const adapter = this.adapters.get(adapterName);
|
|
1374
|
-
|
|
1923
|
+
let relatedThread;
|
|
1924
|
+
if (stored.thread) {
|
|
1925
|
+
relatedThread = ThreadImpl.fromJSON(stored.thread, adapter);
|
|
1926
|
+
}
|
|
1375
1927
|
let relatedMessage;
|
|
1376
|
-
if (stored.message) {
|
|
1928
|
+
if (stored.message && relatedThread) {
|
|
1377
1929
|
const message = Message.fromJSON(stored.message);
|
|
1378
|
-
relatedMessage =
|
|
1930
|
+
relatedMessage = relatedThread.createSentMessageFromMessage(message);
|
|
1931
|
+
}
|
|
1932
|
+
let relatedChannel;
|
|
1933
|
+
if (stored.channel) {
|
|
1934
|
+
relatedChannel = ChannelImpl.fromJSON(stored.channel, adapter);
|
|
1379
1935
|
}
|
|
1380
|
-
return { relatedThread
|
|
1936
|
+
return { relatedThread, relatedMessage, relatedChannel };
|
|
1381
1937
|
}
|
|
1382
1938
|
/**
|
|
1383
1939
|
* Handle an action event internally.
|
|
@@ -1408,12 +1964,12 @@ var Chat = class {
|
|
|
1408
1964
|
metadata: { dateSent: /* @__PURE__ */ new Date(), edited: false },
|
|
1409
1965
|
attachments: []
|
|
1410
1966
|
}) : {};
|
|
1411
|
-
const thread = await this.createThread(
|
|
1967
|
+
const thread = event.threadId ? await this.createThread(
|
|
1412
1968
|
event.adapter,
|
|
1413
1969
|
event.threadId,
|
|
1414
1970
|
messageForThread,
|
|
1415
1971
|
isSubscribed
|
|
1416
|
-
);
|
|
1972
|
+
) : null;
|
|
1417
1973
|
const fullEvent = {
|
|
1418
1974
|
...event,
|
|
1419
1975
|
thread,
|
|
@@ -1455,11 +2011,13 @@ var Chat = class {
|
|
|
1455
2011
|
}
|
|
1456
2012
|
}
|
|
1457
2013
|
const contextId = crypto.randomUUID();
|
|
2014
|
+
const channel = thread.channel;
|
|
1458
2015
|
this.storeModalContext(
|
|
1459
2016
|
event.adapter.name,
|
|
1460
2017
|
contextId,
|
|
1461
2018
|
thread,
|
|
1462
|
-
message
|
|
2019
|
+
message,
|
|
2020
|
+
channel
|
|
1463
2021
|
);
|
|
1464
2022
|
return event.adapter.openModal(
|
|
1465
2023
|
event.triggerId,
|
|
@@ -1601,6 +2159,53 @@ var Chat = class {
|
|
|
1601
2159
|
const threadId = await adapter.openDM(userId);
|
|
1602
2160
|
return this.createThread(adapter, threadId, {}, false);
|
|
1603
2161
|
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Get a Channel by its channel ID.
|
|
2164
|
+
*
|
|
2165
|
+
* The adapter is automatically inferred from the channel ID prefix.
|
|
2166
|
+
*
|
|
2167
|
+
* @param channelId - Channel ID (e.g., "slack:C123ABC", "gchat:spaces/ABC123")
|
|
2168
|
+
* @returns A Channel that can be used to list threads, post messages, iterate messages, etc.
|
|
2169
|
+
*
|
|
2170
|
+
* @example
|
|
2171
|
+
* ```typescript
|
|
2172
|
+
* const channel = chat.channel("slack:C123ABC");
|
|
2173
|
+
*
|
|
2174
|
+
* // Iterate messages newest first
|
|
2175
|
+
* for await (const msg of channel.messages) {
|
|
2176
|
+
* console.log(msg.text);
|
|
2177
|
+
* }
|
|
2178
|
+
*
|
|
2179
|
+
* // List threads
|
|
2180
|
+
* for await (const t of channel.threads()) {
|
|
2181
|
+
* console.log(t.rootMessage.text, t.replyCount);
|
|
2182
|
+
* }
|
|
2183
|
+
*
|
|
2184
|
+
* // Post to channel
|
|
2185
|
+
* await channel.post("Hello channel!");
|
|
2186
|
+
* ```
|
|
2187
|
+
*/
|
|
2188
|
+
channel(channelId) {
|
|
2189
|
+
const adapterName = channelId.split(":")[0];
|
|
2190
|
+
if (!adapterName) {
|
|
2191
|
+
throw new ChatError(
|
|
2192
|
+
`Invalid channel ID: ${channelId}`,
|
|
2193
|
+
"INVALID_CHANNEL_ID"
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
const adapter = this.adapters.get(adapterName);
|
|
2197
|
+
if (!adapter) {
|
|
2198
|
+
throw new ChatError(
|
|
2199
|
+
`Adapter "${adapterName}" not found for channel ID "${channelId}"`,
|
|
2200
|
+
"ADAPTER_NOT_FOUND"
|
|
2201
|
+
);
|
|
2202
|
+
}
|
|
2203
|
+
return new ChannelImpl({
|
|
2204
|
+
id: channelId,
|
|
2205
|
+
adapter,
|
|
2206
|
+
stateAdapter: this._stateAdapter
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
1604
2209
|
/**
|
|
1605
2210
|
* Infer which adapter to use based on the userId format.
|
|
1606
2211
|
*/
|
|
@@ -2206,6 +2811,7 @@ export {
|
|
|
2206
2811
|
Button2 as Button,
|
|
2207
2812
|
Card2 as Card,
|
|
2208
2813
|
CardText2 as CardText,
|
|
2814
|
+
ChannelImpl,
|
|
2209
2815
|
Chat,
|
|
2210
2816
|
ChatError,
|
|
2211
2817
|
ConsoleLogger,
|
|
@@ -2233,6 +2839,7 @@ export {
|
|
|
2233
2839
|
convertEmojiPlaceholders,
|
|
2234
2840
|
createEmoji,
|
|
2235
2841
|
defaultEmojiResolver,
|
|
2842
|
+
deriveChannelId,
|
|
2236
2843
|
emoji,
|
|
2237
2844
|
emphasis,
|
|
2238
2845
|
fromReactElement2 as fromReactElement,
|