@mastra/core 0.10.1 → 0.10.2-alpha.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/agent/index.cjs +7 -2
- package/dist/agent/index.d.cts +9 -9
- package/dist/agent/index.d.ts +9 -9
- package/dist/agent/index.js +2 -1
- package/dist/{base-B96VvaWm.d.cts → base-BIRXTC4Q.d.cts} +1456 -1361
- package/dist/{base-QP4OC4dB.d.ts → base-BUTjc86H.d.ts} +1456 -1361
- package/dist/{chunk-XRF2JEZJ.cjs → chunk-2CK6E5UF.cjs} +24 -0
- package/dist/{chunk-STLR3SIQ.cjs → chunk-4I6LG5EO.cjs} +6 -92
- package/dist/{chunk-2YYOF7DO.js → chunk-5UFUPI3G.js} +5 -91
- package/dist/{chunk-MTIZRZZI.js → chunk-75EMTCUM.js} +24 -0
- package/dist/{chunk-VRACJIAE.js → chunk-A4RQDNP3.js} +2 -2
- package/dist/{chunk-RFERFI6B.js → chunk-D3237VN7.js} +183 -369
- package/dist/{chunk-U3L3NEOM.cjs → chunk-DXLVS7ML.cjs} +1 -19
- package/dist/{chunk-2PW6UJMW.js → chunk-EQTIDVPN.js} +2 -19
- package/dist/{chunk-SFZZYGKB.cjs → chunk-EZYCQBP2.cjs} +2 -2
- package/dist/{chunk-MAFHTHTJ.cjs → chunk-GXEDBJC5.cjs} +4 -4
- package/dist/{chunk-5JRD3NDP.cjs → chunk-I5OLBJ26.cjs} +6 -77
- package/dist/chunk-LCCQWUQP.js +962 -0
- package/dist/{chunk-RARKB3F2.js → chunk-NY4KJJWC.js} +1 -1
- package/dist/{chunk-SLKAWVQR.cjs → chunk-QUCBJ363.cjs} +192 -378
- package/dist/{chunk-SKG2NIZW.cjs → chunk-RK6DHZUA.cjs} +2 -2
- package/dist/{chunk-OCT2762Q.js → chunk-RMTG7VWR.js} +1 -1
- package/dist/chunk-XAO67QBR.cjs +964 -0
- package/dist/{chunk-BPTSLJHA.js → chunk-ZJYHZO6Y.js} +2 -73
- package/dist/eval/index.d.cts +8 -8
- package/dist/eval/index.d.ts +8 -8
- package/dist/index.cjs +41 -45
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +10 -10
- package/dist/integration/index.cjs +3 -3
- package/dist/integration/index.d.cts +9 -9
- package/dist/integration/index.d.ts +9 -9
- package/dist/integration/index.js +1 -1
- package/dist/llm/index.d.cts +8 -8
- package/dist/llm/index.d.ts +8 -8
- package/dist/mastra/index.cjs +2 -2
- package/dist/mastra/index.d.cts +6 -6
- package/dist/mastra/index.d.ts +6 -6
- package/dist/mastra/index.js +1 -1
- package/dist/mcp/index.d.cts +11 -11
- package/dist/mcp/index.d.ts +11 -11
- package/dist/memory/index.cjs +4 -4
- package/dist/memory/index.d.cts +9 -9
- package/dist/memory/index.d.ts +9 -9
- package/dist/memory/index.js +1 -1
- package/dist/network/index.cjs +4 -4
- package/dist/network/index.d.cts +9 -9
- package/dist/network/index.d.ts +9 -9
- package/dist/network/index.js +2 -2
- package/dist/relevance/index.cjs +4 -4
- package/dist/relevance/index.d.cts +11 -11
- package/dist/relevance/index.d.ts +11 -11
- package/dist/relevance/index.js +1 -1
- package/dist/server/index.d.cts +9 -9
- package/dist/server/index.d.ts +9 -9
- package/dist/storage/index.d.cts +18 -18
- package/dist/storage/index.d.ts +18 -18
- package/dist/telemetry/index.d.cts +9 -9
- package/dist/telemetry/index.d.ts +9 -9
- package/dist/tools/index.cjs +4 -4
- package/dist/tools/index.d.cts +9 -9
- package/dist/tools/index.d.ts +9 -9
- package/dist/tools/index.js +1 -1
- package/dist/utils.cjs +15 -19
- package/dist/utils.d.cts +10 -11
- package/dist/utils.d.ts +10 -11
- package/dist/utils.js +1 -1
- package/dist/voice/index.d.cts +9 -9
- package/dist/voice/index.d.ts +9 -9
- package/dist/workflows/index.cjs +9 -9
- package/dist/workflows/index.d.cts +9 -9
- package/dist/workflows/index.d.ts +9 -9
- package/dist/workflows/index.js +1 -1
- package/dist/workflows/legacy/index.cjs +22 -22
- package/dist/workflows/legacy/index.d.cts +9 -9
- package/dist/workflows/legacy/index.d.ts +9 -9
- package/dist/workflows/legacy/index.js +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,962 @@
|
|
|
1
|
+
import { isUiMessage, isCoreMessage } from './chunk-EQTIDVPN.js';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { convertToCoreMessages } from 'ai';
|
|
4
|
+
import { convertUint8ArrayToBase64, convertBase64ToUint8Array } from '@ai-sdk/provider-utils';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
z.union([
|
|
8
|
+
z.string(),
|
|
9
|
+
z.instanceof(Uint8Array),
|
|
10
|
+
z.instanceof(ArrayBuffer),
|
|
11
|
+
z.custom(
|
|
12
|
+
// Buffer might not be available in some environments such as CloudFlare:
|
|
13
|
+
(value) => globalThis.Buffer?.isBuffer(value) ?? false,
|
|
14
|
+
{ message: "Must be a Buffer" }
|
|
15
|
+
)
|
|
16
|
+
]);
|
|
17
|
+
function convertDataContentToBase64String(content) {
|
|
18
|
+
if (typeof content === "string") {
|
|
19
|
+
return content;
|
|
20
|
+
}
|
|
21
|
+
if (content instanceof ArrayBuffer) {
|
|
22
|
+
return convertUint8ArrayToBase64(new Uint8Array(content));
|
|
23
|
+
}
|
|
24
|
+
return convertUint8ArrayToBase64(content);
|
|
25
|
+
}
|
|
26
|
+
function convertDataContentToUint8Array(content) {
|
|
27
|
+
if (content instanceof Uint8Array) {
|
|
28
|
+
return content;
|
|
29
|
+
}
|
|
30
|
+
if (typeof content === "string") {
|
|
31
|
+
try {
|
|
32
|
+
return convertBase64ToUint8Array(content);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error("Invalid data content. Content string is not a base64-encoded media.", {
|
|
35
|
+
cause: error
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (content instanceof ArrayBuffer) {
|
|
40
|
+
return new Uint8Array(content);
|
|
41
|
+
}
|
|
42
|
+
throw new Error(content);
|
|
43
|
+
}
|
|
44
|
+
function convertUint8ArrayToText(uint8Array) {
|
|
45
|
+
try {
|
|
46
|
+
return new TextDecoder().decode(uint8Array);
|
|
47
|
+
} catch {
|
|
48
|
+
throw new Error("Error decoding Uint8Array to text");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/agent/message-list/prompt/attachments-to-parts.ts
|
|
53
|
+
function attachmentsToParts(attachments) {
|
|
54
|
+
const parts = [];
|
|
55
|
+
for (const attachment of attachments) {
|
|
56
|
+
let url;
|
|
57
|
+
try {
|
|
58
|
+
url = new URL(attachment.url);
|
|
59
|
+
} catch {
|
|
60
|
+
throw new Error(`Invalid URL: ${attachment.url}`);
|
|
61
|
+
}
|
|
62
|
+
switch (url.protocol) {
|
|
63
|
+
case "http:":
|
|
64
|
+
case "https:": {
|
|
65
|
+
if (attachment.contentType?.startsWith("image/")) {
|
|
66
|
+
parts.push({ type: "image", image: url });
|
|
67
|
+
} else {
|
|
68
|
+
if (!attachment.contentType) {
|
|
69
|
+
throw new Error("If the attachment is not an image, it must specify a content type");
|
|
70
|
+
}
|
|
71
|
+
parts.push({
|
|
72
|
+
type: "file",
|
|
73
|
+
data: url,
|
|
74
|
+
mimeType: attachment.contentType
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case "data:": {
|
|
80
|
+
let header;
|
|
81
|
+
let base64Content;
|
|
82
|
+
let mimeType;
|
|
83
|
+
try {
|
|
84
|
+
[header, base64Content] = attachment.url.split(",");
|
|
85
|
+
mimeType = header?.split?.(";")?.[0]?.split(":")[1];
|
|
86
|
+
} catch {
|
|
87
|
+
throw new Error(`Error processing data URL: ${attachment.url}`);
|
|
88
|
+
}
|
|
89
|
+
if (mimeType == null || base64Content == null) {
|
|
90
|
+
throw new Error(`Invalid data URL format: ${attachment.url}`);
|
|
91
|
+
}
|
|
92
|
+
if (attachment.contentType?.startsWith("image/")) {
|
|
93
|
+
parts.push({
|
|
94
|
+
type: "image",
|
|
95
|
+
image: convertDataContentToUint8Array(base64Content)
|
|
96
|
+
});
|
|
97
|
+
} else if (attachment.contentType?.startsWith("text/")) {
|
|
98
|
+
parts.push({
|
|
99
|
+
type: "text",
|
|
100
|
+
text: convertUint8ArrayToText(convertDataContentToUint8Array(base64Content))
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
if (!attachment.contentType) {
|
|
104
|
+
throw new Error("If the attachment is not an image or text, it must specify a content type");
|
|
105
|
+
}
|
|
106
|
+
parts.push({
|
|
107
|
+
type: "file",
|
|
108
|
+
data: base64Content,
|
|
109
|
+
mimeType: attachment.contentType
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
default: {
|
|
115
|
+
throw new Error(`Unsupported URL protocol: ${url.protocol}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return parts;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/agent/message-list/prompt/convert-to-mastra-v1.ts
|
|
123
|
+
var makePushOrCombine = (v1Messages) => (msg) => {
|
|
124
|
+
const previousMessage = v1Messages.at(-1);
|
|
125
|
+
if (msg.role === previousMessage?.role && Array.isArray(previousMessage.content) && Array.isArray(msg.content) && // we were creating new messages for tool calls before and not appending to the assistant message
|
|
126
|
+
// so don't append here so everything works as before
|
|
127
|
+
(msg.role !== `assistant` || msg.role === `assistant` && msg.content.at(-1)?.type !== `tool-call`)) {
|
|
128
|
+
for (const part of msg.content) {
|
|
129
|
+
previousMessage.content.push(part);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
v1Messages.push(msg);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
function convertToV1Messages(messages) {
|
|
136
|
+
const v1Messages = [];
|
|
137
|
+
const pushOrCombine = makePushOrCombine(v1Messages);
|
|
138
|
+
for (let i = 0; i < messages.length; i++) {
|
|
139
|
+
const message = messages[i];
|
|
140
|
+
const isLastMessage = i === messages.length - 1;
|
|
141
|
+
if (!message?.content) continue;
|
|
142
|
+
const { content, experimental_attachments, parts } = message.content;
|
|
143
|
+
const { role } = message;
|
|
144
|
+
const fields = {
|
|
145
|
+
id: message.id,
|
|
146
|
+
createdAt: message.createdAt,
|
|
147
|
+
resourceId: message.resourceId,
|
|
148
|
+
threadId: message.threadId
|
|
149
|
+
};
|
|
150
|
+
switch (role) {
|
|
151
|
+
case "user": {
|
|
152
|
+
if (parts == null) {
|
|
153
|
+
const userContent = experimental_attachments ? [{ type: "text", text: content || "" }, ...attachmentsToParts(experimental_attachments)] : { type: "text", text: content || "" };
|
|
154
|
+
pushOrCombine({
|
|
155
|
+
role: "user",
|
|
156
|
+
...fields,
|
|
157
|
+
type: "text",
|
|
158
|
+
// @ts-ignore
|
|
159
|
+
content: userContent
|
|
160
|
+
// Array.isArray(userContent) && userContent.length === 1 && userContent[0]?.type === `text`
|
|
161
|
+
// ? userContent[0].text
|
|
162
|
+
// : userContent,
|
|
163
|
+
});
|
|
164
|
+
throw new Error(`will we ever hit this code?`);
|
|
165
|
+
} else {
|
|
166
|
+
const textParts = message.content.parts.filter((part) => part.type === "text").map((part) => ({
|
|
167
|
+
type: "text",
|
|
168
|
+
text: part.text
|
|
169
|
+
}));
|
|
170
|
+
const userContent = experimental_attachments ? [...textParts, ...attachmentsToParts(experimental_attachments)] : textParts;
|
|
171
|
+
pushOrCombine({
|
|
172
|
+
role: "user",
|
|
173
|
+
...fields,
|
|
174
|
+
type: "text",
|
|
175
|
+
// content: userContent,
|
|
176
|
+
// @ts-ignore
|
|
177
|
+
content: Array.isArray(userContent) && userContent.length === 1 && userContent[0]?.type === `text` && typeof content !== `undefined` ? content : userContent
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case "assistant": {
|
|
183
|
+
if (message.content.parts != null) {
|
|
184
|
+
let processBlock2 = function() {
|
|
185
|
+
const content2 = [];
|
|
186
|
+
for (const part of block) {
|
|
187
|
+
switch (part.type) {
|
|
188
|
+
case "file":
|
|
189
|
+
case "text": {
|
|
190
|
+
content2.push(part);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case "reasoning": {
|
|
194
|
+
for (const detail of part.details) {
|
|
195
|
+
switch (detail.type) {
|
|
196
|
+
case "text":
|
|
197
|
+
content2.push({
|
|
198
|
+
type: "reasoning",
|
|
199
|
+
text: detail.text,
|
|
200
|
+
signature: detail.signature
|
|
201
|
+
});
|
|
202
|
+
break;
|
|
203
|
+
case "redacted":
|
|
204
|
+
content2.push({
|
|
205
|
+
type: "redacted-reasoning",
|
|
206
|
+
data: detail.data
|
|
207
|
+
});
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case "tool-invocation":
|
|
214
|
+
content2.push({
|
|
215
|
+
type: "tool-call",
|
|
216
|
+
toolCallId: part.toolInvocation.toolCallId,
|
|
217
|
+
toolName: part.toolInvocation.toolName,
|
|
218
|
+
args: part.toolInvocation.args
|
|
219
|
+
});
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
pushOrCombine({
|
|
224
|
+
role: "assistant",
|
|
225
|
+
...fields,
|
|
226
|
+
type: content2.some((c) => c.type === `tool-call`) ? "tool-call" : "text",
|
|
227
|
+
// content: content,
|
|
228
|
+
content: typeof content2 !== `string` && Array.isArray(content2) && content2.length === 1 && content2[0]?.type === `text` ? message?.content?.content || content2 : content2
|
|
229
|
+
});
|
|
230
|
+
const stepInvocations = block.filter((part) => `type` in part && part.type === "tool-invocation").map((part) => part.toolInvocation);
|
|
231
|
+
if (stepInvocations.length > 0) {
|
|
232
|
+
pushOrCombine({
|
|
233
|
+
role: "tool",
|
|
234
|
+
...fields,
|
|
235
|
+
type: "tool-result",
|
|
236
|
+
// @ts-ignore
|
|
237
|
+
content: stepInvocations.map((toolInvocation) => {
|
|
238
|
+
const { toolCallId, toolName } = toolInvocation;
|
|
239
|
+
return {
|
|
240
|
+
type: "tool-result",
|
|
241
|
+
toolCallId,
|
|
242
|
+
toolName,
|
|
243
|
+
// @ts-ignore
|
|
244
|
+
result: toolInvocation.result
|
|
245
|
+
};
|
|
246
|
+
})
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
block = [];
|
|
250
|
+
blockHasToolInvocations = false;
|
|
251
|
+
currentStep++;
|
|
252
|
+
};
|
|
253
|
+
let currentStep = 0;
|
|
254
|
+
let blockHasToolInvocations = false;
|
|
255
|
+
let block = [];
|
|
256
|
+
for (const part of message.content.parts) {
|
|
257
|
+
switch (part.type) {
|
|
258
|
+
case "text": {
|
|
259
|
+
if (blockHasToolInvocations) {
|
|
260
|
+
processBlock2();
|
|
261
|
+
}
|
|
262
|
+
block.push(part);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case "file":
|
|
266
|
+
case "reasoning": {
|
|
267
|
+
block.push(part);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case "tool-invocation": {
|
|
271
|
+
if ((part.toolInvocation.step ?? 0) !== currentStep) {
|
|
272
|
+
processBlock2();
|
|
273
|
+
}
|
|
274
|
+
block.push(part);
|
|
275
|
+
blockHasToolInvocations = true;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
processBlock2();
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
const toolInvocations = message.content.toolInvocations;
|
|
284
|
+
if (toolInvocations == null || toolInvocations.length === 0) {
|
|
285
|
+
pushOrCombine({ role: "assistant", ...fields, content: content || "", type: "text" });
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
const maxStep = toolInvocations.reduce((max, toolInvocation) => {
|
|
289
|
+
return Math.max(max, toolInvocation.step ?? 0);
|
|
290
|
+
}, 0);
|
|
291
|
+
for (let i2 = 0; i2 <= maxStep; i2++) {
|
|
292
|
+
const stepInvocations = toolInvocations.filter((toolInvocation) => (toolInvocation.step ?? 0) === i2);
|
|
293
|
+
if (stepInvocations.length === 0) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
pushOrCombine({
|
|
297
|
+
role: "assistant",
|
|
298
|
+
...fields,
|
|
299
|
+
type: "tool-call",
|
|
300
|
+
content: [
|
|
301
|
+
...isLastMessage && content && i2 === 0 ? [{ type: "text", text: content }] : [],
|
|
302
|
+
...stepInvocations.map(({ toolCallId, toolName, args }) => ({
|
|
303
|
+
type: "tool-call",
|
|
304
|
+
toolCallId,
|
|
305
|
+
toolName,
|
|
306
|
+
args
|
|
307
|
+
}))
|
|
308
|
+
]
|
|
309
|
+
});
|
|
310
|
+
pushOrCombine({
|
|
311
|
+
role: "tool",
|
|
312
|
+
...fields,
|
|
313
|
+
type: "tool-result",
|
|
314
|
+
content: stepInvocations.map((toolInvocation) => {
|
|
315
|
+
if (!("result" in toolInvocation)) {
|
|
316
|
+
return toolInvocation;
|
|
317
|
+
}
|
|
318
|
+
const { toolCallId, toolName, result } = toolInvocation;
|
|
319
|
+
return {
|
|
320
|
+
type: "tool-result",
|
|
321
|
+
toolCallId,
|
|
322
|
+
toolName,
|
|
323
|
+
result
|
|
324
|
+
};
|
|
325
|
+
})
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (content && !isLastMessage) {
|
|
329
|
+
pushOrCombine({ role: "assistant", ...fields, type: "text", content: content || "" });
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return v1Messages;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/agent/message-list/index.ts
|
|
339
|
+
var MessageList = class _MessageList {
|
|
340
|
+
messages = [];
|
|
341
|
+
// passed in by dev in input or context
|
|
342
|
+
systemMessages = [];
|
|
343
|
+
// passed in by us for a specific purpose, eg memory system message
|
|
344
|
+
taggedSystemMessages = {};
|
|
345
|
+
memoryInfo = null;
|
|
346
|
+
// used to filter this.messages by how it was added: input/response/memory
|
|
347
|
+
memoryMessages = /* @__PURE__ */ new Set();
|
|
348
|
+
newMessages = /* @__PURE__ */ new Set();
|
|
349
|
+
responseMessages = /* @__PURE__ */ new Set();
|
|
350
|
+
generateMessageId;
|
|
351
|
+
constructor({
|
|
352
|
+
threadId,
|
|
353
|
+
resourceId,
|
|
354
|
+
generateMessageId
|
|
355
|
+
} = {}) {
|
|
356
|
+
if (threadId) {
|
|
357
|
+
this.memoryInfo = { threadId, resourceId };
|
|
358
|
+
this.generateMessageId = generateMessageId;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
add(messages, messageSource) {
|
|
362
|
+
for (const message of Array.isArray(messages) ? messages : [messages]) {
|
|
363
|
+
this.addOne(
|
|
364
|
+
typeof message === `string` ? {
|
|
365
|
+
role: "user",
|
|
366
|
+
content: message
|
|
367
|
+
} : message,
|
|
368
|
+
messageSource
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
return this;
|
|
372
|
+
}
|
|
373
|
+
getLatestUserContent() {
|
|
374
|
+
const currentUserMessages = this.all.core().filter((m) => m.role === "user");
|
|
375
|
+
const content = currentUserMessages.at(-1)?.content;
|
|
376
|
+
if (!content) return null;
|
|
377
|
+
return _MessageList.coreContentToString(content);
|
|
378
|
+
}
|
|
379
|
+
get get() {
|
|
380
|
+
return {
|
|
381
|
+
all: this.all,
|
|
382
|
+
remembered: this.remembered,
|
|
383
|
+
input: this.input,
|
|
384
|
+
response: this.response
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
all = {
|
|
388
|
+
mastra: () => this.messages,
|
|
389
|
+
v1: () => convertToV1Messages(this.messages),
|
|
390
|
+
ui: () => this.messages.map(_MessageList.toUIMessage),
|
|
391
|
+
core: () => this.convertToCoreMessages(this.all.ui()),
|
|
392
|
+
prompt: () => {
|
|
393
|
+
return [...this.systemMessages, ...Object.values(this.taggedSystemMessages).flat(), ...this.all.core()];
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
remembered = {
|
|
397
|
+
mastra: () => this.messages.filter((m) => this.memoryMessages.has(m)),
|
|
398
|
+
v1: () => convertToV1Messages(this.remembered.mastra()),
|
|
399
|
+
ui: () => this.remembered.mastra().map(_MessageList.toUIMessage),
|
|
400
|
+
core: () => this.convertToCoreMessages(this.remembered.ui())
|
|
401
|
+
};
|
|
402
|
+
input = {
|
|
403
|
+
mastra: () => this.messages.filter((m) => this.newMessages.has(m)),
|
|
404
|
+
v1: () => convertToV1Messages(this.input.mastra()),
|
|
405
|
+
ui: () => this.input.mastra().map(_MessageList.toUIMessage),
|
|
406
|
+
core: () => this.convertToCoreMessages(this.input.ui())
|
|
407
|
+
};
|
|
408
|
+
response = {
|
|
409
|
+
mastra: () => this.messages.filter((m) => this.responseMessages.has(m))
|
|
410
|
+
};
|
|
411
|
+
drainUnsavedMessages() {
|
|
412
|
+
const messages = this.messages.filter((m) => this.newMessages.has(m) || this.responseMessages.has(m));
|
|
413
|
+
this.newMessages.clear();
|
|
414
|
+
this.responseMessages.clear();
|
|
415
|
+
return messages;
|
|
416
|
+
}
|
|
417
|
+
getSystemMessages(tag) {
|
|
418
|
+
if (tag) {
|
|
419
|
+
return this.taggedSystemMessages[tag] || [];
|
|
420
|
+
}
|
|
421
|
+
return this.systemMessages;
|
|
422
|
+
}
|
|
423
|
+
addSystem(messages, tag) {
|
|
424
|
+
if (!messages) return this;
|
|
425
|
+
for (const message of Array.isArray(messages) ? messages : [messages]) {
|
|
426
|
+
this.addOneSystem(message, tag);
|
|
427
|
+
}
|
|
428
|
+
return this;
|
|
429
|
+
}
|
|
430
|
+
convertToCoreMessages(messages) {
|
|
431
|
+
return convertToCoreMessages(this.sanitizeUIMessages(messages));
|
|
432
|
+
}
|
|
433
|
+
sanitizeUIMessages(messages) {
|
|
434
|
+
const msgs = messages.map((m) => {
|
|
435
|
+
if (m.parts.length === 0) return false;
|
|
436
|
+
const safeParts = m.parts.filter(
|
|
437
|
+
(p) => p.type !== `tool-invocation` || // calls and partial-calls should be updated to be results at this point
|
|
438
|
+
// if they haven't we can't send them back to the llm and need to remove them.
|
|
439
|
+
p.toolInvocation.state !== `call` && p.toolInvocation.state !== `partial-call`
|
|
440
|
+
);
|
|
441
|
+
if (!safeParts.length) return false;
|
|
442
|
+
const sanitized = {
|
|
443
|
+
...m,
|
|
444
|
+
parts: safeParts
|
|
445
|
+
};
|
|
446
|
+
if (`toolInvocations` in m && m.toolInvocations) {
|
|
447
|
+
sanitized.toolInvocations = m.toolInvocations.filter((t) => t.state === `result`);
|
|
448
|
+
}
|
|
449
|
+
return sanitized;
|
|
450
|
+
}).filter((m) => Boolean(m));
|
|
451
|
+
return msgs;
|
|
452
|
+
}
|
|
453
|
+
addOneSystem(message, tag) {
|
|
454
|
+
if (typeof message === `string`) message = { role: "system", content: message };
|
|
455
|
+
if (tag && !this.isDuplicateSystem(message, tag)) {
|
|
456
|
+
this.taggedSystemMessages[tag] ||= [];
|
|
457
|
+
this.taggedSystemMessages[tag].push(message);
|
|
458
|
+
} else if (!this.isDuplicateSystem(message)) {
|
|
459
|
+
this.systemMessages.push(message);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
isDuplicateSystem(message, tag) {
|
|
463
|
+
if (tag) {
|
|
464
|
+
if (!this.taggedSystemMessages[tag]) return false;
|
|
465
|
+
return this.taggedSystemMessages[tag].some(
|
|
466
|
+
(m) => _MessageList.cacheKeyFromContent(m.content) === _MessageList.cacheKeyFromContent(message.content)
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
return this.systemMessages.some(
|
|
470
|
+
(m) => _MessageList.cacheKeyFromContent(m.content) === _MessageList.cacheKeyFromContent(message.content)
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
static toUIMessage(m) {
|
|
474
|
+
const contentString = typeof m.content.content === `string` && m.content.content !== "" ? m.content.content : m.content.parts.reduce((prev, part) => {
|
|
475
|
+
if (part.type === `text`) {
|
|
476
|
+
return part.text;
|
|
477
|
+
}
|
|
478
|
+
return prev;
|
|
479
|
+
}, "");
|
|
480
|
+
if (m.role === `user`) {
|
|
481
|
+
return {
|
|
482
|
+
id: m.id,
|
|
483
|
+
role: m.role,
|
|
484
|
+
content: m.content.content || contentString,
|
|
485
|
+
createdAt: m.createdAt,
|
|
486
|
+
parts: m.content.parts,
|
|
487
|
+
experimental_attachments: m.content.experimental_attachments || []
|
|
488
|
+
};
|
|
489
|
+
} else if (m.role === `assistant`) {
|
|
490
|
+
return {
|
|
491
|
+
id: m.id,
|
|
492
|
+
role: m.role,
|
|
493
|
+
content: m.content.content || contentString,
|
|
494
|
+
createdAt: m.createdAt,
|
|
495
|
+
parts: m.content.parts,
|
|
496
|
+
reasoning: void 0,
|
|
497
|
+
toolInvocations: `toolInvocations` in m.content ? m.content.toolInvocations : void 0
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
id: m.id,
|
|
502
|
+
role: m.role,
|
|
503
|
+
content: m.content.content || contentString,
|
|
504
|
+
createdAt: m.createdAt,
|
|
505
|
+
parts: m.content.parts
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
getMessageById(id) {
|
|
509
|
+
return this.messages.find((m) => m.id === id);
|
|
510
|
+
}
|
|
511
|
+
shouldReplaceMessage(message) {
|
|
512
|
+
if (!this.messages.length) return { exists: false };
|
|
513
|
+
if (!(`id` in message) || !message?.id) {
|
|
514
|
+
return { exists: false };
|
|
515
|
+
}
|
|
516
|
+
const existingMessage = this.getMessageById(message.id);
|
|
517
|
+
if (!existingMessage) return { exists: false };
|
|
518
|
+
return {
|
|
519
|
+
exists: true,
|
|
520
|
+
shouldReplace: !_MessageList.messagesAreEqual(existingMessage, message),
|
|
521
|
+
id: existingMessage.id
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
addOne(message, messageSource) {
|
|
525
|
+
if (message.role === `system` && _MessageList.isVercelCoreMessage(message)) return this.addSystem(message);
|
|
526
|
+
if (message.role === `system`) {
|
|
527
|
+
throw new Error(
|
|
528
|
+
`A non-CoreMessage system message was added - this is not supported as we didn't expect this could happen. Please open a Github issue and let us know what you did to get here. This is the non-CoreMessage system message we received:
|
|
529
|
+
|
|
530
|
+
messageSource: ${messageSource}
|
|
531
|
+
|
|
532
|
+
${JSON.stringify(message, null, 2)}`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
const messageV2 = this.inputToMastraMessageV2(message, messageSource);
|
|
536
|
+
const { exists, shouldReplace, id } = this.shouldReplaceMessage(messageV2);
|
|
537
|
+
const latestMessage = this.messages.at(-1);
|
|
538
|
+
const singleToolResult = messageV2.role === `assistant` && messageV2.content.parts.length === 1 && messageV2.content.parts[0]?.type === `tool-invocation` && messageV2.content.parts[0].toolInvocation.state === `result` && messageV2.content.parts[0];
|
|
539
|
+
if (singleToolResult && (latestMessage?.role !== `assistant` || !latestMessage.content.parts.some(
|
|
540
|
+
(p) => p.type === `tool-invocation` && p.toolInvocation.toolCallId === singleToolResult.toolInvocation.toolCallId
|
|
541
|
+
))) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (messageSource === `memory`) {
|
|
545
|
+
for (const existingMessage of this.messages) {
|
|
546
|
+
if (_MessageList.messagesAreEqual(existingMessage, messageV2)) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const latestMessagePartType = latestMessage?.content?.parts?.filter((p) => p.type !== `step-start`)?.at?.(-1)?.type;
|
|
552
|
+
const newMessageFirstPartType = messageV2.content.parts.filter((p) => p.type !== `step-start`).at(0)?.type;
|
|
553
|
+
const shouldAppendToLastAssistantMessage = latestMessage?.role === "assistant" && messageV2.role === "assistant";
|
|
554
|
+
const shouldAppendToLastAssistantMessageParts = shouldAppendToLastAssistantMessage && newMessageFirstPartType && (newMessageFirstPartType === `tool-invocation` && latestMessagePartType !== `text` || newMessageFirstPartType === latestMessagePartType);
|
|
555
|
+
if (
|
|
556
|
+
// backwards compat check!
|
|
557
|
+
// this condition can technically be removed and it will make it so all new assistant parts will be added to the last assistant message parts instead of creating new db entries.
|
|
558
|
+
// however, for any downstream code that isn't based around using message parts yet, this may cause tool invocations to show up in the wrong order in their UI, because they use the message.toolInvocations and message.content properties which do not indicate how each is ordered in relation to each other.
|
|
559
|
+
// this code check then causes any tool invocation to be created as a new message and not update the previous assistant message parts.
|
|
560
|
+
// without this condition we will see something like
|
|
561
|
+
// parts: [{type:"step-start"}, {type: "text", text: "let me check the weather"}, {type: "tool-invocation", toolInvocation: x}, {type: "text", text: "the weather in x is y"}]
|
|
562
|
+
// with this condition we will see
|
|
563
|
+
// message1.parts: [{type:"step-start"}, {type: "text", text: "let me check the weather"}]
|
|
564
|
+
// message2.parts: [{type: "tool-invocation", toolInvocation: x}]
|
|
565
|
+
// message3.parts: [{type: "text", text: "the weather in x is y"}]
|
|
566
|
+
shouldAppendToLastAssistantMessageParts
|
|
567
|
+
) {
|
|
568
|
+
latestMessage.createdAt = messageV2.createdAt || latestMessage.createdAt;
|
|
569
|
+
for (const [index, part] of messageV2.content.parts.entries()) {
|
|
570
|
+
if (part.type === "tool-invocation" && part.toolInvocation.state === "result") {
|
|
571
|
+
const existingCallPart = [...latestMessage.content.parts].reverse().find((p) => p.type === "tool-invocation" && p.toolInvocation.toolCallId === part.toolInvocation.toolCallId);
|
|
572
|
+
if (existingCallPart && existingCallPart.type === "tool-invocation") {
|
|
573
|
+
existingCallPart.toolInvocation = {
|
|
574
|
+
...existingCallPart.toolInvocation,
|
|
575
|
+
state: "result",
|
|
576
|
+
result: part.toolInvocation.result
|
|
577
|
+
};
|
|
578
|
+
if (!latestMessage.content.toolInvocations) {
|
|
579
|
+
latestMessage.content.toolInvocations = [];
|
|
580
|
+
}
|
|
581
|
+
if (!latestMessage.content.toolInvocations.some(
|
|
582
|
+
(t) => t.toolCallId === existingCallPart.toolInvocation.toolCallId
|
|
583
|
+
)) {
|
|
584
|
+
latestMessage.content.toolInvocations.push(existingCallPart.toolInvocation);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
} else if (
|
|
588
|
+
// if there's no part at this index yet in the existing message we're merging into
|
|
589
|
+
!latestMessage.content.parts[index] || // or there is and the parts are not identical
|
|
590
|
+
_MessageList.cacheKeyFromParts([latestMessage.content.parts[index]]) !== _MessageList.cacheKeyFromParts([part])
|
|
591
|
+
) {
|
|
592
|
+
latestMessage.content.parts.push(part);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (latestMessage.createdAt.getTime() < messageV2.createdAt.getTime()) {
|
|
596
|
+
latestMessage.createdAt = messageV2.createdAt;
|
|
597
|
+
}
|
|
598
|
+
if (!latestMessage.content.content && messageV2.content.content) {
|
|
599
|
+
latestMessage.content.content = messageV2.content.content;
|
|
600
|
+
}
|
|
601
|
+
if (latestMessage.content.content && messageV2.content.content && latestMessage.content.content !== messageV2.content.content) {
|
|
602
|
+
latestMessage.content.content = messageV2.content.content;
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
if (messageV2.role === "assistant" && messageV2.content.parts[0]?.type !== `step-start`) {
|
|
606
|
+
messageV2.content.parts.unshift({ type: "step-start" });
|
|
607
|
+
}
|
|
608
|
+
const existingIndex = shouldReplace && this.messages.findIndex((m) => m.id === id) || -1;
|
|
609
|
+
const existingMessage = existingIndex !== -1 && this.messages[existingIndex];
|
|
610
|
+
if (shouldReplace && existingMessage) {
|
|
611
|
+
this.messages[existingIndex] = messageV2;
|
|
612
|
+
} else if (!exists) {
|
|
613
|
+
this.messages.push(messageV2);
|
|
614
|
+
}
|
|
615
|
+
if (messageSource === `memory`) {
|
|
616
|
+
this.memoryMessages.add(messageV2);
|
|
617
|
+
} else if (messageSource === `response`) {
|
|
618
|
+
this.responseMessages.add(messageV2);
|
|
619
|
+
} else if (messageSource === `user`) {
|
|
620
|
+
this.newMessages.add(messageV2);
|
|
621
|
+
} else {
|
|
622
|
+
throw new Error(`Missing message source for message ${messageV2}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
this.messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
626
|
+
return this;
|
|
627
|
+
}
|
|
628
|
+
inputToMastraMessageV2(message, messageSource) {
|
|
629
|
+
if (`threadId` in message && message.threadId && this.memoryInfo && message.threadId !== this.memoryInfo.threadId) {
|
|
630
|
+
throw new Error(
|
|
631
|
+
`Received input message with wrong threadId. Input ${message.threadId}, expected ${this.memoryInfo.threadId}`
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
if (`resourceId` in message && message.resourceId && this.memoryInfo?.resourceId && message.resourceId !== this.memoryInfo.resourceId) {
|
|
635
|
+
throw new Error(
|
|
636
|
+
`Received input message with wrong resourceId. Input ${message.resourceId}, expected ${this.memoryInfo.resourceId}`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
if (_MessageList.isMastraMessageV1(message)) {
|
|
640
|
+
return this.mastraMessageV1ToMastraMessageV2(message, messageSource);
|
|
641
|
+
}
|
|
642
|
+
if (_MessageList.isMastraMessageV2(message)) {
|
|
643
|
+
return this.hydrateMastraMessageV2Fields(message);
|
|
644
|
+
}
|
|
645
|
+
if (_MessageList.isVercelCoreMessage(message)) {
|
|
646
|
+
return this.vercelCoreMessageToMastraMessageV2(message, messageSource);
|
|
647
|
+
}
|
|
648
|
+
if (_MessageList.isVercelUIMessage(message)) {
|
|
649
|
+
return this.vercelUIMessageToMastraMessageV2(message, messageSource);
|
|
650
|
+
}
|
|
651
|
+
throw new Error(`Found unhandled message ${JSON.stringify(message)}`);
|
|
652
|
+
}
|
|
653
|
+
lastCreatedAt;
|
|
654
|
+
// this makes sure messages added in order will always have a date atleast 1ms apart.
|
|
655
|
+
generateCreatedAt(messageSource, start) {
|
|
656
|
+
start = start instanceof Date ? start : start ? new Date(start) : void 0;
|
|
657
|
+
if (start && !this.lastCreatedAt) {
|
|
658
|
+
this.lastCreatedAt = start.getTime();
|
|
659
|
+
return start;
|
|
660
|
+
}
|
|
661
|
+
if (start && messageSource === `memory`) {
|
|
662
|
+
return start;
|
|
663
|
+
}
|
|
664
|
+
const now = /* @__PURE__ */ new Date();
|
|
665
|
+
const nowTime = start?.getTime() || now.getTime();
|
|
666
|
+
const lastTime = this.messages.reduce((p, m) => {
|
|
667
|
+
if (m.createdAt.getTime() > p) return m.createdAt.getTime();
|
|
668
|
+
return p;
|
|
669
|
+
}, this.lastCreatedAt || 0);
|
|
670
|
+
if (nowTime <= lastTime) {
|
|
671
|
+
const newDate = new Date(lastTime + 1);
|
|
672
|
+
this.lastCreatedAt = newDate.getTime();
|
|
673
|
+
return newDate;
|
|
674
|
+
}
|
|
675
|
+
this.lastCreatedAt = nowTime;
|
|
676
|
+
return now;
|
|
677
|
+
}
|
|
678
|
+
newMessageId() {
|
|
679
|
+
if (this.generateMessageId) {
|
|
680
|
+
return this.generateMessageId();
|
|
681
|
+
}
|
|
682
|
+
return randomUUID();
|
|
683
|
+
}
|
|
684
|
+
mastraMessageV1ToMastraMessageV2(message, messageSource) {
|
|
685
|
+
const coreV2 = this.vercelCoreMessageToMastraMessageV2(
|
|
686
|
+
{
|
|
687
|
+
content: message.content,
|
|
688
|
+
role: message.role
|
|
689
|
+
},
|
|
690
|
+
messageSource
|
|
691
|
+
);
|
|
692
|
+
return {
|
|
693
|
+
id: message.id,
|
|
694
|
+
role: coreV2.role,
|
|
695
|
+
createdAt: this.generateCreatedAt(messageSource, message.createdAt),
|
|
696
|
+
threadId: message.threadId,
|
|
697
|
+
resourceId: message.resourceId,
|
|
698
|
+
content: coreV2.content
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
hydrateMastraMessageV2Fields(message) {
|
|
702
|
+
if (!(message.createdAt instanceof Date)) message.createdAt = new Date(message.createdAt);
|
|
703
|
+
return message;
|
|
704
|
+
}
|
|
705
|
+
vercelUIMessageToMastraMessageV2(message, messageSource) {
|
|
706
|
+
const content = {
|
|
707
|
+
format: 2,
|
|
708
|
+
parts: message.parts
|
|
709
|
+
};
|
|
710
|
+
if (message.toolInvocations) content.toolInvocations = message.toolInvocations;
|
|
711
|
+
if (message.reasoning) content.reasoning = message.reasoning;
|
|
712
|
+
if (message.annotations) content.annotations = message.annotations;
|
|
713
|
+
if (message.experimental_attachments) {
|
|
714
|
+
content.experimental_attachments = message.experimental_attachments;
|
|
715
|
+
}
|
|
716
|
+
return {
|
|
717
|
+
id: message.id || this.newMessageId(),
|
|
718
|
+
role: _MessageList.getRole(message),
|
|
719
|
+
createdAt: this.generateCreatedAt(messageSource, message.createdAt),
|
|
720
|
+
threadId: this.memoryInfo?.threadId,
|
|
721
|
+
resourceId: this.memoryInfo?.resourceId,
|
|
722
|
+
content
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
vercelCoreMessageToMastraMessageV2(coreMessage, messageSource) {
|
|
726
|
+
const id = `id` in coreMessage ? coreMessage.id : this.newMessageId();
|
|
727
|
+
const parts = [];
|
|
728
|
+
const experimentalAttachments = [];
|
|
729
|
+
const toolInvocations = [];
|
|
730
|
+
if (typeof coreMessage.content === "string") {
|
|
731
|
+
parts.push({ type: "step-start" });
|
|
732
|
+
parts.push({
|
|
733
|
+
type: "text",
|
|
734
|
+
text: coreMessage.content
|
|
735
|
+
});
|
|
736
|
+
} else if (Array.isArray(coreMessage.content)) {
|
|
737
|
+
for (const part of coreMessage.content) {
|
|
738
|
+
switch (part.type) {
|
|
739
|
+
case "text":
|
|
740
|
+
parts.push({
|
|
741
|
+
type: "text",
|
|
742
|
+
text: part.text
|
|
743
|
+
});
|
|
744
|
+
break;
|
|
745
|
+
case "tool-call":
|
|
746
|
+
parts.push({
|
|
747
|
+
type: "tool-invocation",
|
|
748
|
+
toolInvocation: {
|
|
749
|
+
state: "call",
|
|
750
|
+
toolCallId: part.toolCallId,
|
|
751
|
+
toolName: part.toolName,
|
|
752
|
+
args: part.args
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
break;
|
|
756
|
+
case "tool-result":
|
|
757
|
+
const invocation = {
|
|
758
|
+
state: "result",
|
|
759
|
+
toolCallId: part.toolCallId,
|
|
760
|
+
toolName: part.toolName,
|
|
761
|
+
result: part.result ?? "",
|
|
762
|
+
// undefined will cause AI SDK to throw an error, but for client side tool calls this really could be undefined
|
|
763
|
+
args: {}
|
|
764
|
+
// when we combine this invocation onto the existing tool-call part it will have args already
|
|
765
|
+
};
|
|
766
|
+
parts.push({
|
|
767
|
+
type: "tool-invocation",
|
|
768
|
+
toolInvocation: invocation
|
|
769
|
+
});
|
|
770
|
+
toolInvocations.push(invocation);
|
|
771
|
+
break;
|
|
772
|
+
case "reasoning":
|
|
773
|
+
parts.push({
|
|
774
|
+
type: "reasoning",
|
|
775
|
+
reasoning: part.text,
|
|
776
|
+
// Assuming text is the main reasoning content
|
|
777
|
+
details: [{ type: "text", text: part.text, signature: part.signature }]
|
|
778
|
+
});
|
|
779
|
+
break;
|
|
780
|
+
case "redacted-reasoning":
|
|
781
|
+
parts.push({
|
|
782
|
+
type: "reasoning",
|
|
783
|
+
reasoning: "",
|
|
784
|
+
// No text reasoning for redacted parts
|
|
785
|
+
details: [{ type: "redacted", data: part.data }]
|
|
786
|
+
});
|
|
787
|
+
break;
|
|
788
|
+
case "file":
|
|
789
|
+
if (part.data instanceof URL) {
|
|
790
|
+
if (part.data.protocol !== "data:") {
|
|
791
|
+
experimentalAttachments.push({
|
|
792
|
+
name: part.filename,
|
|
793
|
+
url: part.data.toString(),
|
|
794
|
+
contentType: part.mimeType
|
|
795
|
+
});
|
|
796
|
+
} else {
|
|
797
|
+
try {
|
|
798
|
+
const base64Match = part.data.toString().match(/^data:[^;]+;base64,(.+)$/);
|
|
799
|
+
if (base64Match && base64Match[1]) {
|
|
800
|
+
parts.push({
|
|
801
|
+
type: "file",
|
|
802
|
+
mimeType: part.mimeType,
|
|
803
|
+
data: base64Match[1]
|
|
804
|
+
});
|
|
805
|
+
} else {
|
|
806
|
+
console.error(`Invalid data URL format: ${part.data}`);
|
|
807
|
+
}
|
|
808
|
+
} catch (error) {
|
|
809
|
+
console.error(`Failed to process data URL in CoreMessage file part: ${error}`, error);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
} else {
|
|
813
|
+
try {
|
|
814
|
+
parts.push({
|
|
815
|
+
type: "file",
|
|
816
|
+
mimeType: part.mimeType,
|
|
817
|
+
data: convertDataContentToBase64String(part.data)
|
|
818
|
+
});
|
|
819
|
+
} catch (error) {
|
|
820
|
+
console.error(`Failed to convert binary data to base64 in CoreMessage file part: ${error}`, error);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
break;
|
|
824
|
+
default:
|
|
825
|
+
throw new Error(`Found unknown CoreMessage content part type: ${part.type}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const content = {
|
|
830
|
+
format: 2,
|
|
831
|
+
parts
|
|
832
|
+
};
|
|
833
|
+
if (toolInvocations.length) content.toolInvocations = toolInvocations;
|
|
834
|
+
if (typeof coreMessage.content === `string`) content.content = coreMessage.content;
|
|
835
|
+
if (experimentalAttachments.length) content.experimental_attachments = experimentalAttachments;
|
|
836
|
+
return {
|
|
837
|
+
id,
|
|
838
|
+
role: _MessageList.getRole(coreMessage),
|
|
839
|
+
createdAt: this.generateCreatedAt(messageSource),
|
|
840
|
+
threadId: this.memoryInfo?.threadId,
|
|
841
|
+
resourceId: this.memoryInfo?.resourceId,
|
|
842
|
+
content
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
static isVercelUIMessage(msg) {
|
|
846
|
+
return !_MessageList.isMastraMessage(msg) && isUiMessage(msg);
|
|
847
|
+
}
|
|
848
|
+
static isVercelCoreMessage(msg) {
|
|
849
|
+
return !_MessageList.isMastraMessage(msg) && isCoreMessage(msg);
|
|
850
|
+
}
|
|
851
|
+
static isMastraMessage(msg) {
|
|
852
|
+
return _MessageList.isMastraMessageV2(msg) || _MessageList.isMastraMessageV1(msg);
|
|
853
|
+
}
|
|
854
|
+
static isMastraMessageV1(msg) {
|
|
855
|
+
return !_MessageList.isMastraMessageV2(msg) && (`threadId` in msg || `resourceId` in msg);
|
|
856
|
+
}
|
|
857
|
+
static isMastraMessageV2(msg) {
|
|
858
|
+
return Boolean(
|
|
859
|
+
msg.content && !Array.isArray(msg.content) && typeof msg.content !== `string` && // any newly saved Mastra message v2 shape will have content: { format: 2 }
|
|
860
|
+
`format` in msg.content && msg.content.format === 2
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
static getRole(message) {
|
|
864
|
+
if (message.role === `assistant` || message.role === `tool`) return `assistant`;
|
|
865
|
+
if (message.role === `user`) return `user`;
|
|
866
|
+
throw new Error(
|
|
867
|
+
`BUG: add handling for message role ${message.role} in message ${JSON.stringify(message, null, 2)}`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
static cacheKeyFromParts(parts) {
|
|
871
|
+
let key = ``;
|
|
872
|
+
for (const part of parts) {
|
|
873
|
+
key += part.type;
|
|
874
|
+
if (part.type === `text`) {
|
|
875
|
+
key += part.text.length;
|
|
876
|
+
}
|
|
877
|
+
if (part.type === `tool-invocation`) {
|
|
878
|
+
key += part.toolInvocation.toolCallId;
|
|
879
|
+
key += part.toolInvocation.state;
|
|
880
|
+
}
|
|
881
|
+
if (part.type === `reasoning`) {
|
|
882
|
+
key += part.reasoning.length;
|
|
883
|
+
}
|
|
884
|
+
if (part.type === `file`) {
|
|
885
|
+
key += part.data.length;
|
|
886
|
+
key += part.mimeType;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return key;
|
|
890
|
+
}
|
|
891
|
+
static coreContentToString(content) {
|
|
892
|
+
if (typeof content === `string`) return content;
|
|
893
|
+
return content.reduce((p, c) => {
|
|
894
|
+
if (c.type === `text`) {
|
|
895
|
+
p += c.text;
|
|
896
|
+
}
|
|
897
|
+
return p;
|
|
898
|
+
}, "");
|
|
899
|
+
}
|
|
900
|
+
static cacheKeyFromContent(content) {
|
|
901
|
+
if (typeof content === `string`) return content;
|
|
902
|
+
let key = ``;
|
|
903
|
+
for (const part of content) {
|
|
904
|
+
key += part.type;
|
|
905
|
+
if (part.type === `text`) {
|
|
906
|
+
key += part.text.length;
|
|
907
|
+
}
|
|
908
|
+
if (part.type === `reasoning`) {
|
|
909
|
+
key += part.text.length;
|
|
910
|
+
}
|
|
911
|
+
if (part.type === `tool-call`) {
|
|
912
|
+
key += part.toolCallId;
|
|
913
|
+
key += part.toolName;
|
|
914
|
+
}
|
|
915
|
+
if (part.type === `tool-result`) {
|
|
916
|
+
key += part.toolCallId;
|
|
917
|
+
key += part.toolName;
|
|
918
|
+
}
|
|
919
|
+
if (part.type === `file`) {
|
|
920
|
+
key += part.filename;
|
|
921
|
+
key += part.mimeType;
|
|
922
|
+
}
|
|
923
|
+
if (part.type === `image`) {
|
|
924
|
+
key += part.image instanceof URL ? part.image.toString() : part.image.toString().length;
|
|
925
|
+
key += part.mimeType;
|
|
926
|
+
}
|
|
927
|
+
if (part.type === `redacted-reasoning`) {
|
|
928
|
+
key += part.data.length;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return key;
|
|
932
|
+
}
|
|
933
|
+
static messagesAreEqual(one, two) {
|
|
934
|
+
const oneUI = _MessageList.isVercelUIMessage(one) && one;
|
|
935
|
+
const twoUI = _MessageList.isVercelUIMessage(two) && two;
|
|
936
|
+
if (oneUI && !twoUI) return false;
|
|
937
|
+
if (oneUI && twoUI) {
|
|
938
|
+
return _MessageList.cacheKeyFromParts(one.parts) === _MessageList.cacheKeyFromParts(two.parts);
|
|
939
|
+
}
|
|
940
|
+
const oneCM = _MessageList.isVercelCoreMessage(one) && one;
|
|
941
|
+
const twoCM = _MessageList.isVercelCoreMessage(two) && two;
|
|
942
|
+
if (oneCM && !twoCM) return false;
|
|
943
|
+
if (oneCM && twoCM) {
|
|
944
|
+
return _MessageList.cacheKeyFromContent(oneCM.content) === _MessageList.cacheKeyFromContent(twoCM.content);
|
|
945
|
+
}
|
|
946
|
+
const oneMM1 = _MessageList.isMastraMessageV1(one) && one;
|
|
947
|
+
const twoMM1 = _MessageList.isMastraMessageV1(two) && two;
|
|
948
|
+
if (oneMM1 && !twoMM1) return false;
|
|
949
|
+
if (oneMM1 && twoMM1) {
|
|
950
|
+
return oneMM1.id === twoMM1.id && _MessageList.cacheKeyFromContent(oneMM1.content) === _MessageList.cacheKeyFromContent(twoMM1.content);
|
|
951
|
+
}
|
|
952
|
+
const oneMM2 = _MessageList.isMastraMessageV2(one) && one;
|
|
953
|
+
const twoMM2 = _MessageList.isMastraMessageV2(two) && two;
|
|
954
|
+
if (oneMM2 && !twoMM2) return false;
|
|
955
|
+
if (oneMM2 && twoMM2) {
|
|
956
|
+
return oneMM2.id === twoMM2.id && _MessageList.cacheKeyFromParts(oneMM2.content.parts) === _MessageList.cacheKeyFromParts(twoMM2.content.parts);
|
|
957
|
+
}
|
|
958
|
+
return true;
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
export { MessageList };
|