chat 4.18.0 → 4.20.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/README.md +2 -0
- package/dist/{chunk-WAB7KMH4.js → chunk-JW7GYSMH.js} +3 -3
- package/dist/chunk-JW7GYSMH.js.map +1 -0
- package/dist/index.d.ts +220 -13
- package/dist/index.js +274 -56
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/docs/adapters/whatsapp.mdx +222 -0
- package/docs/{adapters/index.mdx → adapters.mdx} +46 -65
- package/docs/api/channel.mdx +15 -0
- package/docs/api/index.mdx +1 -0
- package/docs/api/thread.mdx +50 -0
- package/docs/contributing/building.mdx +1 -0
- package/docs/error-handling.mdx +2 -2
- package/docs/getting-started.mdx +2 -0
- package/docs/guides/code-review-hono.mdx +3 -3
- package/docs/guides/discord-nuxt.mdx +3 -2
- package/docs/guides/durable-chat-sessions-nextjs.mdx +331 -0
- package/docs/guides/meta.json +7 -1
- package/docs/guides/scheduled-posts-neon.mdx +447 -0
- package/docs/guides/slack-nextjs.mdx +3 -2
- package/docs/index.mdx +3 -1
- package/docs/meta.json +3 -4
- package/docs/slash-commands.mdx +4 -4
- package/docs/{state/index.mdx → state.mdx} +24 -12
- package/docs/threads-messages-channels.mdx +17 -0
- package/docs/usage.mdx +5 -0
- package/package.json +1 -1
- package/dist/chunk-WAB7KMH4.js.map +0 -1
- package/docs/adapters/discord.mdx +0 -217
- package/docs/adapters/gchat.mdx +0 -237
- package/docs/adapters/github.mdx +0 -222
- package/docs/adapters/linear.mdx +0 -206
- package/docs/adapters/meta.json +0 -13
- package/docs/adapters/slack.mdx +0 -314
- package/docs/adapters/teams.mdx +0 -287
- package/docs/adapters/telegram.mdx +0 -161
- package/docs/state/ioredis.mdx +0 -81
- package/docs/state/memory.mdx +0 -52
- package/docs/state/meta.json +0 -4
- package/docs/state/postgres.mdx +0 -98
- package/docs/state/redis.mdx +0 -100
- package/dist/{jsx-runtime-BYavlUk9.d.ts → jsx-runtime-C2ATKxHQ.d.ts} +95 -95
package/dist/index.js
CHANGED
|
@@ -59,7 +59,20 @@ import {
|
|
|
59
59
|
toModalElement,
|
|
60
60
|
toPlainText,
|
|
61
61
|
walkAst
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-JW7GYSMH.js";
|
|
63
|
+
|
|
64
|
+
// src/ai.ts
|
|
65
|
+
function toAiMessages(messages, options) {
|
|
66
|
+
const includeNames = options?.includeNames ?? false;
|
|
67
|
+
const sorted = [...messages].sort(
|
|
68
|
+
(a, b) => (a.metadata.dateSent?.getTime() ?? 0) - (b.metadata.dateSent?.getTime() ?? 0)
|
|
69
|
+
);
|
|
70
|
+
return sorted.filter((msg) => msg.text.trim()).map((msg) => {
|
|
71
|
+
const role = msg.author.isMe ? "assistant" : "user";
|
|
72
|
+
const content = includeNames && role === "user" ? `[${msg.author.userName}]: ${msg.text}` : msg.text;
|
|
73
|
+
return { role, content };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
63
76
|
|
|
64
77
|
// src/channel.ts
|
|
65
78
|
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2 } from "@workflow/serde";
|
|
@@ -81,6 +94,42 @@ function hasChatSingleton() {
|
|
|
81
94
|
return _singleton !== null;
|
|
82
95
|
}
|
|
83
96
|
|
|
97
|
+
// src/from-full-stream.ts
|
|
98
|
+
var STREAM_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
99
|
+
"markdown_text",
|
|
100
|
+
"task_update",
|
|
101
|
+
"plan_update"
|
|
102
|
+
]);
|
|
103
|
+
async function* fromFullStream(stream) {
|
|
104
|
+
let needsSeparator = false;
|
|
105
|
+
let hasEmittedText = false;
|
|
106
|
+
for await (const event of stream) {
|
|
107
|
+
if (typeof event === "string") {
|
|
108
|
+
yield event;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (event === null || typeof event !== "object" || !("type" in event)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const typed = event;
|
|
115
|
+
if (STREAM_CHUNK_TYPES.has(typed.type)) {
|
|
116
|
+
yield event;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const textContent = typed.text ?? typed.delta ?? typed.textDelta;
|
|
120
|
+
if (typed.type === "text-delta" && typeof textContent === "string") {
|
|
121
|
+
if (needsSeparator && hasEmittedText) {
|
|
122
|
+
yield "\n\n";
|
|
123
|
+
}
|
|
124
|
+
needsSeparator = false;
|
|
125
|
+
hasEmittedText = true;
|
|
126
|
+
yield textContent;
|
|
127
|
+
} else if (typed.type === "step-finish") {
|
|
128
|
+
needsSeparator = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
84
133
|
// src/message.ts
|
|
85
134
|
import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE } from "@workflow/serde";
|
|
86
135
|
var Message = class _Message {
|
|
@@ -301,6 +350,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
301
350
|
_adapterName;
|
|
302
351
|
_stateAdapterInstance;
|
|
303
352
|
_name = null;
|
|
353
|
+
_messageHistory;
|
|
304
354
|
constructor(config) {
|
|
305
355
|
this.id = config.id;
|
|
306
356
|
this.isDM = config.isDM ?? false;
|
|
@@ -309,6 +359,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
309
359
|
} else {
|
|
310
360
|
this._adapter = config.adapter;
|
|
311
361
|
this._stateAdapterInstance = config.stateAdapter;
|
|
362
|
+
this._messageHistory = config.messageHistory;
|
|
312
363
|
}
|
|
313
364
|
}
|
|
314
365
|
get adapter() {
|
|
@@ -362,14 +413,17 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
362
413
|
get messages() {
|
|
363
414
|
const adapter = this.adapter;
|
|
364
415
|
const channelId = this.id;
|
|
416
|
+
const messageHistory = this._messageHistory;
|
|
365
417
|
return {
|
|
366
418
|
async *[Symbol.asyncIterator]() {
|
|
367
419
|
let cursor;
|
|
420
|
+
let yieldedAny = false;
|
|
368
421
|
while (true) {
|
|
369
422
|
const fetchOptions = { cursor, direction: "backward" };
|
|
370
423
|
const result = adapter.fetchChannelMessages ? await adapter.fetchChannelMessages(channelId, fetchOptions) : await adapter.fetchMessages(channelId, fetchOptions);
|
|
371
424
|
const reversed = [...result.messages].reverse();
|
|
372
425
|
for (const message of reversed) {
|
|
426
|
+
yieldedAny = true;
|
|
373
427
|
yield message;
|
|
374
428
|
}
|
|
375
429
|
if (!result.nextCursor || result.messages.length === 0) {
|
|
@@ -377,6 +431,12 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
377
431
|
}
|
|
378
432
|
cursor = result.nextCursor;
|
|
379
433
|
}
|
|
434
|
+
if (!yieldedAny && messageHistory) {
|
|
435
|
+
const cached = await messageHistory.getMessages(channelId);
|
|
436
|
+
for (let i = cached.length - 1; i >= 0; i--) {
|
|
437
|
+
yield cached[i];
|
|
438
|
+
}
|
|
439
|
+
}
|
|
380
440
|
}
|
|
381
441
|
};
|
|
382
442
|
}
|
|
@@ -422,10 +482,12 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
422
482
|
async post(message) {
|
|
423
483
|
if (isAsyncIterable(message)) {
|
|
424
484
|
let accumulated = "";
|
|
425
|
-
for await (const chunk of message) {
|
|
426
|
-
|
|
485
|
+
for await (const chunk of fromFullStream(message)) {
|
|
486
|
+
if (typeof chunk === "string") {
|
|
487
|
+
accumulated += chunk;
|
|
488
|
+
}
|
|
427
489
|
}
|
|
428
|
-
return this.postSingleMessage(accumulated);
|
|
490
|
+
return this.postSingleMessage({ markdown: accumulated });
|
|
429
491
|
}
|
|
430
492
|
let postable = message;
|
|
431
493
|
if (isJSX(message)) {
|
|
@@ -439,7 +501,15 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
439
501
|
}
|
|
440
502
|
async postSingleMessage(postable) {
|
|
441
503
|
const rawMessage = this.adapter.postChannelMessage ? await this.adapter.postChannelMessage(this.id, postable) : await this.adapter.postMessage(this.id, postable);
|
|
442
|
-
|
|
504
|
+
const sent = this.createSentMessage(
|
|
505
|
+
rawMessage.id,
|
|
506
|
+
postable,
|
|
507
|
+
rawMessage.threadId
|
|
508
|
+
);
|
|
509
|
+
if (this._messageHistory) {
|
|
510
|
+
await this._messageHistory.append(this.id, new Message(sent));
|
|
511
|
+
}
|
|
512
|
+
return sent;
|
|
443
513
|
}
|
|
444
514
|
async postEphemeral(user, message, options) {
|
|
445
515
|
const { fallbackToDM } = options;
|
|
@@ -472,6 +542,25 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
472
542
|
}
|
|
473
543
|
return null;
|
|
474
544
|
}
|
|
545
|
+
async schedule(message, options) {
|
|
546
|
+
let postable;
|
|
547
|
+
if (isJSX(message)) {
|
|
548
|
+
const card = toCardElement(message);
|
|
549
|
+
if (!card) {
|
|
550
|
+
throw new Error("Invalid JSX element: must be a Card element");
|
|
551
|
+
}
|
|
552
|
+
postable = card;
|
|
553
|
+
} else {
|
|
554
|
+
postable = message;
|
|
555
|
+
}
|
|
556
|
+
if (!this.adapter.scheduleMessage) {
|
|
557
|
+
throw new NotImplementedError(
|
|
558
|
+
"Scheduled messages are not supported by this adapter",
|
|
559
|
+
"scheduling"
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
return this.adapter.scheduleMessage(this.id, postable, options);
|
|
563
|
+
}
|
|
475
564
|
async startTyping(status) {
|
|
476
565
|
await this.adapter.startTyping(this.id, status);
|
|
477
566
|
}
|
|
@@ -555,11 +644,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
555
644
|
}
|
|
556
645
|
};
|
|
557
646
|
function deriveChannelId(adapter, threadId) {
|
|
558
|
-
|
|
559
|
-
return adapter.channelIdFromThreadId(threadId);
|
|
560
|
-
}
|
|
561
|
-
const parts = threadId.split(":");
|
|
562
|
-
return parts.slice(0, 2).join(":");
|
|
647
|
+
return adapter.channelIdFromThreadId(threadId);
|
|
563
648
|
}
|
|
564
649
|
function extractMessageContent(message) {
|
|
565
650
|
if (typeof message === "string") {
|
|
@@ -610,45 +695,49 @@ function extractMessageContent(message) {
|
|
|
610
695
|
throw new Error("Invalid PostableMessage format");
|
|
611
696
|
}
|
|
612
697
|
|
|
698
|
+
// src/message-history.ts
|
|
699
|
+
var DEFAULT_MAX_MESSAGES = 100;
|
|
700
|
+
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
701
|
+
var KEY_PREFIX = "msg-history:";
|
|
702
|
+
var MessageHistoryCache = class {
|
|
703
|
+
state;
|
|
704
|
+
maxMessages;
|
|
705
|
+
ttlMs;
|
|
706
|
+
constructor(state, config) {
|
|
707
|
+
this.state = state;
|
|
708
|
+
this.maxMessages = config?.maxMessages ?? DEFAULT_MAX_MESSAGES;
|
|
709
|
+
this.ttlMs = config?.ttlMs ?? DEFAULT_TTL_MS;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Atomically append a message to the history for a thread.
|
|
713
|
+
* Trims to maxMessages (keeps newest) and refreshes TTL.
|
|
714
|
+
*/
|
|
715
|
+
async append(threadId, message) {
|
|
716
|
+
const key = `${KEY_PREFIX}${threadId}`;
|
|
717
|
+
const serialized = message.toJSON();
|
|
718
|
+
serialized.raw = null;
|
|
719
|
+
await this.state.appendToList(key, serialized, {
|
|
720
|
+
maxLength: this.maxMessages,
|
|
721
|
+
ttlMs: this.ttlMs
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Get messages for a thread in chronological order (oldest first).
|
|
726
|
+
*
|
|
727
|
+
* @param threadId - The thread ID
|
|
728
|
+
* @param limit - Optional limit on number of messages to return (returns newest N)
|
|
729
|
+
*/
|
|
730
|
+
async getMessages(threadId, limit) {
|
|
731
|
+
const key = `${KEY_PREFIX}${threadId}`;
|
|
732
|
+
const stored = await this.state.getList(key);
|
|
733
|
+
const sliced = limit && stored.length > limit ? stored.slice(stored.length - limit) : stored;
|
|
734
|
+
return sliced.map((s) => Message.fromJSON(s));
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
|
|
613
738
|
// src/thread.ts
|
|
614
739
|
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE3, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE3 } from "@workflow/serde";
|
|
615
740
|
|
|
616
|
-
// src/from-full-stream.ts
|
|
617
|
-
var STREAM_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
618
|
-
"markdown_text",
|
|
619
|
-
"task_update",
|
|
620
|
-
"plan_update"
|
|
621
|
-
]);
|
|
622
|
-
async function* fromFullStream(stream) {
|
|
623
|
-
let needsSeparator = false;
|
|
624
|
-
let hasEmittedText = false;
|
|
625
|
-
for await (const event of stream) {
|
|
626
|
-
if (typeof event === "string") {
|
|
627
|
-
yield event;
|
|
628
|
-
continue;
|
|
629
|
-
}
|
|
630
|
-
if (event === null || typeof event !== "object" || !("type" in event)) {
|
|
631
|
-
continue;
|
|
632
|
-
}
|
|
633
|
-
const typed = event;
|
|
634
|
-
if (STREAM_CHUNK_TYPES.has(typed.type)) {
|
|
635
|
-
yield event;
|
|
636
|
-
continue;
|
|
637
|
-
}
|
|
638
|
-
const textContent = typed.text ?? typed.delta ?? typed.textDelta;
|
|
639
|
-
if (typed.type === "text-delta" && typeof textContent === "string") {
|
|
640
|
-
if (needsSeparator && hasEmittedText) {
|
|
641
|
-
yield "\n\n";
|
|
642
|
-
}
|
|
643
|
-
needsSeparator = false;
|
|
644
|
-
hasEmittedText = true;
|
|
645
|
-
yield textContent;
|
|
646
|
-
} else if (typed.type === "step-finish") {
|
|
647
|
-
needsSeparator = true;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
741
|
// src/streaming-markdown.ts
|
|
653
742
|
import remend from "remend";
|
|
654
743
|
var StreamingMarkdownRenderer = class {
|
|
@@ -902,6 +991,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
902
991
|
_fallbackStreamingPlaceholderText;
|
|
903
992
|
/** Cached channel instance */
|
|
904
993
|
_channel;
|
|
994
|
+
/** Message history cache (set only for adapters with persistMessageHistory) */
|
|
995
|
+
_messageHistory;
|
|
905
996
|
constructor(config) {
|
|
906
997
|
this.id = config.id;
|
|
907
998
|
this.channelId = config.channelId;
|
|
@@ -915,6 +1006,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
915
1006
|
} else {
|
|
916
1007
|
this._adapter = config.adapter;
|
|
917
1008
|
this._stateAdapterInstance = config.stateAdapter;
|
|
1009
|
+
this._messageHistory = config.messageHistory;
|
|
918
1010
|
}
|
|
919
1011
|
if (config.initialMessage) {
|
|
920
1012
|
this._recentMessages = [config.initialMessage];
|
|
@@ -993,7 +1085,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
993
1085
|
id: channelId,
|
|
994
1086
|
adapter: this.adapter,
|
|
995
1087
|
stateAdapter: this._stateAdapter,
|
|
996
|
-
isDM: this.isDM
|
|
1088
|
+
isDM: this.isDM,
|
|
1089
|
+
messageHistory: this._messageHistory
|
|
997
1090
|
});
|
|
998
1091
|
}
|
|
999
1092
|
return this._channel;
|
|
@@ -1005,9 +1098,11 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1005
1098
|
get messages() {
|
|
1006
1099
|
const adapter = this.adapter;
|
|
1007
1100
|
const threadId = this.id;
|
|
1101
|
+
const messageHistory = this._messageHistory;
|
|
1008
1102
|
return {
|
|
1009
1103
|
async *[Symbol.asyncIterator]() {
|
|
1010
1104
|
let cursor;
|
|
1105
|
+
let yieldedAny = false;
|
|
1011
1106
|
while (true) {
|
|
1012
1107
|
const result = await adapter.fetchMessages(threadId, {
|
|
1013
1108
|
cursor,
|
|
@@ -1015,6 +1110,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1015
1110
|
});
|
|
1016
1111
|
const reversed = [...result.messages].reverse();
|
|
1017
1112
|
for (const message of reversed) {
|
|
1113
|
+
yieldedAny = true;
|
|
1018
1114
|
yield message;
|
|
1019
1115
|
}
|
|
1020
1116
|
if (!result.nextCursor || result.messages.length === 0) {
|
|
@@ -1022,15 +1118,23 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1022
1118
|
}
|
|
1023
1119
|
cursor = result.nextCursor;
|
|
1024
1120
|
}
|
|
1121
|
+
if (!yieldedAny && messageHistory) {
|
|
1122
|
+
const cached = await messageHistory.getMessages(threadId);
|
|
1123
|
+
for (let i = cached.length - 1; i >= 0; i--) {
|
|
1124
|
+
yield cached[i];
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1025
1127
|
}
|
|
1026
1128
|
};
|
|
1027
1129
|
}
|
|
1028
1130
|
get allMessages() {
|
|
1029
1131
|
const adapter = this.adapter;
|
|
1030
1132
|
const threadId = this.id;
|
|
1133
|
+
const messageHistory = this._messageHistory;
|
|
1031
1134
|
return {
|
|
1032
1135
|
async *[Symbol.asyncIterator]() {
|
|
1033
1136
|
let cursor;
|
|
1137
|
+
let yieldedAny = false;
|
|
1034
1138
|
while (true) {
|
|
1035
1139
|
const result = await adapter.fetchMessages(threadId, {
|
|
1036
1140
|
limit: 100,
|
|
@@ -1038,6 +1142,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1038
1142
|
direction: "forward"
|
|
1039
1143
|
});
|
|
1040
1144
|
for (const message of result.messages) {
|
|
1145
|
+
yieldedAny = true;
|
|
1041
1146
|
yield message;
|
|
1042
1147
|
}
|
|
1043
1148
|
if (!result.nextCursor || result.messages.length === 0) {
|
|
@@ -1045,6 +1150,12 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1045
1150
|
}
|
|
1046
1151
|
cursor = result.nextCursor;
|
|
1047
1152
|
}
|
|
1153
|
+
if (!yieldedAny && messageHistory) {
|
|
1154
|
+
const cached = await messageHistory.getMessages(threadId);
|
|
1155
|
+
for (const message of cached) {
|
|
1156
|
+
yield message;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1048
1159
|
}
|
|
1049
1160
|
};
|
|
1050
1161
|
}
|
|
@@ -1081,6 +1192,9 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1081
1192
|
postable,
|
|
1082
1193
|
rawMessage.threadId
|
|
1083
1194
|
);
|
|
1195
|
+
if (this._messageHistory) {
|
|
1196
|
+
await this._messageHistory.append(this.id, new Message(result));
|
|
1197
|
+
}
|
|
1084
1198
|
return result;
|
|
1085
1199
|
}
|
|
1086
1200
|
async postEphemeral(user, message, options) {
|
|
@@ -1114,6 +1228,25 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1114
1228
|
}
|
|
1115
1229
|
return null;
|
|
1116
1230
|
}
|
|
1231
|
+
async schedule(message, options) {
|
|
1232
|
+
let postable;
|
|
1233
|
+
if (isJSX(message)) {
|
|
1234
|
+
const card = toCardElement(message);
|
|
1235
|
+
if (!card) {
|
|
1236
|
+
throw new Error("Invalid JSX element: must be a Card element");
|
|
1237
|
+
}
|
|
1238
|
+
postable = card;
|
|
1239
|
+
} else {
|
|
1240
|
+
postable = message;
|
|
1241
|
+
}
|
|
1242
|
+
if (!this.adapter.scheduleMessage) {
|
|
1243
|
+
throw new NotImplementedError(
|
|
1244
|
+
"Scheduled messages are not supported by this adapter",
|
|
1245
|
+
"scheduling"
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
return this.adapter.scheduleMessage(this.id, postable, options);
|
|
1249
|
+
}
|
|
1117
1250
|
/**
|
|
1118
1251
|
* Handle streaming from an AsyncIterable.
|
|
1119
1252
|
* Normalizes the stream (supports both textStream and fullStream from AI SDK),
|
|
@@ -1149,11 +1282,15 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1149
1282
|
}
|
|
1150
1283
|
};
|
|
1151
1284
|
const raw = await this.adapter.stream(this.id, wrappedStream, options);
|
|
1152
|
-
|
|
1285
|
+
const sent = this.createSentMessage(
|
|
1153
1286
|
raw.id,
|
|
1154
1287
|
{ markdown: accumulated },
|
|
1155
1288
|
raw.threadId
|
|
1156
1289
|
);
|
|
1290
|
+
if (this._messageHistory) {
|
|
1291
|
+
await this._messageHistory.append(this.id, new Message(sent));
|
|
1292
|
+
}
|
|
1293
|
+
return sent;
|
|
1157
1294
|
}
|
|
1158
1295
|
const textOnlyStream = {
|
|
1159
1296
|
[Symbol.asyncIterator]: () => {
|
|
@@ -1265,15 +1402,28 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1265
1402
|
markdown: accumulated
|
|
1266
1403
|
});
|
|
1267
1404
|
}
|
|
1268
|
-
|
|
1405
|
+
const sent = this.createSentMessage(
|
|
1269
1406
|
msg.id,
|
|
1270
1407
|
{ markdown: accumulated },
|
|
1271
1408
|
threadIdForEdits
|
|
1272
1409
|
);
|
|
1410
|
+
if (this._messageHistory) {
|
|
1411
|
+
await this._messageHistory.append(this.id, new Message(sent));
|
|
1412
|
+
}
|
|
1413
|
+
return sent;
|
|
1273
1414
|
}
|
|
1274
1415
|
async refresh() {
|
|
1275
1416
|
const result = await this.adapter.fetchMessages(this.id, { limit: 50 });
|
|
1276
|
-
|
|
1417
|
+
if (result.messages.length > 0) {
|
|
1418
|
+
this._recentMessages = result.messages;
|
|
1419
|
+
} else if (this._messageHistory) {
|
|
1420
|
+
this._recentMessages = await this._messageHistory.getMessages(
|
|
1421
|
+
this.id,
|
|
1422
|
+
50
|
|
1423
|
+
);
|
|
1424
|
+
} else {
|
|
1425
|
+
this._recentMessages = [];
|
|
1426
|
+
}
|
|
1277
1427
|
}
|
|
1278
1428
|
mentionUser(userId) {
|
|
1279
1429
|
return `<@${userId}>`;
|
|
@@ -1529,7 +1679,10 @@ var Chat = class {
|
|
|
1529
1679
|
_streamingUpdateIntervalMs;
|
|
1530
1680
|
_fallbackStreamingPlaceholderText;
|
|
1531
1681
|
_dedupeTtlMs;
|
|
1682
|
+
_onLockConflict;
|
|
1683
|
+
_messageHistory;
|
|
1532
1684
|
mentionHandlers = [];
|
|
1685
|
+
directMessageHandlers = [];
|
|
1533
1686
|
messagePatterns = [];
|
|
1534
1687
|
subscribedMessageHandlers = [];
|
|
1535
1688
|
reactionHandlers = [];
|
|
@@ -1557,6 +1710,11 @@ var Chat = class {
|
|
|
1557
1710
|
this._streamingUpdateIntervalMs = config.streamingUpdateIntervalMs ?? 500;
|
|
1558
1711
|
this._fallbackStreamingPlaceholderText = config.fallbackStreamingPlaceholderText !== void 0 ? config.fallbackStreamingPlaceholderText : "...";
|
|
1559
1712
|
this._dedupeTtlMs = config.dedupeTtlMs ?? DEDUPE_TTL_MS;
|
|
1713
|
+
this._onLockConflict = config.onLockConflict;
|
|
1714
|
+
this._messageHistory = new MessageHistoryCache(
|
|
1715
|
+
this._stateAdapter,
|
|
1716
|
+
config.messageHistory
|
|
1717
|
+
);
|
|
1560
1718
|
if (typeof config.logger === "string") {
|
|
1561
1719
|
this.logger = new ConsoleLogger(config.logger);
|
|
1562
1720
|
} else {
|
|
@@ -1663,6 +1821,27 @@ var Chat = class {
|
|
|
1663
1821
|
this.mentionHandlers.push(handler);
|
|
1664
1822
|
this.logger.debug("Registered mention handler");
|
|
1665
1823
|
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Register a handler for direct messages.
|
|
1826
|
+
*
|
|
1827
|
+
* Called when a message is received in a DM thread that is not subscribed.
|
|
1828
|
+
* If no `onDirectMessage` handlers are registered, DMs fall through to
|
|
1829
|
+
* `onNewMention` for backward compatibility.
|
|
1830
|
+
*
|
|
1831
|
+
* @param handler - Handler called for DM messages
|
|
1832
|
+
*
|
|
1833
|
+
* @example
|
|
1834
|
+
* ```typescript
|
|
1835
|
+
* chat.onDirectMessage(async (thread, message) => {
|
|
1836
|
+
* await thread.subscribe();
|
|
1837
|
+
* await thread.post("Thanks for the DM!");
|
|
1838
|
+
* });
|
|
1839
|
+
* ```
|
|
1840
|
+
*/
|
|
1841
|
+
onDirectMessage(handler) {
|
|
1842
|
+
this.directMessageHandlers.push(handler);
|
|
1843
|
+
this.logger.debug("Registered direct message handler");
|
|
1844
|
+
}
|
|
1666
1845
|
/**
|
|
1667
1846
|
* Register a handler for messages matching a regex pattern.
|
|
1668
1847
|
*
|
|
@@ -2489,15 +2668,34 @@ var Chat = class {
|
|
|
2489
2668
|
});
|
|
2490
2669
|
return;
|
|
2491
2670
|
}
|
|
2492
|
-
|
|
2671
|
+
if (adapter.persistMessageHistory) {
|
|
2672
|
+
const channelId = adapter.channelIdFromThreadId(threadId);
|
|
2673
|
+
const appends = [this._messageHistory.append(threadId, message)];
|
|
2674
|
+
if (channelId !== threadId) {
|
|
2675
|
+
appends.push(this._messageHistory.append(channelId, message));
|
|
2676
|
+
}
|
|
2677
|
+
await Promise.all(appends);
|
|
2678
|
+
}
|
|
2679
|
+
let lock = await this._stateAdapter.acquireLock(
|
|
2493
2680
|
threadId,
|
|
2494
2681
|
DEFAULT_LOCK_TTL_MS
|
|
2495
2682
|
);
|
|
2496
2683
|
if (!lock) {
|
|
2497
|
-
this.
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2684
|
+
const resolution = typeof this._onLockConflict === "function" ? await this._onLockConflict(threadId, message) : this._onLockConflict ?? "drop";
|
|
2685
|
+
if (resolution === "force") {
|
|
2686
|
+
this.logger.info("Force-releasing lock on thread", { threadId });
|
|
2687
|
+
await this._stateAdapter.forceReleaseLock(threadId);
|
|
2688
|
+
lock = await this._stateAdapter.acquireLock(
|
|
2689
|
+
threadId,
|
|
2690
|
+
DEFAULT_LOCK_TTL_MS
|
|
2691
|
+
);
|
|
2692
|
+
}
|
|
2693
|
+
if (!lock) {
|
|
2694
|
+
this.logger.warn("Could not acquire lock on thread", { threadId });
|
|
2695
|
+
throw new LockError(
|
|
2696
|
+
`Could not acquire lock on thread ${threadId}. Another instance may be processing.`
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2501
2699
|
}
|
|
2502
2700
|
this.logger.debug("Lock acquired", { threadId, token: lock.token });
|
|
2503
2701
|
try {
|
|
@@ -2514,6 +2712,21 @@ var Chat = class {
|
|
|
2514
2712
|
message,
|
|
2515
2713
|
isSubscribed
|
|
2516
2714
|
);
|
|
2715
|
+
const isDM = adapter.isDM?.(threadId) ?? false;
|
|
2716
|
+
if (isDM && this.directMessageHandlers.length > 0) {
|
|
2717
|
+
this.logger.debug("Direct message received - calling handlers", {
|
|
2718
|
+
threadId,
|
|
2719
|
+
handlerCount: this.directMessageHandlers.length
|
|
2720
|
+
});
|
|
2721
|
+
const channel = thread.channel;
|
|
2722
|
+
for (const handler of this.directMessageHandlers) {
|
|
2723
|
+
await handler(thread, message, channel);
|
|
2724
|
+
}
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
if (isDM) {
|
|
2728
|
+
message.isMention = true;
|
|
2729
|
+
}
|
|
2517
2730
|
if (isSubscribed) {
|
|
2518
2731
|
this.logger.debug("Message in subscribed thread - calling handlers", {
|
|
2519
2732
|
threadId,
|
|
@@ -2576,7 +2789,8 @@ var Chat = class {
|
|
|
2576
2789
|
isDM,
|
|
2577
2790
|
currentMessage: initialMessage,
|
|
2578
2791
|
streamingUpdateIntervalMs: this._streamingUpdateIntervalMs,
|
|
2579
|
-
fallbackStreamingPlaceholderText: this._fallbackStreamingPlaceholderText
|
|
2792
|
+
fallbackStreamingPlaceholderText: this._fallbackStreamingPlaceholderText,
|
|
2793
|
+
messageHistory: adapter.persistMessageHistory ? this._messageHistory : void 0
|
|
2580
2794
|
});
|
|
2581
2795
|
}
|
|
2582
2796
|
/**
|
|
@@ -2875,6 +3089,8 @@ function convertEmojiPlaceholders(text2, platform, resolver = defaultEmojiResolv
|
|
|
2875
3089
|
return resolver.toGChat(emojiName);
|
|
2876
3090
|
case "linear":
|
|
2877
3091
|
return resolver.toGChat(emojiName);
|
|
3092
|
+
case "whatsapp":
|
|
3093
|
+
return resolver.toGChat(emojiName);
|
|
2878
3094
|
default:
|
|
2879
3095
|
return resolver.toGChat(emojiName);
|
|
2880
3096
|
}
|
|
@@ -3054,6 +3270,7 @@ export {
|
|
|
3054
3270
|
LinkButton2 as LinkButton,
|
|
3055
3271
|
LockError,
|
|
3056
3272
|
Message,
|
|
3273
|
+
MessageHistoryCache,
|
|
3057
3274
|
Modal2 as Modal,
|
|
3058
3275
|
NotImplementedError,
|
|
3059
3276
|
RadioSelect2 as RadioSelect,
|
|
@@ -3110,6 +3327,7 @@ export {
|
|
|
3110
3327
|
tableElementToAscii,
|
|
3111
3328
|
tableToAscii,
|
|
3112
3329
|
text,
|
|
3330
|
+
toAiMessages,
|
|
3113
3331
|
toCardElement2 as toCardElement,
|
|
3114
3332
|
toModalElement2 as toModalElement,
|
|
3115
3333
|
toPlainText,
|