chat 4.0.1 → 4.1.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 +117 -33
- package/dist/index.js +212 -83
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -163,22 +163,97 @@ var BaseFormatConverter = class {
|
|
|
163
163
|
}
|
|
164
164
|
};
|
|
165
165
|
|
|
166
|
+
// src/types.ts
|
|
167
|
+
var ConsoleLogger = class _ConsoleLogger {
|
|
168
|
+
constructor(level = "info", prefix = "chat-sdk") {
|
|
169
|
+
this.level = level;
|
|
170
|
+
this.prefix = prefix;
|
|
171
|
+
}
|
|
172
|
+
prefix;
|
|
173
|
+
shouldLog(level) {
|
|
174
|
+
const levels = ["debug", "info", "warn", "error", "silent"];
|
|
175
|
+
return levels.indexOf(level) >= levels.indexOf(this.level);
|
|
176
|
+
}
|
|
177
|
+
child(prefix) {
|
|
178
|
+
return new _ConsoleLogger(this.level, `${this.prefix}:${prefix}`);
|
|
179
|
+
}
|
|
180
|
+
// eslint-disable-next-line no-console
|
|
181
|
+
debug(message, ...args) {
|
|
182
|
+
if (this.shouldLog("debug"))
|
|
183
|
+
console.debug(`[${this.prefix}] ${message}`, ...args);
|
|
184
|
+
}
|
|
185
|
+
// eslint-disable-next-line no-console
|
|
186
|
+
info(message, ...args) {
|
|
187
|
+
if (this.shouldLog("info"))
|
|
188
|
+
console.info(`[${this.prefix}] ${message}`, ...args);
|
|
189
|
+
}
|
|
190
|
+
// eslint-disable-next-line no-console
|
|
191
|
+
warn(message, ...args) {
|
|
192
|
+
if (this.shouldLog("warn"))
|
|
193
|
+
console.warn(`[${this.prefix}] ${message}`, ...args);
|
|
194
|
+
}
|
|
195
|
+
// eslint-disable-next-line no-console
|
|
196
|
+
error(message, ...args) {
|
|
197
|
+
if (this.shouldLog("error"))
|
|
198
|
+
console.error(`[${this.prefix}] ${message}`, ...args);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var THREAD_STATE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
202
|
+
var ChatError = class extends Error {
|
|
203
|
+
constructor(message, code, cause) {
|
|
204
|
+
super(message);
|
|
205
|
+
this.code = code;
|
|
206
|
+
this.cause = cause;
|
|
207
|
+
this.name = "ChatError";
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
var RateLimitError = class extends ChatError {
|
|
211
|
+
constructor(message, retryAfterMs, cause) {
|
|
212
|
+
super(message, "RATE_LIMITED", cause);
|
|
213
|
+
this.retryAfterMs = retryAfterMs;
|
|
214
|
+
this.name = "RateLimitError";
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
var LockError = class extends ChatError {
|
|
218
|
+
constructor(message, cause) {
|
|
219
|
+
super(message, "LOCK_FAILED", cause);
|
|
220
|
+
this.name = "LockError";
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var NotImplementedError = class extends ChatError {
|
|
224
|
+
constructor(message, feature, cause) {
|
|
225
|
+
super(message, "NOT_IMPLEMENTED", cause);
|
|
226
|
+
this.feature = feature;
|
|
227
|
+
this.name = "NotImplementedError";
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
166
231
|
// src/thread.ts
|
|
232
|
+
var THREAD_STATE_KEY_PREFIX = "thread-state:";
|
|
233
|
+
function isAsyncIterable(value) {
|
|
234
|
+
return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
235
|
+
}
|
|
167
236
|
var ThreadImpl = class {
|
|
168
237
|
id;
|
|
169
238
|
adapter;
|
|
170
239
|
channelId;
|
|
171
240
|
isDM;
|
|
172
|
-
|
|
241
|
+
_stateAdapter;
|
|
173
242
|
_recentMessages = [];
|
|
174
243
|
_isSubscribedContext;
|
|
244
|
+
/** Current message context for streaming - provides userId/teamId */
|
|
245
|
+
_currentMessage;
|
|
246
|
+
/** Update interval for fallback streaming */
|
|
247
|
+
_streamingUpdateIntervalMs;
|
|
175
248
|
constructor(config) {
|
|
176
249
|
this.id = config.id;
|
|
177
250
|
this.adapter = config.adapter;
|
|
178
251
|
this.channelId = config.channelId;
|
|
179
252
|
this.isDM = config.isDM ?? false;
|
|
180
|
-
this.
|
|
253
|
+
this._stateAdapter = config.stateAdapter;
|
|
181
254
|
this._isSubscribedContext = config.isSubscribedContext ?? false;
|
|
255
|
+
this._currentMessage = config.currentMessage;
|
|
256
|
+
this._streamingUpdateIntervalMs = config.streamingUpdateIntervalMs ?? 500;
|
|
182
257
|
if (config.initialMessage) {
|
|
183
258
|
this._recentMessages = [config.initialMessage];
|
|
184
259
|
}
|
|
@@ -189,6 +264,29 @@ var ThreadImpl = class {
|
|
|
189
264
|
set recentMessages(messages) {
|
|
190
265
|
this._recentMessages = messages;
|
|
191
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Get the current thread state.
|
|
269
|
+
* Returns null if no state has been set.
|
|
270
|
+
*/
|
|
271
|
+
get state() {
|
|
272
|
+
return this._stateAdapter.get(
|
|
273
|
+
`${THREAD_STATE_KEY_PREFIX}${this.id}`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Set the thread state. Merges with existing state by default.
|
|
278
|
+
* State is persisted for 30 days.
|
|
279
|
+
*/
|
|
280
|
+
async setState(newState, options) {
|
|
281
|
+
const key = `${THREAD_STATE_KEY_PREFIX}${this.id}`;
|
|
282
|
+
if (options?.replace) {
|
|
283
|
+
await this._stateAdapter.set(key, newState, THREAD_STATE_TTL_MS);
|
|
284
|
+
} else {
|
|
285
|
+
const existing = await this._stateAdapter.get(key);
|
|
286
|
+
const merged = { ...existing, ...newState };
|
|
287
|
+
await this._stateAdapter.set(key, merged, THREAD_STATE_TTL_MS);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
192
290
|
get allMessages() {
|
|
193
291
|
const adapter = this.adapter;
|
|
194
292
|
const threadId = this.id;
|
|
@@ -220,18 +318,21 @@ var ThreadImpl = class {
|
|
|
220
318
|
if (this._isSubscribedContext) {
|
|
221
319
|
return true;
|
|
222
320
|
}
|
|
223
|
-
return this.
|
|
321
|
+
return this._stateAdapter.isSubscribed(this.id);
|
|
224
322
|
}
|
|
225
323
|
async subscribe() {
|
|
226
|
-
await this.
|
|
324
|
+
await this._stateAdapter.subscribe(this.id);
|
|
227
325
|
if (this.adapter.onThreadSubscribe) {
|
|
228
326
|
await this.adapter.onThreadSubscribe(this.id);
|
|
229
327
|
}
|
|
230
328
|
}
|
|
231
329
|
async unsubscribe() {
|
|
232
|
-
await this.
|
|
330
|
+
await this._stateAdapter.unsubscribe(this.id);
|
|
233
331
|
}
|
|
234
332
|
async post(message) {
|
|
333
|
+
if (isAsyncIterable(message)) {
|
|
334
|
+
return this.handleStream(message);
|
|
335
|
+
}
|
|
235
336
|
let postable = message;
|
|
236
337
|
if (isJSX(message)) {
|
|
237
338
|
const card = toCardElement(message);
|
|
@@ -243,9 +344,93 @@ var ThreadImpl = class {
|
|
|
243
344
|
const rawMessage = await this.adapter.postMessage(this.id, postable);
|
|
244
345
|
return this.createSentMessage(rawMessage.id, postable);
|
|
245
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Handle streaming from an AsyncIterable.
|
|
349
|
+
* Uses adapter's native streaming if available, otherwise falls back to post+edit.
|
|
350
|
+
*/
|
|
351
|
+
async handleStream(textStream) {
|
|
352
|
+
const options = {};
|
|
353
|
+
if (this._currentMessage) {
|
|
354
|
+
options.recipientUserId = this._currentMessage.author.userId;
|
|
355
|
+
const raw = this._currentMessage.raw;
|
|
356
|
+
options.recipientTeamId = raw?.team_id ?? raw?.team;
|
|
357
|
+
}
|
|
358
|
+
if (this.adapter.stream) {
|
|
359
|
+
let accumulated = "";
|
|
360
|
+
const wrappedStream = {
|
|
361
|
+
[Symbol.asyncIterator]: () => {
|
|
362
|
+
const iterator = textStream[Symbol.asyncIterator]();
|
|
363
|
+
return {
|
|
364
|
+
async next() {
|
|
365
|
+
const result = await iterator.next();
|
|
366
|
+
if (!result.done) {
|
|
367
|
+
accumulated += result.value;
|
|
368
|
+
}
|
|
369
|
+
return result;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
const raw = await this.adapter.stream(this.id, wrappedStream, options);
|
|
375
|
+
return this.createSentMessage(raw.id, accumulated);
|
|
376
|
+
}
|
|
377
|
+
return this.fallbackStream(textStream, options);
|
|
378
|
+
}
|
|
246
379
|
async startTyping() {
|
|
247
380
|
await this.adapter.startTyping(this.id);
|
|
248
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* Fallback streaming implementation using post + edit.
|
|
384
|
+
* Used when adapter doesn't support native streaming.
|
|
385
|
+
* Uses recursive setTimeout to send updates every intervalMs (default 500ms).
|
|
386
|
+
* Schedules next update only after current edit completes to avoid overwhelming slow services.
|
|
387
|
+
*/
|
|
388
|
+
async fallbackStream(textStream, options) {
|
|
389
|
+
const intervalMs = options?.updateIntervalMs ?? this._streamingUpdateIntervalMs;
|
|
390
|
+
const msg = await this.adapter.postMessage(this.id, "...");
|
|
391
|
+
let accumulated = "";
|
|
392
|
+
let lastEditContent = "...";
|
|
393
|
+
let stopped = false;
|
|
394
|
+
let pendingEdit = null;
|
|
395
|
+
let timerId = null;
|
|
396
|
+
const doEditAndReschedule = async () => {
|
|
397
|
+
if (stopped) return;
|
|
398
|
+
if (accumulated !== lastEditContent) {
|
|
399
|
+
const content = accumulated;
|
|
400
|
+
try {
|
|
401
|
+
await this.adapter.editMessage(this.id, msg.id, content);
|
|
402
|
+
lastEditContent = content;
|
|
403
|
+
} catch {
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (!stopped) {
|
|
407
|
+
timerId = setTimeout(() => {
|
|
408
|
+
pendingEdit = doEditAndReschedule();
|
|
409
|
+
}, intervalMs);
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
timerId = setTimeout(() => {
|
|
413
|
+
pendingEdit = doEditAndReschedule();
|
|
414
|
+
}, intervalMs);
|
|
415
|
+
try {
|
|
416
|
+
for await (const chunk of textStream) {
|
|
417
|
+
accumulated += chunk;
|
|
418
|
+
}
|
|
419
|
+
} finally {
|
|
420
|
+
stopped = true;
|
|
421
|
+
if (timerId) {
|
|
422
|
+
clearTimeout(timerId);
|
|
423
|
+
timerId = null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (pendingEdit) {
|
|
427
|
+
await pendingEdit;
|
|
428
|
+
}
|
|
429
|
+
if (accumulated !== lastEditContent) {
|
|
430
|
+
await this.adapter.editMessage(this.id, msg.id, accumulated);
|
|
431
|
+
}
|
|
432
|
+
return this.createSentMessage(msg.id, accumulated);
|
|
433
|
+
}
|
|
249
434
|
async refresh() {
|
|
250
435
|
const messages = await this.adapter.fetchMessages(this.id, { limit: 50 });
|
|
251
436
|
this._recentMessages = messages;
|
|
@@ -351,78 +536,15 @@ function extractMessageContent(message) {
|
|
|
351
536
|
throw new Error("Invalid PostableMessage format");
|
|
352
537
|
}
|
|
353
538
|
|
|
354
|
-
// src/types.ts
|
|
355
|
-
var ConsoleLogger = class _ConsoleLogger {
|
|
356
|
-
constructor(level = "info", prefix = "chat-sdk") {
|
|
357
|
-
this.level = level;
|
|
358
|
-
this.prefix = prefix;
|
|
359
|
-
}
|
|
360
|
-
prefix;
|
|
361
|
-
shouldLog(level) {
|
|
362
|
-
const levels = ["debug", "info", "warn", "error", "silent"];
|
|
363
|
-
return levels.indexOf(level) >= levels.indexOf(this.level);
|
|
364
|
-
}
|
|
365
|
-
child(prefix) {
|
|
366
|
-
return new _ConsoleLogger(this.level, `${this.prefix}:${prefix}`);
|
|
367
|
-
}
|
|
368
|
-
// eslint-disable-next-line no-console
|
|
369
|
-
debug(message, ...args) {
|
|
370
|
-
if (this.shouldLog("debug"))
|
|
371
|
-
console.debug(`[${this.prefix}] ${message}`, ...args);
|
|
372
|
-
}
|
|
373
|
-
// eslint-disable-next-line no-console
|
|
374
|
-
info(message, ...args) {
|
|
375
|
-
if (this.shouldLog("info"))
|
|
376
|
-
console.info(`[${this.prefix}] ${message}`, ...args);
|
|
377
|
-
}
|
|
378
|
-
// eslint-disable-next-line no-console
|
|
379
|
-
warn(message, ...args) {
|
|
380
|
-
if (this.shouldLog("warn"))
|
|
381
|
-
console.warn(`[${this.prefix}] ${message}`, ...args);
|
|
382
|
-
}
|
|
383
|
-
// eslint-disable-next-line no-console
|
|
384
|
-
error(message, ...args) {
|
|
385
|
-
if (this.shouldLog("error"))
|
|
386
|
-
console.error(`[${this.prefix}] ${message}`, ...args);
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
var ChatError = class extends Error {
|
|
390
|
-
constructor(message, code, cause) {
|
|
391
|
-
super(message);
|
|
392
|
-
this.code = code;
|
|
393
|
-
this.cause = cause;
|
|
394
|
-
this.name = "ChatError";
|
|
395
|
-
}
|
|
396
|
-
};
|
|
397
|
-
var RateLimitError = class extends ChatError {
|
|
398
|
-
constructor(message, retryAfterMs, cause) {
|
|
399
|
-
super(message, "RATE_LIMITED", cause);
|
|
400
|
-
this.retryAfterMs = retryAfterMs;
|
|
401
|
-
this.name = "RateLimitError";
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
var LockError = class extends ChatError {
|
|
405
|
-
constructor(message, cause) {
|
|
406
|
-
super(message, "LOCK_FAILED", cause);
|
|
407
|
-
this.name = "LockError";
|
|
408
|
-
}
|
|
409
|
-
};
|
|
410
|
-
var NotImplementedError = class extends ChatError {
|
|
411
|
-
constructor(message, feature, cause) {
|
|
412
|
-
super(message, "NOT_IMPLEMENTED", cause);
|
|
413
|
-
this.feature = feature;
|
|
414
|
-
this.name = "NotImplementedError";
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
|
|
418
539
|
// src/chat.ts
|
|
419
540
|
var DEFAULT_LOCK_TTL_MS = 3e4;
|
|
420
541
|
var DEDUPE_TTL_MS = 6e4;
|
|
421
542
|
var Chat = class {
|
|
422
543
|
adapters;
|
|
423
|
-
|
|
544
|
+
_stateAdapter;
|
|
424
545
|
userName;
|
|
425
546
|
logger;
|
|
547
|
+
_streamingUpdateIntervalMs;
|
|
426
548
|
mentionHandlers = [];
|
|
427
549
|
messagePatterns = [];
|
|
428
550
|
subscribedMessageHandlers = [];
|
|
@@ -439,8 +561,9 @@ var Chat = class {
|
|
|
439
561
|
webhooks;
|
|
440
562
|
constructor(config) {
|
|
441
563
|
this.userName = config.userName;
|
|
442
|
-
this.
|
|
564
|
+
this._stateAdapter = config.state;
|
|
443
565
|
this.adapters = /* @__PURE__ */ new Map();
|
|
566
|
+
this._streamingUpdateIntervalMs = config.streamingUpdateIntervalMs ?? 500;
|
|
444
567
|
if (!config.logger) {
|
|
445
568
|
this.logger = new ConsoleLogger("info");
|
|
446
569
|
} else if (typeof config.logger === "string") {
|
|
@@ -485,7 +608,7 @@ var Chat = class {
|
|
|
485
608
|
}
|
|
486
609
|
async doInitialize() {
|
|
487
610
|
this.logger.info("Initializing chat instance...");
|
|
488
|
-
await this.
|
|
611
|
+
await this._stateAdapter.connect();
|
|
489
612
|
this.logger.debug("State connected");
|
|
490
613
|
const initPromises = Array.from(this.adapters.values()).map(
|
|
491
614
|
async (adapter) => {
|
|
@@ -506,7 +629,7 @@ var Chat = class {
|
|
|
506
629
|
*/
|
|
507
630
|
async shutdown() {
|
|
508
631
|
this.logger.info("Shutting down chat instance...");
|
|
509
|
-
await this.
|
|
632
|
+
await this._stateAdapter.disconnect();
|
|
510
633
|
this.initialized = false;
|
|
511
634
|
this.initPromise = null;
|
|
512
635
|
this.logger.info("Chat instance shut down");
|
|
@@ -682,7 +805,7 @@ var Chat = class {
|
|
|
682
805
|
});
|
|
683
806
|
return;
|
|
684
807
|
}
|
|
685
|
-
const isSubscribed = await this.
|
|
808
|
+
const isSubscribed = await this._stateAdapter.isSubscribed(event.threadId);
|
|
686
809
|
const thread = await this.createThread(
|
|
687
810
|
event.adapter,
|
|
688
811
|
event.threadId,
|
|
@@ -734,7 +857,7 @@ var Chat = class {
|
|
|
734
857
|
this.logger.error("Reaction event missing adapter");
|
|
735
858
|
return;
|
|
736
859
|
}
|
|
737
|
-
const isSubscribed = await this.
|
|
860
|
+
const isSubscribed = await this._stateAdapter.isSubscribed(event.threadId);
|
|
738
861
|
const thread = await this.createThread(
|
|
739
862
|
event.adapter,
|
|
740
863
|
event.threadId,
|
|
@@ -776,7 +899,7 @@ var Chat = class {
|
|
|
776
899
|
}
|
|
777
900
|
}
|
|
778
901
|
getState() {
|
|
779
|
-
return this.
|
|
902
|
+
return this._stateAdapter;
|
|
780
903
|
}
|
|
781
904
|
getUserName() {
|
|
782
905
|
return this.userName;
|
|
@@ -876,7 +999,7 @@ var Chat = class {
|
|
|
876
999
|
return;
|
|
877
1000
|
}
|
|
878
1001
|
const dedupeKey = `dedupe:${adapter.name}:${message.id}`;
|
|
879
|
-
const alreadyProcessed = await this.
|
|
1002
|
+
const alreadyProcessed = await this._stateAdapter.get(dedupeKey);
|
|
880
1003
|
if (alreadyProcessed) {
|
|
881
1004
|
this.logger.debug("Skipping duplicate message", {
|
|
882
1005
|
adapter: adapter.name,
|
|
@@ -884,8 +1007,11 @@ var Chat = class {
|
|
|
884
1007
|
});
|
|
885
1008
|
return;
|
|
886
1009
|
}
|
|
887
|
-
await this.
|
|
888
|
-
const lock = await this.
|
|
1010
|
+
await this._stateAdapter.set(dedupeKey, true, DEDUPE_TTL_MS);
|
|
1011
|
+
const lock = await this._stateAdapter.acquireLock(
|
|
1012
|
+
threadId,
|
|
1013
|
+
DEFAULT_LOCK_TTL_MS
|
|
1014
|
+
);
|
|
889
1015
|
if (!lock) {
|
|
890
1016
|
this.logger.warn("Could not acquire lock on thread", { threadId });
|
|
891
1017
|
throw new LockError(
|
|
@@ -895,7 +1021,7 @@ var Chat = class {
|
|
|
895
1021
|
this.logger.debug("Lock acquired", { threadId, token: lock.token });
|
|
896
1022
|
try {
|
|
897
1023
|
message.isMention = this.detectMention(adapter, message);
|
|
898
|
-
const isSubscribed = await this.
|
|
1024
|
+
const isSubscribed = await this._stateAdapter.isSubscribed(threadId);
|
|
899
1025
|
this.logger.debug("Subscription check", {
|
|
900
1026
|
threadId,
|
|
901
1027
|
isSubscribed,
|
|
@@ -951,7 +1077,7 @@ var Chat = class {
|
|
|
951
1077
|
});
|
|
952
1078
|
}
|
|
953
1079
|
} finally {
|
|
954
|
-
await this.
|
|
1080
|
+
await this._stateAdapter.releaseLock(lock);
|
|
955
1081
|
this.logger.debug("Lock released", { threadId });
|
|
956
1082
|
}
|
|
957
1083
|
}
|
|
@@ -963,10 +1089,12 @@ var Chat = class {
|
|
|
963
1089
|
id: threadId,
|
|
964
1090
|
adapter,
|
|
965
1091
|
channelId,
|
|
966
|
-
|
|
1092
|
+
stateAdapter: this._stateAdapter,
|
|
967
1093
|
initialMessage,
|
|
968
1094
|
isSubscribedContext,
|
|
969
|
-
isDM
|
|
1095
|
+
isDM,
|
|
1096
|
+
currentMessage: initialMessage,
|
|
1097
|
+
streamingUpdateIntervalMs: this._streamingUpdateIntervalMs
|
|
970
1098
|
});
|
|
971
1099
|
}
|
|
972
1100
|
/**
|
|
@@ -1405,6 +1533,7 @@ export {
|
|
|
1405
1533
|
NotImplementedError,
|
|
1406
1534
|
RateLimitError,
|
|
1407
1535
|
Section2 as Section,
|
|
1536
|
+
THREAD_STATE_TTL_MS,
|
|
1408
1537
|
blockquote,
|
|
1409
1538
|
codeBlock,
|
|
1410
1539
|
convertEmojiPlaceholders,
|