chat 4.19.0 → 4.20.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-WAB7KMH4.js → chunk-JW7GYSMH.js} +3 -3
- package/dist/chunk-JW7GYSMH.js.map +1 -0
- package/dist/index.d.ts +219 -5
- package/dist/index.js +350 -49
- 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.mdx +54 -0
- package/docs/api/channel.mdx +15 -0
- package/docs/api/index.mdx +7 -0
- package/docs/api/message.mdx +48 -0
- package/docs/api/thread.mdx +50 -0
- package/docs/contributing/building.mdx +1 -0
- package/docs/error-handling.mdx +1 -1
- package/docs/getting-started.mdx +2 -0
- 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/handling-events.mdx +5 -5
- package/docs/index.mdx +3 -1
- package/docs/streaming.mdx +54 -7
- package/docs/threads-messages-channels.mdx +17 -0
- package/package.json +1 -1
- package/dist/chunk-WAB7KMH4.js.map +0 -1
- package/dist/{jsx-runtime-BYavlUk9.d.ts → jsx-runtime-C2ATKxHQ.d.ts} +95 -95
package/dist/index.js
CHANGED
|
@@ -59,7 +59,132 @@ import {
|
|
|
59
59
|
toModalElement,
|
|
60
60
|
toPlainText,
|
|
61
61
|
walkAst
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-JW7GYSMH.js";
|
|
63
|
+
|
|
64
|
+
// src/ai.ts
|
|
65
|
+
var TEXT_MIME_PREFIXES = [
|
|
66
|
+
"text/",
|
|
67
|
+
"application/json",
|
|
68
|
+
"application/xml",
|
|
69
|
+
"application/javascript",
|
|
70
|
+
"application/typescript",
|
|
71
|
+
"application/yaml",
|
|
72
|
+
"application/x-yaml",
|
|
73
|
+
"application/toml"
|
|
74
|
+
];
|
|
75
|
+
function isTextMimeType(mimeType) {
|
|
76
|
+
return TEXT_MIME_PREFIXES.some(
|
|
77
|
+
(prefix) => mimeType === prefix || mimeType.startsWith(prefix)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
async function attachmentToPart(att) {
|
|
81
|
+
if (att.type === "image") {
|
|
82
|
+
if (att.fetchData) {
|
|
83
|
+
try {
|
|
84
|
+
const buffer = await att.fetchData();
|
|
85
|
+
const mimeType = att.mimeType ?? "image/png";
|
|
86
|
+
return {
|
|
87
|
+
type: "file",
|
|
88
|
+
data: `data:${mimeType};base64,${buffer.toString("base64")}`,
|
|
89
|
+
mediaType: mimeType,
|
|
90
|
+
filename: att.name
|
|
91
|
+
};
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error("toAiMessages: failed to fetch image data", error);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (att.type === "file" && att.mimeType && isTextMimeType(att.mimeType)) {
|
|
100
|
+
if (att.fetchData) {
|
|
101
|
+
try {
|
|
102
|
+
const buffer = await att.fetchData();
|
|
103
|
+
return {
|
|
104
|
+
type: "file",
|
|
105
|
+
data: `data:${att.mimeType};base64,${buffer.toString("base64")}`,
|
|
106
|
+
filename: att.name,
|
|
107
|
+
mediaType: att.mimeType
|
|
108
|
+
};
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error("toAiMessages: failed to fetch file data", error);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
async function toAiMessages(messages, options) {
|
|
119
|
+
const includeNames = options?.includeNames ?? false;
|
|
120
|
+
const transformMessage = options?.transformMessage;
|
|
121
|
+
const onUnsupported = options?.onUnsupportedAttachment ?? ((att) => {
|
|
122
|
+
console.warn(
|
|
123
|
+
`toAiMessages: unsupported attachment type "${att.type}"${att.name ? ` (${att.name})` : ""} \u2014 skipped`
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
const sorted = [...messages].sort(
|
|
127
|
+
(a, b) => (a.metadata.dateSent?.getTime() ?? 0) - (b.metadata.dateSent?.getTime() ?? 0)
|
|
128
|
+
);
|
|
129
|
+
const filtered = sorted.filter((msg) => msg.text.trim());
|
|
130
|
+
const results = await Promise.all(
|
|
131
|
+
filtered.map(async (msg) => {
|
|
132
|
+
const role = msg.author.isMe ? "assistant" : "user";
|
|
133
|
+
let textContent = includeNames && role === "user" ? `[${msg.author.userName}]: ${msg.text}` : msg.text;
|
|
134
|
+
if (msg.links.length > 0) {
|
|
135
|
+
const linkParts = msg.links.map((link2) => {
|
|
136
|
+
const parts = link2.fetchMessage ? [`[Embedded message: ${link2.url}]`] : [link2.url];
|
|
137
|
+
if (link2.title) {
|
|
138
|
+
parts.push(`Title: ${link2.title}`);
|
|
139
|
+
}
|
|
140
|
+
if (link2.description) {
|
|
141
|
+
parts.push(`Description: ${link2.description}`);
|
|
142
|
+
}
|
|
143
|
+
if (link2.siteName) {
|
|
144
|
+
parts.push(`Site: ${link2.siteName}`);
|
|
145
|
+
}
|
|
146
|
+
return parts.join("\n");
|
|
147
|
+
}).join("\n\n");
|
|
148
|
+
textContent += `
|
|
149
|
+
|
|
150
|
+
Links:
|
|
151
|
+
${linkParts}`;
|
|
152
|
+
}
|
|
153
|
+
let aiMessage;
|
|
154
|
+
if (role === "user") {
|
|
155
|
+
const attachmentParts = [];
|
|
156
|
+
for (const att of msg.attachments) {
|
|
157
|
+
const part = await attachmentToPart(att);
|
|
158
|
+
if (part) {
|
|
159
|
+
attachmentParts.push(part);
|
|
160
|
+
} else if (att.type === "video" || att.type === "audio") {
|
|
161
|
+
onUnsupported(att, msg);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (attachmentParts.length > 0) {
|
|
165
|
+
aiMessage = {
|
|
166
|
+
role,
|
|
167
|
+
content: [
|
|
168
|
+
{ type: "text", text: textContent },
|
|
169
|
+
...attachmentParts
|
|
170
|
+
]
|
|
171
|
+
};
|
|
172
|
+
} else {
|
|
173
|
+
aiMessage = { role, content: textContent };
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
aiMessage = { role, content: textContent };
|
|
177
|
+
}
|
|
178
|
+
if (transformMessage) {
|
|
179
|
+
return { result: await transformMessage(aiMessage, msg), source: msg };
|
|
180
|
+
}
|
|
181
|
+
return { result: aiMessage, source: msg };
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
return results.filter(
|
|
185
|
+
(r) => r.result != null
|
|
186
|
+
).map((r) => r.result);
|
|
187
|
+
}
|
|
63
188
|
|
|
64
189
|
// src/channel.ts
|
|
65
190
|
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2 } from "@workflow/serde";
|
|
@@ -81,6 +206,42 @@ function hasChatSingleton() {
|
|
|
81
206
|
return _singleton !== null;
|
|
82
207
|
}
|
|
83
208
|
|
|
209
|
+
// src/from-full-stream.ts
|
|
210
|
+
var STREAM_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
211
|
+
"markdown_text",
|
|
212
|
+
"task_update",
|
|
213
|
+
"plan_update"
|
|
214
|
+
]);
|
|
215
|
+
async function* fromFullStream(stream) {
|
|
216
|
+
let needsSeparator = false;
|
|
217
|
+
let hasEmittedText = false;
|
|
218
|
+
for await (const event of stream) {
|
|
219
|
+
if (typeof event === "string") {
|
|
220
|
+
yield event;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (event === null || typeof event !== "object" || !("type" in event)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const typed = event;
|
|
227
|
+
if (STREAM_CHUNK_TYPES.has(typed.type)) {
|
|
228
|
+
yield event;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const textContent = typed.text ?? typed.delta ?? typed.textDelta;
|
|
232
|
+
if (typed.type === "text-delta" && typeof textContent === "string") {
|
|
233
|
+
if (needsSeparator && hasEmittedText) {
|
|
234
|
+
yield "\n\n";
|
|
235
|
+
}
|
|
236
|
+
needsSeparator = false;
|
|
237
|
+
hasEmittedText = true;
|
|
238
|
+
yield textContent;
|
|
239
|
+
} else if (typed.type === "step-finish") {
|
|
240
|
+
needsSeparator = true;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
84
245
|
// src/message.ts
|
|
85
246
|
import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE } from "@workflow/serde";
|
|
86
247
|
var Message = class _Message {
|
|
@@ -121,6 +282,8 @@ var Message = class _Message {
|
|
|
121
282
|
* ```
|
|
122
283
|
*/
|
|
123
284
|
isMention;
|
|
285
|
+
/** Links found in the message */
|
|
286
|
+
links;
|
|
124
287
|
constructor(data) {
|
|
125
288
|
this.id = data.id;
|
|
126
289
|
this.threadId = data.threadId;
|
|
@@ -131,6 +294,7 @@ var Message = class _Message {
|
|
|
131
294
|
this.metadata = data.metadata;
|
|
132
295
|
this.attachments = data.attachments;
|
|
133
296
|
this.isMention = data.isMention;
|
|
297
|
+
this.links = data.links ?? [];
|
|
134
298
|
}
|
|
135
299
|
/**
|
|
136
300
|
* Serialize the message to a plain JSON object.
|
|
@@ -168,7 +332,14 @@ var Message = class _Message {
|
|
|
168
332
|
width: att.width,
|
|
169
333
|
height: att.height
|
|
170
334
|
})),
|
|
171
|
-
isMention: this.isMention
|
|
335
|
+
isMention: this.isMention,
|
|
336
|
+
links: this.links.length > 0 ? this.links.map((link2) => ({
|
|
337
|
+
url: link2.url,
|
|
338
|
+
title: link2.title,
|
|
339
|
+
description: link2.description,
|
|
340
|
+
imageUrl: link2.imageUrl,
|
|
341
|
+
siteName: link2.siteName
|
|
342
|
+
})) : void 0
|
|
172
343
|
};
|
|
173
344
|
}
|
|
174
345
|
/**
|
|
@@ -189,7 +360,8 @@ var Message = class _Message {
|
|
|
189
360
|
editedAt: json.metadata.editedAt ? new Date(json.metadata.editedAt) : void 0
|
|
190
361
|
},
|
|
191
362
|
attachments: json.attachments,
|
|
192
|
-
isMention: json.isMention
|
|
363
|
+
isMention: json.isMention,
|
|
364
|
+
links: json.links
|
|
193
365
|
});
|
|
194
366
|
}
|
|
195
367
|
/**
|
|
@@ -301,6 +473,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
301
473
|
_adapterName;
|
|
302
474
|
_stateAdapterInstance;
|
|
303
475
|
_name = null;
|
|
476
|
+
_messageHistory;
|
|
304
477
|
constructor(config) {
|
|
305
478
|
this.id = config.id;
|
|
306
479
|
this.isDM = config.isDM ?? false;
|
|
@@ -309,6 +482,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
309
482
|
} else {
|
|
310
483
|
this._adapter = config.adapter;
|
|
311
484
|
this._stateAdapterInstance = config.stateAdapter;
|
|
485
|
+
this._messageHistory = config.messageHistory;
|
|
312
486
|
}
|
|
313
487
|
}
|
|
314
488
|
get adapter() {
|
|
@@ -362,14 +536,17 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
362
536
|
get messages() {
|
|
363
537
|
const adapter = this.adapter;
|
|
364
538
|
const channelId = this.id;
|
|
539
|
+
const messageHistory = this._messageHistory;
|
|
365
540
|
return {
|
|
366
541
|
async *[Symbol.asyncIterator]() {
|
|
367
542
|
let cursor;
|
|
543
|
+
let yieldedAny = false;
|
|
368
544
|
while (true) {
|
|
369
545
|
const fetchOptions = { cursor, direction: "backward" };
|
|
370
546
|
const result = adapter.fetchChannelMessages ? await adapter.fetchChannelMessages(channelId, fetchOptions) : await adapter.fetchMessages(channelId, fetchOptions);
|
|
371
547
|
const reversed = [...result.messages].reverse();
|
|
372
548
|
for (const message of reversed) {
|
|
549
|
+
yieldedAny = true;
|
|
373
550
|
yield message;
|
|
374
551
|
}
|
|
375
552
|
if (!result.nextCursor || result.messages.length === 0) {
|
|
@@ -377,6 +554,12 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
377
554
|
}
|
|
378
555
|
cursor = result.nextCursor;
|
|
379
556
|
}
|
|
557
|
+
if (!yieldedAny && messageHistory) {
|
|
558
|
+
const cached = await messageHistory.getMessages(channelId);
|
|
559
|
+
for (let i = cached.length - 1; i >= 0; i--) {
|
|
560
|
+
yield cached[i];
|
|
561
|
+
}
|
|
562
|
+
}
|
|
380
563
|
}
|
|
381
564
|
};
|
|
382
565
|
}
|
|
@@ -422,10 +605,12 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
422
605
|
async post(message) {
|
|
423
606
|
if (isAsyncIterable(message)) {
|
|
424
607
|
let accumulated = "";
|
|
425
|
-
for await (const chunk of message) {
|
|
426
|
-
|
|
608
|
+
for await (const chunk of fromFullStream(message)) {
|
|
609
|
+
if (typeof chunk === "string") {
|
|
610
|
+
accumulated += chunk;
|
|
611
|
+
}
|
|
427
612
|
}
|
|
428
|
-
return this.postSingleMessage(accumulated);
|
|
613
|
+
return this.postSingleMessage({ markdown: accumulated });
|
|
429
614
|
}
|
|
430
615
|
let postable = message;
|
|
431
616
|
if (isJSX(message)) {
|
|
@@ -439,7 +624,15 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
439
624
|
}
|
|
440
625
|
async postSingleMessage(postable) {
|
|
441
626
|
const rawMessage = this.adapter.postChannelMessage ? await this.adapter.postChannelMessage(this.id, postable) : await this.adapter.postMessage(this.id, postable);
|
|
442
|
-
|
|
627
|
+
const sent = this.createSentMessage(
|
|
628
|
+
rawMessage.id,
|
|
629
|
+
postable,
|
|
630
|
+
rawMessage.threadId
|
|
631
|
+
);
|
|
632
|
+
if (this._messageHistory) {
|
|
633
|
+
await this._messageHistory.append(this.id, new Message(sent));
|
|
634
|
+
}
|
|
635
|
+
return sent;
|
|
443
636
|
}
|
|
444
637
|
async postEphemeral(user, message, options) {
|
|
445
638
|
const { fallbackToDM } = options;
|
|
@@ -545,6 +738,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
545
738
|
edited: false
|
|
546
739
|
},
|
|
547
740
|
attachments,
|
|
741
|
+
links: [],
|
|
548
742
|
toJSON() {
|
|
549
743
|
return new Message(this).toJSON();
|
|
550
744
|
},
|
|
@@ -625,45 +819,49 @@ function extractMessageContent(message) {
|
|
|
625
819
|
throw new Error("Invalid PostableMessage format");
|
|
626
820
|
}
|
|
627
821
|
|
|
822
|
+
// src/message-history.ts
|
|
823
|
+
var DEFAULT_MAX_MESSAGES = 100;
|
|
824
|
+
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
825
|
+
var KEY_PREFIX = "msg-history:";
|
|
826
|
+
var MessageHistoryCache = class {
|
|
827
|
+
state;
|
|
828
|
+
maxMessages;
|
|
829
|
+
ttlMs;
|
|
830
|
+
constructor(state, config) {
|
|
831
|
+
this.state = state;
|
|
832
|
+
this.maxMessages = config?.maxMessages ?? DEFAULT_MAX_MESSAGES;
|
|
833
|
+
this.ttlMs = config?.ttlMs ?? DEFAULT_TTL_MS;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Atomically append a message to the history for a thread.
|
|
837
|
+
* Trims to maxMessages (keeps newest) and refreshes TTL.
|
|
838
|
+
*/
|
|
839
|
+
async append(threadId, message) {
|
|
840
|
+
const key = `${KEY_PREFIX}${threadId}`;
|
|
841
|
+
const serialized = message.toJSON();
|
|
842
|
+
serialized.raw = null;
|
|
843
|
+
await this.state.appendToList(key, serialized, {
|
|
844
|
+
maxLength: this.maxMessages,
|
|
845
|
+
ttlMs: this.ttlMs
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Get messages for a thread in chronological order (oldest first).
|
|
850
|
+
*
|
|
851
|
+
* @param threadId - The thread ID
|
|
852
|
+
* @param limit - Optional limit on number of messages to return (returns newest N)
|
|
853
|
+
*/
|
|
854
|
+
async getMessages(threadId, limit) {
|
|
855
|
+
const key = `${KEY_PREFIX}${threadId}`;
|
|
856
|
+
const stored = await this.state.getList(key);
|
|
857
|
+
const sliced = limit && stored.length > limit ? stored.slice(stored.length - limit) : stored;
|
|
858
|
+
return sliced.map((s) => Message.fromJSON(s));
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
|
|
628
862
|
// src/thread.ts
|
|
629
863
|
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE3, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE3 } from "@workflow/serde";
|
|
630
864
|
|
|
631
|
-
// src/from-full-stream.ts
|
|
632
|
-
var STREAM_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
633
|
-
"markdown_text",
|
|
634
|
-
"task_update",
|
|
635
|
-
"plan_update"
|
|
636
|
-
]);
|
|
637
|
-
async function* fromFullStream(stream) {
|
|
638
|
-
let needsSeparator = false;
|
|
639
|
-
let hasEmittedText = false;
|
|
640
|
-
for await (const event of stream) {
|
|
641
|
-
if (typeof event === "string") {
|
|
642
|
-
yield event;
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
if (event === null || typeof event !== "object" || !("type" in event)) {
|
|
646
|
-
continue;
|
|
647
|
-
}
|
|
648
|
-
const typed = event;
|
|
649
|
-
if (STREAM_CHUNK_TYPES.has(typed.type)) {
|
|
650
|
-
yield event;
|
|
651
|
-
continue;
|
|
652
|
-
}
|
|
653
|
-
const textContent = typed.text ?? typed.delta ?? typed.textDelta;
|
|
654
|
-
if (typed.type === "text-delta" && typeof textContent === "string") {
|
|
655
|
-
if (needsSeparator && hasEmittedText) {
|
|
656
|
-
yield "\n\n";
|
|
657
|
-
}
|
|
658
|
-
needsSeparator = false;
|
|
659
|
-
hasEmittedText = true;
|
|
660
|
-
yield textContent;
|
|
661
|
-
} else if (typed.type === "step-finish") {
|
|
662
|
-
needsSeparator = true;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
865
|
// src/streaming-markdown.ts
|
|
668
866
|
import remend from "remend";
|
|
669
867
|
var StreamingMarkdownRenderer = class {
|
|
@@ -917,12 +1115,16 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
917
1115
|
_fallbackStreamingPlaceholderText;
|
|
918
1116
|
/** Cached channel instance */
|
|
919
1117
|
_channel;
|
|
1118
|
+
/** Message history cache (set only for adapters with persistMessageHistory) */
|
|
1119
|
+
_messageHistory;
|
|
1120
|
+
_logger;
|
|
920
1121
|
constructor(config) {
|
|
921
1122
|
this.id = config.id;
|
|
922
1123
|
this.channelId = config.channelId;
|
|
923
1124
|
this.isDM = config.isDM ?? false;
|
|
924
1125
|
this._isSubscribedContext = config.isSubscribedContext ?? false;
|
|
925
1126
|
this._currentMessage = config.currentMessage;
|
|
1127
|
+
this._logger = config.logger;
|
|
926
1128
|
this._streamingUpdateIntervalMs = config.streamingUpdateIntervalMs ?? 500;
|
|
927
1129
|
this._fallbackStreamingPlaceholderText = config.fallbackStreamingPlaceholderText !== void 0 ? config.fallbackStreamingPlaceholderText : "...";
|
|
928
1130
|
if (isLazyConfig2(config)) {
|
|
@@ -930,6 +1132,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
930
1132
|
} else {
|
|
931
1133
|
this._adapter = config.adapter;
|
|
932
1134
|
this._stateAdapterInstance = config.stateAdapter;
|
|
1135
|
+
this._messageHistory = config.messageHistory;
|
|
933
1136
|
}
|
|
934
1137
|
if (config.initialMessage) {
|
|
935
1138
|
this._recentMessages = [config.initialMessage];
|
|
@@ -1008,7 +1211,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1008
1211
|
id: channelId,
|
|
1009
1212
|
adapter: this.adapter,
|
|
1010
1213
|
stateAdapter: this._stateAdapter,
|
|
1011
|
-
isDM: this.isDM
|
|
1214
|
+
isDM: this.isDM,
|
|
1215
|
+
messageHistory: this._messageHistory
|
|
1012
1216
|
});
|
|
1013
1217
|
}
|
|
1014
1218
|
return this._channel;
|
|
@@ -1020,9 +1224,11 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1020
1224
|
get messages() {
|
|
1021
1225
|
const adapter = this.adapter;
|
|
1022
1226
|
const threadId = this.id;
|
|
1227
|
+
const messageHistory = this._messageHistory;
|
|
1023
1228
|
return {
|
|
1024
1229
|
async *[Symbol.asyncIterator]() {
|
|
1025
1230
|
let cursor;
|
|
1231
|
+
let yieldedAny = false;
|
|
1026
1232
|
while (true) {
|
|
1027
1233
|
const result = await adapter.fetchMessages(threadId, {
|
|
1028
1234
|
cursor,
|
|
@@ -1030,6 +1236,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1030
1236
|
});
|
|
1031
1237
|
const reversed = [...result.messages].reverse();
|
|
1032
1238
|
for (const message of reversed) {
|
|
1239
|
+
yieldedAny = true;
|
|
1033
1240
|
yield message;
|
|
1034
1241
|
}
|
|
1035
1242
|
if (!result.nextCursor || result.messages.length === 0) {
|
|
@@ -1037,15 +1244,23 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1037
1244
|
}
|
|
1038
1245
|
cursor = result.nextCursor;
|
|
1039
1246
|
}
|
|
1247
|
+
if (!yieldedAny && messageHistory) {
|
|
1248
|
+
const cached = await messageHistory.getMessages(threadId);
|
|
1249
|
+
for (let i = cached.length - 1; i >= 0; i--) {
|
|
1250
|
+
yield cached[i];
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1040
1253
|
}
|
|
1041
1254
|
};
|
|
1042
1255
|
}
|
|
1043
1256
|
get allMessages() {
|
|
1044
1257
|
const adapter = this.adapter;
|
|
1045
1258
|
const threadId = this.id;
|
|
1259
|
+
const messageHistory = this._messageHistory;
|
|
1046
1260
|
return {
|
|
1047
1261
|
async *[Symbol.asyncIterator]() {
|
|
1048
1262
|
let cursor;
|
|
1263
|
+
let yieldedAny = false;
|
|
1049
1264
|
while (true) {
|
|
1050
1265
|
const result = await adapter.fetchMessages(threadId, {
|
|
1051
1266
|
limit: 100,
|
|
@@ -1053,6 +1268,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1053
1268
|
direction: "forward"
|
|
1054
1269
|
});
|
|
1055
1270
|
for (const message of result.messages) {
|
|
1271
|
+
yieldedAny = true;
|
|
1056
1272
|
yield message;
|
|
1057
1273
|
}
|
|
1058
1274
|
if (!result.nextCursor || result.messages.length === 0) {
|
|
@@ -1060,6 +1276,12 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1060
1276
|
}
|
|
1061
1277
|
cursor = result.nextCursor;
|
|
1062
1278
|
}
|
|
1279
|
+
if (!yieldedAny && messageHistory) {
|
|
1280
|
+
const cached = await messageHistory.getMessages(threadId);
|
|
1281
|
+
for (const message of cached) {
|
|
1282
|
+
yield message;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1063
1285
|
}
|
|
1064
1286
|
};
|
|
1065
1287
|
}
|
|
@@ -1096,6 +1318,9 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1096
1318
|
postable,
|
|
1097
1319
|
rawMessage.threadId
|
|
1098
1320
|
);
|
|
1321
|
+
if (this._messageHistory) {
|
|
1322
|
+
await this._messageHistory.append(this.id, new Message(result));
|
|
1323
|
+
}
|
|
1099
1324
|
return result;
|
|
1100
1325
|
}
|
|
1101
1326
|
async postEphemeral(user, message, options) {
|
|
@@ -1183,11 +1408,15 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1183
1408
|
}
|
|
1184
1409
|
};
|
|
1185
1410
|
const raw = await this.adapter.stream(this.id, wrappedStream, options);
|
|
1186
|
-
|
|
1411
|
+
const sent = this.createSentMessage(
|
|
1187
1412
|
raw.id,
|
|
1188
1413
|
{ markdown: accumulated },
|
|
1189
1414
|
raw.threadId
|
|
1190
1415
|
);
|
|
1416
|
+
if (this._messageHistory) {
|
|
1417
|
+
await this._messageHistory.append(this.id, new Message(sent));
|
|
1418
|
+
}
|
|
1419
|
+
return sent;
|
|
1191
1420
|
}
|
|
1192
1421
|
const textOnlyStream = {
|
|
1193
1422
|
[Symbol.asyncIterator]: () => {
|
|
@@ -1252,7 +1481,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1252
1481
|
markdown: content
|
|
1253
1482
|
});
|
|
1254
1483
|
lastEditContent = content;
|
|
1255
|
-
} catch {
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
this._logger?.warn("fallbackStream edit failed", error);
|
|
1256
1486
|
}
|
|
1257
1487
|
}
|
|
1258
1488
|
if (!stopped) {
|
|
@@ -1299,15 +1529,28 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1299
1529
|
markdown: accumulated
|
|
1300
1530
|
});
|
|
1301
1531
|
}
|
|
1302
|
-
|
|
1532
|
+
const sent = this.createSentMessage(
|
|
1303
1533
|
msg.id,
|
|
1304
1534
|
{ markdown: accumulated },
|
|
1305
1535
|
threadIdForEdits
|
|
1306
1536
|
);
|
|
1537
|
+
if (this._messageHistory) {
|
|
1538
|
+
await this._messageHistory.append(this.id, new Message(sent));
|
|
1539
|
+
}
|
|
1540
|
+
return sent;
|
|
1307
1541
|
}
|
|
1308
1542
|
async refresh() {
|
|
1309
1543
|
const result = await this.adapter.fetchMessages(this.id, { limit: 50 });
|
|
1310
|
-
|
|
1544
|
+
if (result.messages.length > 0) {
|
|
1545
|
+
this._recentMessages = result.messages;
|
|
1546
|
+
} else if (this._messageHistory) {
|
|
1547
|
+
this._recentMessages = await this._messageHistory.getMessages(
|
|
1548
|
+
this.id,
|
|
1549
|
+
50
|
|
1550
|
+
);
|
|
1551
|
+
} else {
|
|
1552
|
+
this._recentMessages = [];
|
|
1553
|
+
}
|
|
1311
1554
|
}
|
|
1312
1555
|
mentionUser(userId) {
|
|
1313
1556
|
return `<@${userId}>`;
|
|
@@ -1389,6 +1632,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1389
1632
|
formatted,
|
|
1390
1633
|
raw: null,
|
|
1391
1634
|
// Will be populated if needed
|
|
1635
|
+
links: [],
|
|
1392
1636
|
author: {
|
|
1393
1637
|
userId: "self",
|
|
1394
1638
|
userName: adapter.userName,
|
|
@@ -1442,6 +1686,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1442
1686
|
author: message.author,
|
|
1443
1687
|
metadata: message.metadata,
|
|
1444
1688
|
attachments: message.attachments,
|
|
1689
|
+
links: message.links,
|
|
1445
1690
|
isMention: message.isMention,
|
|
1446
1691
|
toJSON() {
|
|
1447
1692
|
return message.toJSON();
|
|
@@ -1564,7 +1809,9 @@ var Chat = class {
|
|
|
1564
1809
|
_fallbackStreamingPlaceholderText;
|
|
1565
1810
|
_dedupeTtlMs;
|
|
1566
1811
|
_onLockConflict;
|
|
1812
|
+
_messageHistory;
|
|
1567
1813
|
mentionHandlers = [];
|
|
1814
|
+
directMessageHandlers = [];
|
|
1568
1815
|
messagePatterns = [];
|
|
1569
1816
|
subscribedMessageHandlers = [];
|
|
1570
1817
|
reactionHandlers = [];
|
|
@@ -1593,6 +1840,10 @@ var Chat = class {
|
|
|
1593
1840
|
this._fallbackStreamingPlaceholderText = config.fallbackStreamingPlaceholderText !== void 0 ? config.fallbackStreamingPlaceholderText : "...";
|
|
1594
1841
|
this._dedupeTtlMs = config.dedupeTtlMs ?? DEDUPE_TTL_MS;
|
|
1595
1842
|
this._onLockConflict = config.onLockConflict;
|
|
1843
|
+
this._messageHistory = new MessageHistoryCache(
|
|
1844
|
+
this._stateAdapter,
|
|
1845
|
+
config.messageHistory
|
|
1846
|
+
);
|
|
1596
1847
|
if (typeof config.logger === "string") {
|
|
1597
1848
|
this.logger = new ConsoleLogger(config.logger);
|
|
1598
1849
|
} else {
|
|
@@ -1699,6 +1950,27 @@ var Chat = class {
|
|
|
1699
1950
|
this.mentionHandlers.push(handler);
|
|
1700
1951
|
this.logger.debug("Registered mention handler");
|
|
1701
1952
|
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Register a handler for direct messages.
|
|
1955
|
+
*
|
|
1956
|
+
* Called when a message is received in a DM thread that is not subscribed.
|
|
1957
|
+
* If no `onDirectMessage` handlers are registered, DMs fall through to
|
|
1958
|
+
* `onNewMention` for backward compatibility.
|
|
1959
|
+
*
|
|
1960
|
+
* @param handler - Handler called for DM messages
|
|
1961
|
+
*
|
|
1962
|
+
* @example
|
|
1963
|
+
* ```typescript
|
|
1964
|
+
* chat.onDirectMessage(async (thread, message) => {
|
|
1965
|
+
* await thread.subscribe();
|
|
1966
|
+
* await thread.post("Thanks for the DM!");
|
|
1967
|
+
* });
|
|
1968
|
+
* ```
|
|
1969
|
+
*/
|
|
1970
|
+
onDirectMessage(handler) {
|
|
1971
|
+
this.directMessageHandlers.push(handler);
|
|
1972
|
+
this.logger.debug("Registered direct message handler");
|
|
1973
|
+
}
|
|
1702
1974
|
/**
|
|
1703
1975
|
* Register a handler for messages matching a regex pattern.
|
|
1704
1976
|
*
|
|
@@ -2525,6 +2797,14 @@ var Chat = class {
|
|
|
2525
2797
|
});
|
|
2526
2798
|
return;
|
|
2527
2799
|
}
|
|
2800
|
+
if (adapter.persistMessageHistory) {
|
|
2801
|
+
const channelId = adapter.channelIdFromThreadId(threadId);
|
|
2802
|
+
const appends = [this._messageHistory.append(threadId, message)];
|
|
2803
|
+
if (channelId !== threadId) {
|
|
2804
|
+
appends.push(this._messageHistory.append(channelId, message));
|
|
2805
|
+
}
|
|
2806
|
+
await Promise.all(appends);
|
|
2807
|
+
}
|
|
2528
2808
|
let lock = await this._stateAdapter.acquireLock(
|
|
2529
2809
|
threadId,
|
|
2530
2810
|
DEFAULT_LOCK_TTL_MS
|
|
@@ -2561,6 +2841,21 @@ var Chat = class {
|
|
|
2561
2841
|
message,
|
|
2562
2842
|
isSubscribed
|
|
2563
2843
|
);
|
|
2844
|
+
const isDM = adapter.isDM?.(threadId) ?? false;
|
|
2845
|
+
if (isDM && this.directMessageHandlers.length > 0) {
|
|
2846
|
+
this.logger.debug("Direct message received - calling handlers", {
|
|
2847
|
+
threadId,
|
|
2848
|
+
handlerCount: this.directMessageHandlers.length
|
|
2849
|
+
});
|
|
2850
|
+
const channel = thread.channel;
|
|
2851
|
+
for (const handler of this.directMessageHandlers) {
|
|
2852
|
+
await handler(thread, message, channel);
|
|
2853
|
+
}
|
|
2854
|
+
return;
|
|
2855
|
+
}
|
|
2856
|
+
if (isDM) {
|
|
2857
|
+
message.isMention = true;
|
|
2858
|
+
}
|
|
2564
2859
|
if (isSubscribed) {
|
|
2565
2860
|
this.logger.debug("Message in subscribed thread - calling handlers", {
|
|
2566
2861
|
threadId,
|
|
@@ -2622,8 +2917,10 @@ var Chat = class {
|
|
|
2622
2917
|
isSubscribedContext,
|
|
2623
2918
|
isDM,
|
|
2624
2919
|
currentMessage: initialMessage,
|
|
2920
|
+
logger: this.logger,
|
|
2625
2921
|
streamingUpdateIntervalMs: this._streamingUpdateIntervalMs,
|
|
2626
|
-
fallbackStreamingPlaceholderText: this._fallbackStreamingPlaceholderText
|
|
2922
|
+
fallbackStreamingPlaceholderText: this._fallbackStreamingPlaceholderText,
|
|
2923
|
+
messageHistory: adapter.persistMessageHistory ? this._messageHistory : void 0
|
|
2627
2924
|
});
|
|
2628
2925
|
}
|
|
2629
2926
|
/**
|
|
@@ -2922,6 +3219,8 @@ function convertEmojiPlaceholders(text2, platform, resolver = defaultEmojiResolv
|
|
|
2922
3219
|
return resolver.toGChat(emojiName);
|
|
2923
3220
|
case "linear":
|
|
2924
3221
|
return resolver.toGChat(emojiName);
|
|
3222
|
+
case "whatsapp":
|
|
3223
|
+
return resolver.toGChat(emojiName);
|
|
2925
3224
|
default:
|
|
2926
3225
|
return resolver.toGChat(emojiName);
|
|
2927
3226
|
}
|
|
@@ -3101,6 +3400,7 @@ export {
|
|
|
3101
3400
|
LinkButton2 as LinkButton,
|
|
3102
3401
|
LockError,
|
|
3103
3402
|
Message,
|
|
3403
|
+
MessageHistoryCache,
|
|
3104
3404
|
Modal2 as Modal,
|
|
3105
3405
|
NotImplementedError,
|
|
3106
3406
|
RadioSelect2 as RadioSelect,
|
|
@@ -3157,6 +3457,7 @@ export {
|
|
|
3157
3457
|
tableElementToAscii,
|
|
3158
3458
|
tableToAscii,
|
|
3159
3459
|
text,
|
|
3460
|
+
toAiMessages,
|
|
3160
3461
|
toCardElement2 as toCardElement,
|
|
3161
3462
|
toModalElement2 as toModalElement,
|
|
3162
3463
|
toPlainText,
|