chat 4.27.0 → 4.29.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/ai/index.d.ts +501 -0
- package/dist/ai/index.js +500 -0
- package/dist/chat-D9UYaaNO.d.ts +3156 -0
- package/dist/chunk-HD375J7S.js +128 -0
- package/dist/{chunk-AN7MRAVW.js → chunk-V25FKIIL.js} +5 -1
- package/dist/index.d.ts +35 -2934
- package/dist/index.js +567 -210
- package/dist/{jsx-runtime-Co9uV6l7.d.ts → jsx-runtime-CFq1K_Ve.d.ts} +11 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/docs/actions.mdx +52 -1
- package/docs/adapters.mdx +72 -36
- package/docs/ai/ai-sdk-tools.mdx +227 -0
- package/docs/ai/index.mdx +63 -0
- package/docs/ai/meta.json +4 -0
- package/docs/{api → ai}/to-ai-messages.mdx +16 -3
- package/docs/ai/types.mdx +243 -0
- package/docs/api/cards.mdx +4 -0
- package/docs/api/chat.mdx +132 -10
- package/docs/api/index.mdx +6 -6
- package/docs/api/markdown.mdx +28 -5
- package/docs/api/message.mdx +54 -1
- package/docs/api/meta.json +1 -0
- package/docs/api/modals.mdx +50 -0
- package/docs/api/postable-message.mdx +58 -4
- package/docs/api/thread.mdx +11 -3
- package/docs/api/transcripts.mdx +220 -0
- package/docs/cards.mdx +6 -0
- package/docs/concurrency.mdx +58 -15
- package/docs/contributing/building.mdx +74 -2
- package/docs/contributing/testing.mdx +4 -0
- package/docs/conversation-history.mdx +137 -0
- package/docs/direct-messages.mdx +23 -5
- package/docs/ephemeral-messages.mdx +1 -1
- package/docs/error-handling.mdx +15 -3
- package/docs/files.mdx +21 -1
- package/docs/handling-events.mdx +10 -7
- package/docs/index.mdx +8 -5
- package/docs/meta.json +17 -3
- package/docs/modals.mdx +24 -0
- package/docs/posting-messages.mdx +10 -4
- package/docs/slash-commands.mdx +4 -4
- package/docs/streaming.mdx +75 -27
- package/docs/subject.mdx +53 -0
- package/docs/testing.mdx +142 -0
- package/docs/threads-messages-channels.mdx +10 -1
- package/docs/usage.mdx +15 -2
- package/package.json +23 -2
- package/resources/guides/how-to-build-an-ai-agent-for-slack-with-chat-sdk-and-ai-sdk.md +1 -1
package/dist/index.js
CHANGED
|
@@ -60,135 +60,134 @@ import {
|
|
|
60
60
|
toModalElement,
|
|
61
61
|
toPlainText,
|
|
62
62
|
walkAst
|
|
63
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-V25FKIIL.js";
|
|
64
|
+
import {
|
|
65
|
+
toAiMessages
|
|
66
|
+
} from "./chunk-HD375J7S.js";
|
|
64
67
|
|
|
65
|
-
// src/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"application/toml"
|
|
75
|
-
];
|
|
76
|
-
function isTextMimeType(mimeType) {
|
|
77
|
-
return TEXT_MIME_PREFIXES.some(
|
|
78
|
-
(prefix) => mimeType === prefix || mimeType.startsWith(prefix)
|
|
79
|
-
);
|
|
68
|
+
// src/channel.ts
|
|
69
|
+
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2 } from "@workflow/serde";
|
|
70
|
+
|
|
71
|
+
// src/callback-url.ts
|
|
72
|
+
var CALLBACK_TOKEN_PREFIX = "__cb:";
|
|
73
|
+
var CALLBACK_CACHE_KEY_PREFIX = "chat:callback:";
|
|
74
|
+
var CALLBACK_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
75
|
+
function encodeCallbackValue(token) {
|
|
76
|
+
return `${CALLBACK_TOKEN_PREFIX}${token}`;
|
|
80
77
|
}
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
function decodeCallbackValue(value) {
|
|
79
|
+
if (!value?.startsWith(CALLBACK_TOKEN_PREFIX)) {
|
|
80
|
+
return { callbackToken: void 0 };
|
|
81
|
+
}
|
|
82
|
+
return { callbackToken: value.slice(CALLBACK_TOKEN_PREFIX.length) };
|
|
83
|
+
}
|
|
84
|
+
function generateToken() {
|
|
85
|
+
return crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
86
|
+
}
|
|
87
|
+
async function processActionsElement(actions, stateAdapter) {
|
|
88
|
+
return {
|
|
89
|
+
type: "actions",
|
|
90
|
+
children: await Promise.all(
|
|
91
|
+
actions.children.map(async (el) => {
|
|
92
|
+
if (el.type !== "button" || !el.callbackUrl) {
|
|
93
|
+
return el;
|
|
94
|
+
}
|
|
95
|
+
const token = generateToken();
|
|
96
|
+
const stored = {
|
|
97
|
+
url: el.callbackUrl,
|
|
98
|
+
originalValue: el.value
|
|
92
99
|
};
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
await stateAdapter.set(
|
|
101
|
+
`${CALLBACK_CACHE_KEY_PREFIX}${token}`,
|
|
102
|
+
stored,
|
|
103
|
+
CALLBACK_TTL_MS
|
|
104
|
+
);
|
|
105
|
+
const processed = {
|
|
106
|
+
type: "button",
|
|
107
|
+
id: el.id,
|
|
108
|
+
label: el.label,
|
|
109
|
+
style: el.style,
|
|
110
|
+
disabled: el.disabled,
|
|
111
|
+
value: encodeCallbackValue(token),
|
|
112
|
+
actionType: el.actionType
|
|
113
|
+
};
|
|
114
|
+
return processed;
|
|
115
|
+
})
|
|
116
|
+
)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function hasCallbackButtons(children) {
|
|
120
|
+
for (const child of children) {
|
|
121
|
+
if (child.type === "actions") {
|
|
122
|
+
for (const el of child.children) {
|
|
123
|
+
if (el.type === "button" && el.callbackUrl) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
96
126
|
}
|
|
97
127
|
}
|
|
98
|
-
|
|
128
|
+
if (child.type === "section" && "children" in child && hasCallbackButtons(child.children)) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
99
131
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
async function processChildren(children, stateAdapter) {
|
|
135
|
+
const result = [];
|
|
136
|
+
for (const child of children) {
|
|
137
|
+
if (child.type === "actions") {
|
|
138
|
+
result.push(await processActionsElement(child, stateAdapter));
|
|
139
|
+
} else if (child.type === "section" && "children" in child) {
|
|
140
|
+
result.push({
|
|
141
|
+
...child,
|
|
142
|
+
children: await processChildren(child.children, stateAdapter)
|
|
143
|
+
});
|
|
144
|
+
} else {
|
|
145
|
+
result.push(child);
|
|
114
146
|
}
|
|
115
|
-
return null;
|
|
116
147
|
}
|
|
117
|
-
return
|
|
148
|
+
return result;
|
|
118
149
|
}
|
|
119
|
-
async function
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const results = await Promise.all(
|
|
132
|
-
filtered.map(async (msg) => {
|
|
133
|
-
const role = msg.author.isMe ? "assistant" : "user";
|
|
134
|
-
let textContent = includeNames && role === "user" ? `[${msg.author.userName}]: ${msg.text}` : msg.text;
|
|
135
|
-
if (msg.links && msg.links.length > 0) {
|
|
136
|
-
const linkParts = msg.links.map((link2) => {
|
|
137
|
-
const parts = link2.fetchMessage ? [`[Embedded message: ${link2.url}]`] : [link2.url];
|
|
138
|
-
if (link2.title) {
|
|
139
|
-
parts.push(`Title: ${link2.title}`);
|
|
140
|
-
}
|
|
141
|
-
if (link2.description) {
|
|
142
|
-
parts.push(`Description: ${link2.description}`);
|
|
143
|
-
}
|
|
144
|
-
if (link2.siteName) {
|
|
145
|
-
parts.push(`Site: ${link2.siteName}`);
|
|
146
|
-
}
|
|
147
|
-
return parts.join("\n");
|
|
148
|
-
}).join("\n\n");
|
|
149
|
-
textContent += `
|
|
150
|
-
|
|
151
|
-
Links:
|
|
152
|
-
${linkParts}`;
|
|
153
|
-
}
|
|
154
|
-
let aiMessage;
|
|
155
|
-
if (role === "user") {
|
|
156
|
-
const attachmentParts = [];
|
|
157
|
-
for (const att of msg.attachments ?? []) {
|
|
158
|
-
const part = await attachmentToPart(att);
|
|
159
|
-
if (part) {
|
|
160
|
-
attachmentParts.push(part);
|
|
161
|
-
} else if (att.type === "video" || att.type === "audio") {
|
|
162
|
-
onUnsupported(att, msg);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (attachmentParts.length > 0) {
|
|
166
|
-
aiMessage = {
|
|
167
|
-
role,
|
|
168
|
-
content: [
|
|
169
|
-
{ type: "text", text: textContent },
|
|
170
|
-
...attachmentParts
|
|
171
|
-
]
|
|
172
|
-
};
|
|
173
|
-
} else {
|
|
174
|
-
aiMessage = { role, content: textContent };
|
|
175
|
-
}
|
|
176
|
-
} else {
|
|
177
|
-
aiMessage = { role, content: textContent };
|
|
178
|
-
}
|
|
179
|
-
if (transformMessage) {
|
|
180
|
-
return { result: await transformMessage(aiMessage, msg), source: msg };
|
|
181
|
-
}
|
|
182
|
-
return { result: aiMessage, source: msg };
|
|
183
|
-
})
|
|
150
|
+
async function processCardCallbackUrls(card, stateAdapter) {
|
|
151
|
+
if (!hasCallbackButtons(card.children)) {
|
|
152
|
+
return card;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
...card,
|
|
156
|
+
children: await processChildren(card.children, stateAdapter)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async function resolveCallbackUrl(token, stateAdapter) {
|
|
160
|
+
const stored = await stateAdapter.get(
|
|
161
|
+
`${CALLBACK_CACHE_KEY_PREFIX}${token}`
|
|
184
162
|
);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
163
|
+
if (!stored) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
if (typeof stored === "string") {
|
|
167
|
+
return { url: stored };
|
|
168
|
+
}
|
|
169
|
+
return stored;
|
|
170
|
+
}
|
|
171
|
+
async function postToCallbackUrl(callbackUrl, payload) {
|
|
172
|
+
try {
|
|
173
|
+
const response = await fetch(callbackUrl, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: { "Content-Type": "application/json" },
|
|
176
|
+
body: JSON.stringify(payload)
|
|
177
|
+
});
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
return {
|
|
180
|
+
error: new Error(
|
|
181
|
+
`Callback URL returned ${response.status}: ${await response.text().catch(() => "")}`
|
|
182
|
+
),
|
|
183
|
+
status: response.status
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return { status: response.status };
|
|
187
|
+
} catch (error) {
|
|
188
|
+
return { error };
|
|
189
|
+
}
|
|
188
190
|
}
|
|
189
|
-
|
|
190
|
-
// src/channel.ts
|
|
191
|
-
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2 } from "@workflow/serde";
|
|
192
191
|
|
|
193
192
|
// src/chat-singleton.ts
|
|
194
193
|
var _singleton = null;
|
|
@@ -245,6 +244,10 @@ async function* fromFullStream(stream) {
|
|
|
245
244
|
|
|
246
245
|
// src/message.ts
|
|
247
246
|
import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE } from "@workflow/serde";
|
|
247
|
+
var adapterMap = /* @__PURE__ */ new WeakMap();
|
|
248
|
+
function setMessageAdapter(message, adapter) {
|
|
249
|
+
adapterMap.set(message, adapter);
|
|
250
|
+
}
|
|
248
251
|
var Message = class _Message {
|
|
249
252
|
/** Unique message ID */
|
|
250
253
|
id;
|
|
@@ -283,8 +286,31 @@ var Message = class _Message {
|
|
|
283
286
|
* ```
|
|
284
287
|
*/
|
|
285
288
|
isMention;
|
|
289
|
+
/**
|
|
290
|
+
* Cross-platform user key for this message's author.
|
|
291
|
+
*
|
|
292
|
+
* Set by the Chat SDK before passing the message to handlers, when
|
|
293
|
+
* `ChatConfig.identity` is configured. `undefined` if no resolver is
|
|
294
|
+
* configured; `undefined` (i.e. absent) when the resolver returned null.
|
|
295
|
+
*
|
|
296
|
+
* Used by the Transcripts API to look up / append per-user transcripts.
|
|
297
|
+
*/
|
|
298
|
+
userKey;
|
|
286
299
|
/** Links found in the message */
|
|
287
300
|
links;
|
|
301
|
+
_subjectPromise;
|
|
302
|
+
get subject() {
|
|
303
|
+
if (this._subjectPromise) {
|
|
304
|
+
return this._subjectPromise;
|
|
305
|
+
}
|
|
306
|
+
const adapter = adapterMap.get(this);
|
|
307
|
+
if (!adapter?.fetchSubject) {
|
|
308
|
+
this._subjectPromise = Promise.resolve(null);
|
|
309
|
+
return this._subjectPromise;
|
|
310
|
+
}
|
|
311
|
+
this._subjectPromise = adapter.fetchSubject(this.raw).catch(() => null);
|
|
312
|
+
return this._subjectPromise;
|
|
313
|
+
}
|
|
288
314
|
constructor(data) {
|
|
289
315
|
this.id = data.id;
|
|
290
316
|
this.threadId = data.threadId;
|
|
@@ -497,7 +523,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
497
523
|
_adapterName;
|
|
498
524
|
_stateAdapterInstance;
|
|
499
525
|
_name = null;
|
|
500
|
-
|
|
526
|
+
_threadHistory;
|
|
501
527
|
constructor(config) {
|
|
502
528
|
this.id = config.id;
|
|
503
529
|
this.isDM = config.isDM ?? false;
|
|
@@ -507,7 +533,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
507
533
|
} else {
|
|
508
534
|
this._adapter = config.adapter;
|
|
509
535
|
this._stateAdapterInstance = config.stateAdapter;
|
|
510
|
-
this.
|
|
536
|
+
this._threadHistory = config.threadHistory;
|
|
511
537
|
}
|
|
512
538
|
}
|
|
513
539
|
get adapter() {
|
|
@@ -561,7 +587,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
561
587
|
get messages() {
|
|
562
588
|
const adapter = this.adapter;
|
|
563
589
|
const channelId = this.id;
|
|
564
|
-
const
|
|
590
|
+
const threadHistory = this._threadHistory;
|
|
565
591
|
return {
|
|
566
592
|
async *[Symbol.asyncIterator]() {
|
|
567
593
|
let cursor;
|
|
@@ -579,8 +605,8 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
579
605
|
}
|
|
580
606
|
cursor = result.nextCursor;
|
|
581
607
|
}
|
|
582
|
-
if (!yieldedAny &&
|
|
583
|
-
const cached = await
|
|
608
|
+
if (!yieldedAny && threadHistory) {
|
|
609
|
+
const cached = await threadHistory.getMessages(channelId);
|
|
584
610
|
for (let i = cached.length - 1; i >= 0; i--) {
|
|
585
611
|
yield cached[i];
|
|
586
612
|
}
|
|
@@ -637,6 +663,8 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
637
663
|
for await (const chunk of fromFullStream(message)) {
|
|
638
664
|
if (typeof chunk === "string") {
|
|
639
665
|
accumulated += chunk;
|
|
666
|
+
} else if (chunk.type === "markdown_text") {
|
|
667
|
+
accumulated += chunk.text;
|
|
640
668
|
}
|
|
641
669
|
}
|
|
642
670
|
return this.postSingleMessage({ markdown: accumulated });
|
|
@@ -649,6 +677,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
649
677
|
}
|
|
650
678
|
postable = card;
|
|
651
679
|
}
|
|
680
|
+
postable = await this.processCallbackUrls(postable);
|
|
652
681
|
return this.postSingleMessage(postable);
|
|
653
682
|
}
|
|
654
683
|
async handlePostableObject(obj) {
|
|
@@ -666,8 +695,8 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
666
695
|
postable,
|
|
667
696
|
rawMessage.threadId
|
|
668
697
|
);
|
|
669
|
-
if (this.
|
|
670
|
-
await this.
|
|
698
|
+
if (this._threadHistory) {
|
|
699
|
+
await this._threadHistory.append(this.id, new Message(sent));
|
|
671
700
|
}
|
|
672
701
|
return sent;
|
|
673
702
|
}
|
|
@@ -684,6 +713,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
684
713
|
} else {
|
|
685
714
|
postable = message;
|
|
686
715
|
}
|
|
716
|
+
postable = await this.processCallbackUrls(postable);
|
|
687
717
|
if (this.adapter.postEphemeral) {
|
|
688
718
|
return this.adapter.postEphemeral(this.id, userId, postable);
|
|
689
719
|
}
|
|
@@ -713,6 +743,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
713
743
|
} else {
|
|
714
744
|
postable = message;
|
|
715
745
|
}
|
|
746
|
+
postable = await this.processCallbackUrls(postable);
|
|
716
747
|
if (!this.adapter.scheduleMessage) {
|
|
717
748
|
throw new NotImplementedError(
|
|
718
749
|
"Scheduled messages are not supported by this adapter",
|
|
@@ -721,6 +752,24 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
721
752
|
}
|
|
722
753
|
return this.adapter.scheduleMessage(this.id, postable, options);
|
|
723
754
|
}
|
|
755
|
+
async processCallbackUrls(postable) {
|
|
756
|
+
if (typeof postable === "string") {
|
|
757
|
+
return postable;
|
|
758
|
+
}
|
|
759
|
+
if ("type" in postable && postable.type === "card") {
|
|
760
|
+
return processCardCallbackUrls(postable, this._stateAdapter);
|
|
761
|
+
}
|
|
762
|
+
if ("card" in postable && postable.card?.type === "card") {
|
|
763
|
+
const processed = await processCardCallbackUrls(
|
|
764
|
+
postable.card,
|
|
765
|
+
this._stateAdapter
|
|
766
|
+
);
|
|
767
|
+
if (processed !== postable.card) {
|
|
768
|
+
return { ...postable, card: processed };
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return postable;
|
|
772
|
+
}
|
|
724
773
|
async startTyping(status) {
|
|
725
774
|
await this.adapter.startTyping(this.id, status);
|
|
726
775
|
}
|
|
@@ -790,6 +839,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
790
839
|
}
|
|
791
840
|
editPostable = card;
|
|
792
841
|
}
|
|
842
|
+
editPostable = await self.processCallbackUrls(editPostable);
|
|
793
843
|
await adapter.editMessage(threadId, messageId, editPostable);
|
|
794
844
|
return self.createSentMessage(messageId, editPostable);
|
|
795
845
|
},
|
|
@@ -858,46 +908,6 @@ function extractMessageContent(message) {
|
|
|
858
908
|
throw new Error("Invalid PostableMessage format");
|
|
859
909
|
}
|
|
860
910
|
|
|
861
|
-
// src/message-history.ts
|
|
862
|
-
var DEFAULT_MAX_MESSAGES = 100;
|
|
863
|
-
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
864
|
-
var KEY_PREFIX = "msg-history:";
|
|
865
|
-
var MessageHistoryCache = class {
|
|
866
|
-
state;
|
|
867
|
-
maxMessages;
|
|
868
|
-
ttlMs;
|
|
869
|
-
constructor(state, config) {
|
|
870
|
-
this.state = state;
|
|
871
|
-
this.maxMessages = config?.maxMessages ?? DEFAULT_MAX_MESSAGES;
|
|
872
|
-
this.ttlMs = config?.ttlMs ?? DEFAULT_TTL_MS;
|
|
873
|
-
}
|
|
874
|
-
/**
|
|
875
|
-
* Atomically append a message to the history for a thread.
|
|
876
|
-
* Trims to maxMessages (keeps newest) and refreshes TTL.
|
|
877
|
-
*/
|
|
878
|
-
async append(threadId, message) {
|
|
879
|
-
const key = `${KEY_PREFIX}${threadId}`;
|
|
880
|
-
const serialized = message.toJSON();
|
|
881
|
-
serialized.raw = null;
|
|
882
|
-
await this.state.appendToList(key, serialized, {
|
|
883
|
-
maxLength: this.maxMessages,
|
|
884
|
-
ttlMs: this.ttlMs
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Get messages for a thread in chronological order (oldest first).
|
|
889
|
-
*
|
|
890
|
-
* @param threadId - The thread ID
|
|
891
|
-
* @param limit - Optional limit on number of messages to return (returns newest N)
|
|
892
|
-
*/
|
|
893
|
-
async getMessages(threadId, limit) {
|
|
894
|
-
const key = `${KEY_PREFIX}${threadId}`;
|
|
895
|
-
const stored = await this.state.getList(key);
|
|
896
|
-
const sliced = limit && stored.length > limit ? stored.slice(stored.length - limit) : stored;
|
|
897
|
-
return sliced.map((s) => Message.fromJSON(s));
|
|
898
|
-
}
|
|
899
|
-
};
|
|
900
|
-
|
|
901
911
|
// src/thread.ts
|
|
902
912
|
import { WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE3, WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE3 } from "@workflow/serde";
|
|
903
913
|
|
|
@@ -1168,8 +1178,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1168
1178
|
_fallbackStreamingPlaceholderText;
|
|
1169
1179
|
/** Cached channel instance */
|
|
1170
1180
|
_channel;
|
|
1171
|
-
/**
|
|
1172
|
-
|
|
1181
|
+
/** Thread history cache (set only for adapters with persistThreadHistory) */
|
|
1182
|
+
_threadHistory;
|
|
1173
1183
|
_logger;
|
|
1174
1184
|
constructor(config) {
|
|
1175
1185
|
this.id = config.id;
|
|
@@ -1186,7 +1196,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1186
1196
|
} else {
|
|
1187
1197
|
this._adapter = config.adapter;
|
|
1188
1198
|
this._stateAdapterInstance = config.stateAdapter;
|
|
1189
|
-
this.
|
|
1199
|
+
this._threadHistory = config.threadHistory;
|
|
1190
1200
|
}
|
|
1191
1201
|
if (config.initialMessage) {
|
|
1192
1202
|
this._recentMessages = [config.initialMessage];
|
|
@@ -1267,7 +1277,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1267
1277
|
stateAdapter: this._stateAdapter,
|
|
1268
1278
|
isDM: this.isDM,
|
|
1269
1279
|
channelVisibility: this.channelVisibility,
|
|
1270
|
-
|
|
1280
|
+
threadHistory: this._threadHistory
|
|
1271
1281
|
});
|
|
1272
1282
|
}
|
|
1273
1283
|
return this._channel;
|
|
@@ -1279,7 +1289,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1279
1289
|
get messages() {
|
|
1280
1290
|
const adapter = this.adapter;
|
|
1281
1291
|
const threadId = this.id;
|
|
1282
|
-
const
|
|
1292
|
+
const threadHistory = this._threadHistory;
|
|
1283
1293
|
return {
|
|
1284
1294
|
async *[Symbol.asyncIterator]() {
|
|
1285
1295
|
let cursor;
|
|
@@ -1299,8 +1309,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1299
1309
|
}
|
|
1300
1310
|
cursor = result.nextCursor;
|
|
1301
1311
|
}
|
|
1302
|
-
if (!yieldedAny &&
|
|
1303
|
-
const cached = await
|
|
1312
|
+
if (!yieldedAny && threadHistory) {
|
|
1313
|
+
const cached = await threadHistory.getMessages(threadId);
|
|
1304
1314
|
for (let i = cached.length - 1; i >= 0; i--) {
|
|
1305
1315
|
yield cached[i];
|
|
1306
1316
|
}
|
|
@@ -1311,7 +1321,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1311
1321
|
get allMessages() {
|
|
1312
1322
|
const adapter = this.adapter;
|
|
1313
1323
|
const threadId = this.id;
|
|
1314
|
-
const
|
|
1324
|
+
const threadHistory = this._threadHistory;
|
|
1315
1325
|
return {
|
|
1316
1326
|
async *[Symbol.asyncIterator]() {
|
|
1317
1327
|
let cursor;
|
|
@@ -1331,8 +1341,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1331
1341
|
}
|
|
1332
1342
|
cursor = result.nextCursor;
|
|
1333
1343
|
}
|
|
1334
|
-
if (!yieldedAny &&
|
|
1335
|
-
const cached = await
|
|
1344
|
+
if (!yieldedAny && threadHistory) {
|
|
1345
|
+
const cached = await threadHistory.getMessages(threadId);
|
|
1336
1346
|
for (const message of cached) {
|
|
1337
1347
|
yield message;
|
|
1338
1348
|
}
|
|
@@ -1394,14 +1404,15 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1394
1404
|
}
|
|
1395
1405
|
postable = card;
|
|
1396
1406
|
}
|
|
1407
|
+
postable = await this.processCallbackUrls(postable);
|
|
1397
1408
|
const rawMessage = await this.adapter.postMessage(this.id, postable);
|
|
1398
1409
|
const result = this.createSentMessage(
|
|
1399
1410
|
rawMessage.id,
|
|
1400
1411
|
postable,
|
|
1401
1412
|
rawMessage.threadId
|
|
1402
1413
|
);
|
|
1403
|
-
if (this.
|
|
1404
|
-
await this.
|
|
1414
|
+
if (this._threadHistory) {
|
|
1415
|
+
await this._threadHistory.append(this.id, new Message(result));
|
|
1405
1416
|
}
|
|
1406
1417
|
return result;
|
|
1407
1418
|
}
|
|
@@ -1427,6 +1438,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1427
1438
|
} else {
|
|
1428
1439
|
postable = message;
|
|
1429
1440
|
}
|
|
1441
|
+
postable = await this.processCallbackUrls(postable);
|
|
1430
1442
|
if (this.adapter.postEphemeral) {
|
|
1431
1443
|
return this.adapter.postEphemeral(this.id, userId, postable);
|
|
1432
1444
|
}
|
|
@@ -1445,6 +1457,24 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1445
1457
|
}
|
|
1446
1458
|
return null;
|
|
1447
1459
|
}
|
|
1460
|
+
async processCallbackUrls(postable) {
|
|
1461
|
+
if (typeof postable === "string") {
|
|
1462
|
+
return postable;
|
|
1463
|
+
}
|
|
1464
|
+
if ("type" in postable && postable.type === "card") {
|
|
1465
|
+
return processCardCallbackUrls(postable, this._stateAdapter);
|
|
1466
|
+
}
|
|
1467
|
+
if ("card" in postable && postable.card?.type === "card") {
|
|
1468
|
+
const processed = await processCardCallbackUrls(
|
|
1469
|
+
postable.card,
|
|
1470
|
+
this._stateAdapter
|
|
1471
|
+
);
|
|
1472
|
+
if (processed !== postable.card) {
|
|
1473
|
+
return { ...postable, card: processed };
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
return postable;
|
|
1477
|
+
}
|
|
1448
1478
|
async schedule(message, options) {
|
|
1449
1479
|
let postable;
|
|
1450
1480
|
if (isJSX(message)) {
|
|
@@ -1456,6 +1486,9 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1456
1486
|
} else {
|
|
1457
1487
|
postable = message;
|
|
1458
1488
|
}
|
|
1489
|
+
postable = await this.processCallbackUrls(
|
|
1490
|
+
postable
|
|
1491
|
+
);
|
|
1459
1492
|
if (!this.adapter.scheduleMessage) {
|
|
1460
1493
|
throw new NotImplementedError(
|
|
1461
1494
|
"Scheduled messages are not supported by this adapter",
|
|
@@ -1467,7 +1500,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1467
1500
|
/**
|
|
1468
1501
|
* Handle streaming from an AsyncIterable.
|
|
1469
1502
|
* Normalizes the stream (supports both textStream and fullStream from AI SDK),
|
|
1470
|
-
* then uses adapter's
|
|
1503
|
+
* then uses the adapter's stream implementation if available, otherwise falls back to post+edit.
|
|
1471
1504
|
*/
|
|
1472
1505
|
async handleStream(rawStream, callerOptions) {
|
|
1473
1506
|
const textStream = fromFullStream(rawStream);
|
|
@@ -1505,8 +1538,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1505
1538
|
{ markdown: accumulated },
|
|
1506
1539
|
raw.threadId
|
|
1507
1540
|
);
|
|
1508
|
-
if (this.
|
|
1509
|
-
await this.
|
|
1541
|
+
if (this._threadHistory) {
|
|
1542
|
+
await this._threadHistory.append(this.id, new Message(sent));
|
|
1510
1543
|
}
|
|
1511
1544
|
return sent;
|
|
1512
1545
|
}
|
|
@@ -1653,8 +1686,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1653
1686
|
{ markdown: accumulated },
|
|
1654
1687
|
threadIdForEdits
|
|
1655
1688
|
);
|
|
1656
|
-
if (this.
|
|
1657
|
-
await this.
|
|
1689
|
+
if (this._threadHistory) {
|
|
1690
|
+
await this._threadHistory.append(this.id, new Message(sent));
|
|
1658
1691
|
}
|
|
1659
1692
|
return sent;
|
|
1660
1693
|
}
|
|
@@ -1662,11 +1695,8 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1662
1695
|
const result = await this.adapter.fetchMessages(this.id, { limit: 50 });
|
|
1663
1696
|
if (result.messages.length > 0) {
|
|
1664
1697
|
this._recentMessages = result.messages;
|
|
1665
|
-
} else if (this.
|
|
1666
|
-
this._recentMessages = await this.
|
|
1667
|
-
this.id,
|
|
1668
|
-
50
|
|
1669
|
-
);
|
|
1698
|
+
} else if (this._threadHistory) {
|
|
1699
|
+
this._recentMessages = await this._threadHistory.getMessages(this.id, 50);
|
|
1670
1700
|
} else {
|
|
1671
1701
|
this._recentMessages = [];
|
|
1672
1702
|
}
|
|
@@ -1778,6 +1808,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1778
1808
|
}
|
|
1779
1809
|
postable2 = card;
|
|
1780
1810
|
}
|
|
1811
|
+
postable2 = await self.processCallbackUrls(postable2);
|
|
1781
1812
|
await adapter.editMessage(threadId, messageId, postable2);
|
|
1782
1813
|
return self.createSentMessage(messageId, postable2);
|
|
1783
1814
|
},
|
|
@@ -1821,6 +1852,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1821
1852
|
}
|
|
1822
1853
|
postable = card;
|
|
1823
1854
|
}
|
|
1855
|
+
postable = await self.processCallbackUrls(postable);
|
|
1824
1856
|
await adapter.editMessage(threadId, messageId, postable);
|
|
1825
1857
|
return self.createSentMessage(messageId, postable, threadId);
|
|
1826
1858
|
},
|
|
@@ -1902,6 +1934,177 @@ function reviver(_key, value) {
|
|
|
1902
1934
|
return value;
|
|
1903
1935
|
}
|
|
1904
1936
|
|
|
1937
|
+
// src/thread-history.ts
|
|
1938
|
+
var DEFAULT_MAX_MESSAGES = 100;
|
|
1939
|
+
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1940
|
+
var KEY_PREFIX = "msg-history:";
|
|
1941
|
+
var ThreadHistoryCache = class {
|
|
1942
|
+
state;
|
|
1943
|
+
maxMessages;
|
|
1944
|
+
ttlMs;
|
|
1945
|
+
constructor(state, config) {
|
|
1946
|
+
this.state = state;
|
|
1947
|
+
this.maxMessages = config?.maxMessages ?? DEFAULT_MAX_MESSAGES;
|
|
1948
|
+
this.ttlMs = config?.ttlMs ?? DEFAULT_TTL_MS;
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Atomically append a message to the history for a thread.
|
|
1952
|
+
* Trims to maxMessages (keeps newest) and refreshes TTL.
|
|
1953
|
+
*/
|
|
1954
|
+
async append(threadId, message) {
|
|
1955
|
+
const key = `${KEY_PREFIX}${threadId}`;
|
|
1956
|
+
const serialized = message.toJSON();
|
|
1957
|
+
serialized.raw = null;
|
|
1958
|
+
await this.state.appendToList(key, serialized, {
|
|
1959
|
+
maxLength: this.maxMessages,
|
|
1960
|
+
ttlMs: this.ttlMs
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Get messages for a thread in chronological order (oldest first).
|
|
1965
|
+
*
|
|
1966
|
+
* @param threadId - The thread ID
|
|
1967
|
+
* @param limit - Optional limit on number of messages to return (returns newest N)
|
|
1968
|
+
*/
|
|
1969
|
+
async getMessages(threadId, limit) {
|
|
1970
|
+
const key = `${KEY_PREFIX}${threadId}`;
|
|
1971
|
+
const stored = await this.state.getList(key);
|
|
1972
|
+
const sliced = limit && stored.length > limit ? stored.slice(stored.length - limit) : stored;
|
|
1973
|
+
return sliced.map((s) => Message.fromJSON(s));
|
|
1974
|
+
}
|
|
1975
|
+
};
|
|
1976
|
+
|
|
1977
|
+
// src/transcripts.ts
|
|
1978
|
+
var KEY_PREFIX2 = "transcripts:user:";
|
|
1979
|
+
var DEFAULT_MAX_PER_USER = 200;
|
|
1980
|
+
var DEFAULT_LIST_LIMIT = 50;
|
|
1981
|
+
var DURATION_RE = /^(\d+)([smhd])$/;
|
|
1982
|
+
var TOMBSTONE_MARKER = "__chatSdkTombstone";
|
|
1983
|
+
function isTombstone(value) {
|
|
1984
|
+
return typeof value === "object" && value !== null && value[TOMBSTONE_MARKER] === true;
|
|
1985
|
+
}
|
|
1986
|
+
var MS_PER_UNIT = {
|
|
1987
|
+
s: 1e3,
|
|
1988
|
+
m: 6e4,
|
|
1989
|
+
h: 36e5,
|
|
1990
|
+
d: 864e5
|
|
1991
|
+
};
|
|
1992
|
+
var TranscriptsApiImpl = class {
|
|
1993
|
+
state;
|
|
1994
|
+
maxPerUser;
|
|
1995
|
+
retentionMs;
|
|
1996
|
+
storeFormatted;
|
|
1997
|
+
constructor(state, config) {
|
|
1998
|
+
this.state = state;
|
|
1999
|
+
this.maxPerUser = config.maxPerUser ?? DEFAULT_MAX_PER_USER;
|
|
2000
|
+
this.retentionMs = parseDuration(config.retention);
|
|
2001
|
+
this.storeFormatted = config.storeFormatted ?? false;
|
|
2002
|
+
}
|
|
2003
|
+
async append(thread, message, options) {
|
|
2004
|
+
const isMessage = message instanceof Message;
|
|
2005
|
+
let userKey;
|
|
2006
|
+
let role;
|
|
2007
|
+
let platformMessageId;
|
|
2008
|
+
if (isMessage) {
|
|
2009
|
+
userKey = message.userKey;
|
|
2010
|
+
role = "user";
|
|
2011
|
+
platformMessageId = message.id;
|
|
2012
|
+
if (!userKey) {
|
|
2013
|
+
return null;
|
|
2014
|
+
}
|
|
2015
|
+
} else {
|
|
2016
|
+
userKey = options?.userKey;
|
|
2017
|
+
role = message.role;
|
|
2018
|
+
platformMessageId = message.platformMessageId;
|
|
2019
|
+
if (!userKey) {
|
|
2020
|
+
throw new Error(
|
|
2021
|
+
"transcripts.append: options.userKey is required when appending an AppendInput"
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
const entry = {
|
|
2026
|
+
id: crypto.randomUUID(),
|
|
2027
|
+
userKey,
|
|
2028
|
+
role,
|
|
2029
|
+
text: message.text,
|
|
2030
|
+
platform: thread.adapter.name,
|
|
2031
|
+
threadId: thread.id,
|
|
2032
|
+
timestamp: Date.now()
|
|
2033
|
+
};
|
|
2034
|
+
if (this.storeFormatted && message.formatted) {
|
|
2035
|
+
entry.formatted = message.formatted;
|
|
2036
|
+
}
|
|
2037
|
+
if (platformMessageId !== void 0) {
|
|
2038
|
+
entry.platformMessageId = platformMessageId;
|
|
2039
|
+
}
|
|
2040
|
+
await this.state.appendToList(keyFor(userKey), entry, {
|
|
2041
|
+
maxLength: this.maxPerUser,
|
|
2042
|
+
ttlMs: this.retentionMs
|
|
2043
|
+
});
|
|
2044
|
+
return entry;
|
|
2045
|
+
}
|
|
2046
|
+
async list(query) {
|
|
2047
|
+
const raw = await this.state.getList(
|
|
2048
|
+
keyFor(query.userKey)
|
|
2049
|
+
);
|
|
2050
|
+
let filtered = raw.filter(
|
|
2051
|
+
(entry) => !isTombstone(entry)
|
|
2052
|
+
);
|
|
2053
|
+
if (query.platforms && query.platforms.length > 0) {
|
|
2054
|
+
const platforms = new Set(query.platforms);
|
|
2055
|
+
filtered = filtered.filter((m) => platforms.has(m.platform));
|
|
2056
|
+
}
|
|
2057
|
+
if (query.threadId !== void 0) {
|
|
2058
|
+
const tid = query.threadId;
|
|
2059
|
+
filtered = filtered.filter((m) => m.threadId === tid);
|
|
2060
|
+
}
|
|
2061
|
+
if (query.roles && query.roles.length > 0) {
|
|
2062
|
+
const roles = new Set(query.roles);
|
|
2063
|
+
filtered = filtered.filter((m) => roles.has(m.role));
|
|
2064
|
+
}
|
|
2065
|
+
const limit = query.limit ?? DEFAULT_LIST_LIMIT;
|
|
2066
|
+
if (filtered.length > limit) {
|
|
2067
|
+
filtered = filtered.slice(filtered.length - limit);
|
|
2068
|
+
}
|
|
2069
|
+
return filtered;
|
|
2070
|
+
}
|
|
2071
|
+
async count(query) {
|
|
2072
|
+
const raw = await this.state.getList(keyFor(query.userKey));
|
|
2073
|
+
return raw.filter((entry) => !isTombstone(entry)).length;
|
|
2074
|
+
}
|
|
2075
|
+
async delete(target) {
|
|
2076
|
+
const key = keyFor(target.userKey);
|
|
2077
|
+
const existing = await this.state.getList(key);
|
|
2078
|
+
const previous = existing.filter((entry) => !isTombstone(entry)).length;
|
|
2079
|
+
const tombstone = { [TOMBSTONE_MARKER]: true };
|
|
2080
|
+
await this.state.appendToList(key, tombstone, {
|
|
2081
|
+
maxLength: 1,
|
|
2082
|
+
ttlMs: this.retentionMs
|
|
2083
|
+
});
|
|
2084
|
+
return { deleted: previous };
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
function keyFor(userKey) {
|
|
2088
|
+
return `${KEY_PREFIX2}${userKey}`;
|
|
2089
|
+
}
|
|
2090
|
+
function parseDuration(value) {
|
|
2091
|
+
if (value === void 0) {
|
|
2092
|
+
return void 0;
|
|
2093
|
+
}
|
|
2094
|
+
if (typeof value === "number") {
|
|
2095
|
+
return value;
|
|
2096
|
+
}
|
|
2097
|
+
const match = DURATION_RE.exec(value);
|
|
2098
|
+
if (!match) {
|
|
2099
|
+
throw new Error(
|
|
2100
|
+
`Invalid duration: ${value} (expected number of ms, or "<n>[smhd]")`
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
2103
|
+
const n = Number.parseInt(match[1], 10);
|
|
2104
|
+
const unit = match[2];
|
|
2105
|
+
return n * MS_PER_UNIT[unit];
|
|
2106
|
+
}
|
|
2107
|
+
|
|
1905
2108
|
// src/chat.ts
|
|
1906
2109
|
var DEFAULT_LOCK_TTL_MS = 3e4;
|
|
1907
2110
|
function sleep(ms) {
|
|
@@ -1931,6 +2134,21 @@ var Chat = class {
|
|
|
1931
2134
|
setChatSingleton(this);
|
|
1932
2135
|
return this;
|
|
1933
2136
|
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Cross-platform per-user transcript store.
|
|
2139
|
+
*
|
|
2140
|
+
* Available only when `transcripts` is configured on the Chat instance
|
|
2141
|
+
* (and an `identity` resolver is set). Throws on access otherwise so
|
|
2142
|
+
* callers fail loudly rather than silently no-op'ing.
|
|
2143
|
+
*/
|
|
2144
|
+
get transcripts() {
|
|
2145
|
+
if (!this._transcripts) {
|
|
2146
|
+
throw new Error(
|
|
2147
|
+
"chat.transcripts is not configured \u2014 pass `transcripts` and `identity` to ChatConfig to enable it"
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
return this._transcripts;
|
|
2151
|
+
}
|
|
1934
2152
|
/**
|
|
1935
2153
|
* Get the registered singleton Chat instance.
|
|
1936
2154
|
* Throws if no singleton has been registered.
|
|
@@ -1952,7 +2170,9 @@ var Chat = class {
|
|
|
1952
2170
|
_fallbackStreamingPlaceholderText;
|
|
1953
2171
|
_dedupeTtlMs;
|
|
1954
2172
|
_onLockConflict;
|
|
1955
|
-
|
|
2173
|
+
_threadHistory;
|
|
2174
|
+
_identity;
|
|
2175
|
+
_transcripts;
|
|
1956
2176
|
_concurrencyStrategy;
|
|
1957
2177
|
_concurrencyConfig;
|
|
1958
2178
|
_concurrentSlots = /* @__PURE__ */ new Map();
|
|
@@ -2035,10 +2255,24 @@ var Chat = class {
|
|
|
2035
2255
|
queueEntryTtlMs: 9e4
|
|
2036
2256
|
};
|
|
2037
2257
|
}
|
|
2038
|
-
this.
|
|
2258
|
+
this._threadHistory = new ThreadHistoryCache(
|
|
2039
2259
|
this._stateAdapter,
|
|
2040
|
-
config.messageHistory
|
|
2260
|
+
config.threadHistory ?? config.messageHistory
|
|
2041
2261
|
);
|
|
2262
|
+
if (config.transcripts) {
|
|
2263
|
+
if (!config.identity) {
|
|
2264
|
+
throw new Error(
|
|
2265
|
+
"ChatConfig.transcripts requires ChatConfig.identity to be set \u2014 the cross-platform user key must be resolvable"
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2268
|
+
this._identity = config.identity;
|
|
2269
|
+
this._transcripts = new TranscriptsApiImpl(
|
|
2270
|
+
this._stateAdapter,
|
|
2271
|
+
config.transcripts
|
|
2272
|
+
);
|
|
2273
|
+
} else {
|
|
2274
|
+
this._identity = config.identity;
|
|
2275
|
+
}
|
|
2042
2276
|
const webhooks = {};
|
|
2043
2277
|
for (const [name, adapter] of Object.entries(config.adapters)) {
|
|
2044
2278
|
this.adapters.set(name, adapter);
|
|
@@ -2159,9 +2393,13 @@ var Chat = class {
|
|
|
2159
2393
|
/**
|
|
2160
2394
|
* Register a handler for direct messages.
|
|
2161
2395
|
*
|
|
2162
|
-
* Called
|
|
2163
|
-
*
|
|
2164
|
-
* `onNewMention
|
|
2396
|
+
* Called for every message received in a DM thread when at least one
|
|
2397
|
+
* direct message handler is registered. Direct message handlers run before
|
|
2398
|
+
* `onSubscribedMessage`, `onNewMention`, and pattern handlers.
|
|
2399
|
+
*
|
|
2400
|
+
* If no `onDirectMessage` handlers are registered, DMs continue through
|
|
2401
|
+
* normal routing. Unsubscribed DMs fall through to `onNewMention` for
|
|
2402
|
+
* backward compatibility.
|
|
2165
2403
|
*
|
|
2166
2404
|
* @param handler - Handler called for DM messages
|
|
2167
2405
|
*
|
|
@@ -2357,12 +2595,14 @@ var Chat = class {
|
|
|
2357
2595
|
const task = (async () => {
|
|
2358
2596
|
const message = typeof messageOrFactory === "function" ? await messageOrFactory() : messageOrFactory;
|
|
2359
2597
|
await this.handleIncomingMessage(adapter, threadId, message);
|
|
2360
|
-
})()
|
|
2598
|
+
})();
|
|
2599
|
+
const tracked = task.catch((err) => {
|
|
2361
2600
|
this.logger.error("Message processing error", { error: err, threadId });
|
|
2362
2601
|
});
|
|
2363
2602
|
if (options?.waitUntil) {
|
|
2364
|
-
options.waitUntil(
|
|
2603
|
+
options.waitUntil(tracked);
|
|
2365
2604
|
}
|
|
2605
|
+
return task;
|
|
2366
2606
|
}
|
|
2367
2607
|
/**
|
|
2368
2608
|
* Process an incoming reaction event from an adapter.
|
|
@@ -2420,20 +2660,22 @@ var Chat = class {
|
|
|
2420
2660
|
}
|
|
2421
2661
|
}
|
|
2422
2662
|
}
|
|
2423
|
-
async processModalSubmit(event, contextId,
|
|
2424
|
-
const { relatedThread, relatedMessage, relatedChannel } = await this.retrieveModalContext(event.adapter.name, contextId);
|
|
2663
|
+
async processModalSubmit(event, contextId, options) {
|
|
2664
|
+
const { callbackUrl, relatedThread, relatedMessage, relatedChannel } = await this.retrieveModalContext(event.adapter.name, contextId);
|
|
2425
2665
|
const fullEvent = {
|
|
2426
2666
|
...event,
|
|
2427
2667
|
relatedThread,
|
|
2428
2668
|
relatedMessage,
|
|
2429
2669
|
relatedChannel
|
|
2430
2670
|
};
|
|
2671
|
+
let result;
|
|
2431
2672
|
for (const { callbackIds, handler } of this.modalSubmitHandlers) {
|
|
2432
2673
|
if (callbackIds.length === 0 || callbackIds.includes(event.callbackId)) {
|
|
2433
2674
|
try {
|
|
2434
2675
|
const response = await handler(fullEvent);
|
|
2435
2676
|
if (response) {
|
|
2436
|
-
|
|
2677
|
+
result = response;
|
|
2678
|
+
break;
|
|
2437
2679
|
}
|
|
2438
2680
|
} catch (err) {
|
|
2439
2681
|
this.logger.error("Modal submit handler error", {
|
|
@@ -2443,6 +2685,30 @@ var Chat = class {
|
|
|
2443
2685
|
}
|
|
2444
2686
|
}
|
|
2445
2687
|
}
|
|
2688
|
+
if (callbackUrl && result?.action !== "errors") {
|
|
2689
|
+
const task = postToCallbackUrl(callbackUrl, {
|
|
2690
|
+
type: "modal_submit",
|
|
2691
|
+
callbackId: event.callbackId,
|
|
2692
|
+
values: event.values,
|
|
2693
|
+
user: { id: event.user.userId, name: event.user.userName }
|
|
2694
|
+
}).then(({ error }) => {
|
|
2695
|
+
if (error) {
|
|
2696
|
+
this.logger.error("Modal callbackUrl POST failed", {
|
|
2697
|
+
callbackUrl,
|
|
2698
|
+
error
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
}).catch((error) => {
|
|
2702
|
+
this.logger.error("Modal callbackUrl POST failed", {
|
|
2703
|
+
callbackUrl,
|
|
2704
|
+
error
|
|
2705
|
+
});
|
|
2706
|
+
});
|
|
2707
|
+
if (options?.waitUntil) {
|
|
2708
|
+
options.waitUntil(task);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
return result;
|
|
2446
2712
|
}
|
|
2447
2713
|
processModalClose(event, contextId, options) {
|
|
2448
2714
|
const task = (async () => {
|
|
@@ -2594,7 +2860,8 @@ var Chat = class {
|
|
|
2594
2860
|
contextId,
|
|
2595
2861
|
void 0,
|
|
2596
2862
|
void 0,
|
|
2597
|
-
channel
|
|
2863
|
+
channel,
|
|
2864
|
+
modalElement.callbackUrl
|
|
2598
2865
|
);
|
|
2599
2866
|
if (options?.onOpenModal) {
|
|
2600
2867
|
return options.onOpenModal(modalElement, contextId);
|
|
@@ -2631,12 +2898,13 @@ var Chat = class {
|
|
|
2631
2898
|
* Store modal context server-side with a context ID.
|
|
2632
2899
|
* Called when opening a modal to preserve thread/message/channel for the submit handler.
|
|
2633
2900
|
*/
|
|
2634
|
-
async storeModalContext(adapterName, contextId, thread, message, channel) {
|
|
2901
|
+
async storeModalContext(adapterName, contextId, thread, message, channel, callbackUrl) {
|
|
2635
2902
|
const key = `modal-context:${adapterName}:${contextId}`;
|
|
2636
2903
|
const context = {
|
|
2637
2904
|
thread: thread?.toJSON(),
|
|
2638
2905
|
message: message?.toJSON(),
|
|
2639
|
-
channel: channel?.toJSON()
|
|
2906
|
+
channel: channel?.toJSON(),
|
|
2907
|
+
callbackUrl
|
|
2640
2908
|
};
|
|
2641
2909
|
try {
|
|
2642
2910
|
await this._stateAdapter.set(key, context, MODAL_CONTEXT_TTL_MS);
|
|
@@ -2654,6 +2922,7 @@ var Chat = class {
|
|
|
2654
2922
|
async retrieveModalContext(adapterName, contextId) {
|
|
2655
2923
|
if (!contextId) {
|
|
2656
2924
|
return {
|
|
2925
|
+
callbackUrl: void 0,
|
|
2657
2926
|
relatedThread: void 0,
|
|
2658
2927
|
relatedMessage: void 0,
|
|
2659
2928
|
relatedChannel: void 0
|
|
@@ -2663,6 +2932,7 @@ var Chat = class {
|
|
|
2663
2932
|
const stored = await this._stateAdapter.get(key);
|
|
2664
2933
|
if (!stored) {
|
|
2665
2934
|
return {
|
|
2935
|
+
callbackUrl: void 0,
|
|
2666
2936
|
relatedThread: void 0,
|
|
2667
2937
|
relatedMessage: void 0,
|
|
2668
2938
|
relatedChannel: void 0
|
|
@@ -2683,7 +2953,12 @@ var Chat = class {
|
|
|
2683
2953
|
if (stored.channel) {
|
|
2684
2954
|
relatedChannel = ChannelImpl.fromJSON(stored.channel, adapter);
|
|
2685
2955
|
}
|
|
2686
|
-
return {
|
|
2956
|
+
return {
|
|
2957
|
+
callbackUrl: stored.callbackUrl,
|
|
2958
|
+
relatedThread,
|
|
2959
|
+
relatedMessage,
|
|
2960
|
+
relatedChannel
|
|
2961
|
+
};
|
|
2687
2962
|
}
|
|
2688
2963
|
/**
|
|
2689
2964
|
* Handle an action event internally.
|
|
@@ -2703,6 +2978,33 @@ var Chat = class {
|
|
|
2703
2978
|
});
|
|
2704
2979
|
return;
|
|
2705
2980
|
}
|
|
2981
|
+
const { callbackToken } = decodeCallbackValue(event.value);
|
|
2982
|
+
let resolved = null;
|
|
2983
|
+
if (callbackToken) {
|
|
2984
|
+
resolved = await resolveCallbackUrl(callbackToken, this._stateAdapter);
|
|
2985
|
+
}
|
|
2986
|
+
const actionEvent = resolved ? { ...event, value: resolved.originalValue } : event;
|
|
2987
|
+
let callbackUrlPromise;
|
|
2988
|
+
if (resolved) {
|
|
2989
|
+
const callbackUrl = resolved.url;
|
|
2990
|
+
callbackUrlPromise = (async () => {
|
|
2991
|
+
const { error } = await postToCallbackUrl(callbackUrl, {
|
|
2992
|
+
type: "action",
|
|
2993
|
+
actionId: event.actionId,
|
|
2994
|
+
value: resolved.originalValue,
|
|
2995
|
+
user: { id: event.user.userId, name: event.user.userName },
|
|
2996
|
+
threadId: event.threadId,
|
|
2997
|
+
messageId: event.messageId
|
|
2998
|
+
});
|
|
2999
|
+
if (error) {
|
|
3000
|
+
this.logger.error("Button callbackUrl POST failed", {
|
|
3001
|
+
callbackUrl,
|
|
3002
|
+
actionId: event.actionId,
|
|
3003
|
+
error
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
})();
|
|
3007
|
+
}
|
|
2706
3008
|
const isSubscribed = false;
|
|
2707
3009
|
const messageForThread = event.messageId ? new Message({
|
|
2708
3010
|
id: event.messageId,
|
|
@@ -2721,7 +3023,7 @@ var Chat = class {
|
|
|
2721
3023
|
isSubscribed
|
|
2722
3024
|
) : null;
|
|
2723
3025
|
const fullEvent = {
|
|
2724
|
-
...
|
|
3026
|
+
...actionEvent,
|
|
2725
3027
|
thread,
|
|
2726
3028
|
openModal: async (modal) => {
|
|
2727
3029
|
if (!(event.triggerId || options?.onOpenModal)) {
|
|
@@ -2769,7 +3071,8 @@ var Chat = class {
|
|
|
2769
3071
|
contextId,
|
|
2770
3072
|
thread ? thread : void 0,
|
|
2771
3073
|
message,
|
|
2772
|
-
channel
|
|
3074
|
+
channel,
|
|
3075
|
+
modalElement.callbackUrl
|
|
2773
3076
|
);
|
|
2774
3077
|
if (options?.onOpenModal) {
|
|
2775
3078
|
return options.onOpenModal(modalElement, contextId);
|
|
@@ -2801,6 +3104,9 @@ var Chat = class {
|
|
|
2801
3104
|
await handler(fullEvent);
|
|
2802
3105
|
}
|
|
2803
3106
|
}
|
|
3107
|
+
if (callbackUrlPromise) {
|
|
3108
|
+
await callbackUrlPromise;
|
|
3109
|
+
}
|
|
2804
3110
|
}
|
|
2805
3111
|
/**
|
|
2806
3112
|
* Handle a reaction event internally.
|
|
@@ -3110,9 +3416,10 @@ var Chat = class {
|
|
|
3110
3416
|
* - Deduplication: Same message may arrive multiple times (e.g., Slack sends
|
|
3111
3417
|
* both `message` and `app_mention` events, GChat sends direct webhook + Pub/Sub)
|
|
3112
3418
|
* - Bot filtering: Messages from the bot itself are skipped
|
|
3113
|
-
* - Concurrency: Controlled by `concurrency` config (drop, queue, debounce, concurrent)
|
|
3419
|
+
* - Concurrency: Controlled by `concurrency` config (drop, queue, debounce, burst, concurrent)
|
|
3114
3420
|
*/
|
|
3115
3421
|
async handleIncomingMessage(adapter, threadId, message) {
|
|
3422
|
+
setMessageAdapter(message, adapter);
|
|
3116
3423
|
this.logger.debug("Incoming message", {
|
|
3117
3424
|
adapter: adapter.name,
|
|
3118
3425
|
threadId,
|
|
@@ -3144,11 +3451,11 @@ var Chat = class {
|
|
|
3144
3451
|
});
|
|
3145
3452
|
return;
|
|
3146
3453
|
}
|
|
3147
|
-
if (adapter.persistMessageHistory) {
|
|
3454
|
+
if (adapter.persistThreadHistory || adapter.persistMessageHistory) {
|
|
3148
3455
|
const channelId = adapter.channelIdFromThreadId(threadId);
|
|
3149
|
-
const appends = [this.
|
|
3456
|
+
const appends = [this._threadHistory.append(threadId, message)];
|
|
3150
3457
|
if (channelId !== threadId) {
|
|
3151
|
-
appends.push(this.
|
|
3458
|
+
appends.push(this._threadHistory.append(channelId, message));
|
|
3152
3459
|
}
|
|
3153
3460
|
await Promise.all(appends);
|
|
3154
3461
|
}
|
|
@@ -3158,7 +3465,7 @@ var Chat = class {
|
|
|
3158
3465
|
await this.handleConcurrent(adapter, threadId, message);
|
|
3159
3466
|
return;
|
|
3160
3467
|
}
|
|
3161
|
-
if (strategy === "queue" || strategy === "debounce") {
|
|
3468
|
+
if (strategy === "queue" || strategy === "debounce" || strategy === "burst") {
|
|
3162
3469
|
await this.handleQueueOrDebounce(
|
|
3163
3470
|
adapter,
|
|
3164
3471
|
threadId,
|
|
@@ -3277,6 +3584,25 @@ var Chat = class {
|
|
|
3277
3584
|
debounceMs
|
|
3278
3585
|
});
|
|
3279
3586
|
await this.debounceLoop(lock, adapter, threadId, lockKey);
|
|
3587
|
+
} else if (strategy === "burst") {
|
|
3588
|
+
await this._stateAdapter.enqueue(
|
|
3589
|
+
lockKey,
|
|
3590
|
+
{
|
|
3591
|
+
message,
|
|
3592
|
+
enqueuedAt: Date.now(),
|
|
3593
|
+
expiresAt: Date.now() + queueEntryTtlMs
|
|
3594
|
+
},
|
|
3595
|
+
maxQueueSize
|
|
3596
|
+
);
|
|
3597
|
+
this.logger.info("message-debouncing", {
|
|
3598
|
+
threadId,
|
|
3599
|
+
lockKey,
|
|
3600
|
+
messageId: message.id,
|
|
3601
|
+
debounceMs
|
|
3602
|
+
});
|
|
3603
|
+
await sleep(debounceMs);
|
|
3604
|
+
await this._stateAdapter.extendLock(lock, DEFAULT_LOCK_TTL_MS);
|
|
3605
|
+
await this.drainQueue(lock, adapter, threadId, lockKey);
|
|
3280
3606
|
} else {
|
|
3281
3607
|
await this.dispatchToHandlers(adapter, threadId, message);
|
|
3282
3608
|
await this.drainQueue(lock, adapter, threadId, lockKey);
|
|
@@ -3436,6 +3762,25 @@ var Chat = class {
|
|
|
3436
3762
|
message,
|
|
3437
3763
|
isSubscribed
|
|
3438
3764
|
);
|
|
3765
|
+
if (this._identity && message.userKey === void 0) {
|
|
3766
|
+
try {
|
|
3767
|
+
const resolved = await this._identity({
|
|
3768
|
+
adapter: adapter.name,
|
|
3769
|
+
author: message.author,
|
|
3770
|
+
message
|
|
3771
|
+
});
|
|
3772
|
+
if (resolved) {
|
|
3773
|
+
message.userKey = resolved;
|
|
3774
|
+
}
|
|
3775
|
+
} catch (err) {
|
|
3776
|
+
this.logger.warn("Identity resolver threw; skipping userKey", {
|
|
3777
|
+
error: err,
|
|
3778
|
+
adapter: adapter.name,
|
|
3779
|
+
threadId,
|
|
3780
|
+
authorUserId: message.author.userId
|
|
3781
|
+
});
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3439
3784
|
const isDM = adapter.isDM?.(threadId) ?? false;
|
|
3440
3785
|
if (isDM && this.directMessageHandlers.length > 0) {
|
|
3441
3786
|
this.logger.debug("Direct message received - calling handlers", {
|
|
@@ -3517,7 +3862,7 @@ var Chat = class {
|
|
|
3517
3862
|
logger: this.logger,
|
|
3518
3863
|
streamingUpdateIntervalMs: this._streamingUpdateIntervalMs,
|
|
3519
3864
|
fallbackStreamingPlaceholderText: this._fallbackStreamingPlaceholderText,
|
|
3520
|
-
|
|
3865
|
+
threadHistory: adapter.persistThreadHistory || adapter.persistMessageHistory ? this._threadHistory : void 0
|
|
3521
3866
|
});
|
|
3522
3867
|
}
|
|
3523
3868
|
/**
|
|
@@ -3563,6 +3908,9 @@ var Chat = class {
|
|
|
3563
3908
|
*/
|
|
3564
3909
|
rehydrateMessage(raw, adapter) {
|
|
3565
3910
|
if (raw instanceof Message) {
|
|
3911
|
+
if (adapter) {
|
|
3912
|
+
setMessageAdapter(raw, adapter);
|
|
3913
|
+
}
|
|
3566
3914
|
return raw;
|
|
3567
3915
|
}
|
|
3568
3916
|
const obj = raw;
|
|
@@ -3592,6 +3940,9 @@ var Chat = class {
|
|
|
3592
3940
|
links: obj.links ?? []
|
|
3593
3941
|
});
|
|
3594
3942
|
}
|
|
3943
|
+
if (adapter) {
|
|
3944
|
+
setMessageAdapter(msg, adapter);
|
|
3945
|
+
}
|
|
3595
3946
|
const rehydrate = adapter?.rehydrateAttachment?.bind(adapter);
|
|
3596
3947
|
if (rehydrate && msg.attachments.length > 0) {
|
|
3597
3948
|
msg.attachments = msg.attachments.map(
|
|
@@ -3607,6 +3958,9 @@ var Chat = class {
|
|
|
3607
3958
|
}
|
|
3608
3959
|
};
|
|
3609
3960
|
|
|
3961
|
+
// src/message-history.ts
|
|
3962
|
+
var MessageHistoryCache = ThreadHistoryCache;
|
|
3963
|
+
|
|
3610
3964
|
// src/plan.ts
|
|
3611
3965
|
function contentToPlainText(content) {
|
|
3612
3966
|
if (!content) {
|
|
@@ -4107,6 +4461,8 @@ function convertEmojiPlaceholders(text2, platform, resolver = defaultEmojiResolv
|
|
|
4107
4461
|
return resolver.toGChat(emojiName);
|
|
4108
4462
|
case "discord":
|
|
4109
4463
|
return resolver.toDiscord(emojiName);
|
|
4464
|
+
case "messenger":
|
|
4465
|
+
return resolver.toGChat(emojiName);
|
|
4110
4466
|
case "github":
|
|
4111
4467
|
return resolver.toGChat(emojiName);
|
|
4112
4468
|
case "linear":
|
|
@@ -4308,6 +4664,7 @@ export {
|
|
|
4308
4664
|
THREAD_STATE_TTL_MS,
|
|
4309
4665
|
Table2 as Table,
|
|
4310
4666
|
TextInput2 as TextInput,
|
|
4667
|
+
ThreadHistoryCache,
|
|
4311
4668
|
ThreadImpl,
|
|
4312
4669
|
blockquote,
|
|
4313
4670
|
cardChildToFallbackText2 as cardChildToFallbackText,
|