@convex-dev/agent 0.0.16-alpha.0 → 0.0.17-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -71
- package/dist/commonjs/client/index.d.ts +1012 -920
- package/dist/commonjs/client/index.d.ts.map +1 -1
- package/dist/commonjs/client/index.js +188 -31
- package/dist/commonjs/client/index.js.map +1 -1
- package/dist/commonjs/client/playground.d.ts +251 -116
- package/dist/commonjs/client/playground.d.ts.map +1 -1
- package/dist/commonjs/client/playground.js +33 -11
- package/dist/commonjs/client/playground.js.map +1 -1
- package/dist/commonjs/component/messages.d.ts +683 -571
- package/dist/commonjs/component/messages.d.ts.map +1 -1
- package/dist/commonjs/component/messages.js +56 -33
- package/dist/commonjs/component/messages.js.map +1 -1
- package/dist/commonjs/component/schema.d.ts +1080 -1324
- package/dist/commonjs/component/schema.d.ts.map +1 -1
- package/dist/commonjs/component/schema.js +20 -14
- package/dist/commonjs/component/schema.js.map +1 -1
- package/dist/commonjs/component/vector/index.d.ts.map +1 -1
- package/dist/commonjs/component/vector/index.js +4 -6
- package/dist/commonjs/component/vector/index.js.map +1 -1
- package/dist/commonjs/component/vector/tables.d.ts +10 -10
- package/dist/commonjs/component/vector/tables.d.ts.map +1 -1
- package/dist/commonjs/component/vector/tables.js.map +1 -1
- package/dist/commonjs/mapping.d.ts +7 -1
- package/dist/commonjs/mapping.d.ts.map +1 -1
- package/dist/commonjs/mapping.js +39 -18
- package/dist/commonjs/mapping.js.map +1 -1
- package/dist/commonjs/validators.d.ts +1669 -2336
- package/dist/commonjs/validators.d.ts.map +1 -1
- package/dist/commonjs/validators.js +35 -25
- package/dist/commonjs/validators.js.map +1 -1
- package/dist/esm/client/index.d.ts +1012 -920
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js +188 -31
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/playground.d.ts +251 -116
- package/dist/esm/client/playground.d.ts.map +1 -1
- package/dist/esm/client/playground.js +33 -11
- package/dist/esm/client/playground.js.map +1 -1
- package/dist/esm/component/messages.d.ts +683 -571
- package/dist/esm/component/messages.d.ts.map +1 -1
- package/dist/esm/component/messages.js +56 -33
- package/dist/esm/component/messages.js.map +1 -1
- package/dist/esm/component/schema.d.ts +1080 -1324
- package/dist/esm/component/schema.d.ts.map +1 -1
- package/dist/esm/component/schema.js +20 -14
- package/dist/esm/component/schema.js.map +1 -1
- package/dist/esm/component/vector/index.d.ts.map +1 -1
- package/dist/esm/component/vector/index.js +4 -6
- package/dist/esm/component/vector/index.js.map +1 -1
- package/dist/esm/component/vector/tables.d.ts +10 -10
- package/dist/esm/component/vector/tables.d.ts.map +1 -1
- package/dist/esm/component/vector/tables.js.map +1 -1
- package/dist/esm/mapping.d.ts +7 -1
- package/dist/esm/mapping.d.ts.map +1 -1
- package/dist/esm/mapping.js +39 -18
- package/dist/esm/mapping.js.map +1 -1
- package/dist/esm/validators.d.ts +1669 -2336
- package/dist/esm/validators.d.ts.map +1 -1
- package/dist/esm/validators.js +35 -25
- package/dist/esm/validators.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.test.ts +68 -0
- package/src/client/index.ts +219 -35
- package/src/client/playground.ts +49 -23
- package/src/component/_generated/api.d.ts +311 -323
- package/src/component/messages.test.ts +23 -0
- package/src/component/messages.ts +75 -42
- package/src/component/schema.ts +26 -13
- package/src/component/vector/index.ts +5 -6
- package/src/component/vector/tables.ts +15 -15
- package/src/mapping.ts +65 -32
- package/src/validators.ts +47 -27
|
@@ -5,9 +5,32 @@ import { convexTest } from "convex-test";
|
|
|
5
5
|
import schema from "./schema.js";
|
|
6
6
|
import { api } from "./_generated/api.js";
|
|
7
7
|
import { modules } from "./setup.test.js";
|
|
8
|
+
import { Id } from "./_generated/dataModel.js";
|
|
9
|
+
import { getMaxMessage } from "./messages.js";
|
|
8
10
|
|
|
9
11
|
describe("agent", () => {
|
|
10
12
|
test("add and subtract", async () => {
|
|
11
13
|
const t = convexTest(schema, modules);
|
|
12
14
|
});
|
|
15
|
+
test("getMaxMessage works for threads", async () => {
|
|
16
|
+
const t = convexTest(schema, modules);
|
|
17
|
+
const thread = await t.mutation(api.threads.createThread, {
|
|
18
|
+
userId: "test",
|
|
19
|
+
});
|
|
20
|
+
const { messages } = await t.mutation(api.messages.addMessages, {
|
|
21
|
+
threadId: thread._id as Id<"threads">,
|
|
22
|
+
messages: [
|
|
23
|
+
{ message: { role: "user", content: "hello" } },
|
|
24
|
+
{ message: { role: "assistant", content: "world" } },
|
|
25
|
+
],
|
|
26
|
+
});
|
|
27
|
+
const maxMessage = await t.run(async (ctx) => {
|
|
28
|
+
return await getMaxMessage(ctx, thread._id as Id<"threads">, "test");
|
|
29
|
+
});
|
|
30
|
+
expect(maxMessage).toMatchObject({
|
|
31
|
+
_id: messages.at(-1)!._id,
|
|
32
|
+
order: 1,
|
|
33
|
+
stepOrder: 0,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
13
36
|
});
|
|
@@ -57,10 +57,11 @@ export async function deleteMessage(
|
|
|
57
57
|
if (messageDoc.embeddingId) {
|
|
58
58
|
await ctx.db.delete(messageDoc.embeddingId);
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
for (const { fileId } of messageDoc.files ?? []) {
|
|
61
|
+
if (!fileId) continue;
|
|
62
|
+
const file = await ctx.db.get(fileId);
|
|
62
63
|
if (file) {
|
|
63
|
-
await ctx.db.patch(
|
|
64
|
+
await ctx.db.patch(fileId, { refcount: file.refcount - 1 });
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
}
|
|
@@ -117,9 +118,11 @@ async function addMessagesHandler(
|
|
|
117
118
|
}
|
|
118
119
|
const maxMessage = await getMaxMessage(ctx, threadId, userId);
|
|
119
120
|
let order = maxMessage?.order ?? -1;
|
|
121
|
+
let stepOrder = maxMessage?.stepOrder ?? 0;
|
|
122
|
+
let lastMessageIsTool = maxMessage?.tool ?? false;
|
|
120
123
|
const toReturn: Doc<"messages">[] = [];
|
|
121
124
|
if (messages.length > 0) {
|
|
122
|
-
for (const { message,
|
|
125
|
+
for (const { message, files, embedding, ...fields } of messages) {
|
|
123
126
|
let embeddingId: VectorTableId | undefined;
|
|
124
127
|
if (embedding) {
|
|
125
128
|
embeddingId = await insertVector(ctx, embedding.dimension, {
|
|
@@ -131,9 +134,13 @@ async function addMessagesHandler(
|
|
|
131
134
|
});
|
|
132
135
|
}
|
|
133
136
|
const tool = isTool(message);
|
|
134
|
-
if (
|
|
137
|
+
if (lastMessageIsTool) {
|
|
138
|
+
stepOrder++;
|
|
139
|
+
} else {
|
|
135
140
|
order++;
|
|
141
|
+
stepOrder = 0;
|
|
136
142
|
}
|
|
143
|
+
lastMessageIsTool = tool;
|
|
137
144
|
const text = extractText(message);
|
|
138
145
|
const messageId = await ctx.db.insert("messages", {
|
|
139
146
|
...rest,
|
|
@@ -145,11 +152,17 @@ async function addMessagesHandler(
|
|
|
145
152
|
order,
|
|
146
153
|
tool,
|
|
147
154
|
text,
|
|
148
|
-
|
|
155
|
+
files,
|
|
149
156
|
status: pending ? "pending" : "success",
|
|
150
|
-
stepOrder
|
|
157
|
+
stepOrder,
|
|
151
158
|
});
|
|
152
|
-
if (
|
|
159
|
+
if (!fields.id) {
|
|
160
|
+
await ctx.db.patch(messageId, {
|
|
161
|
+
id: messageId,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
for (const { fileId } of files ?? []) {
|
|
165
|
+
if (!fileId) continue;
|
|
153
166
|
await ctx.db.patch(fileId, {
|
|
154
167
|
refcount: (await ctx.db.get(fileId))!.refcount + 1,
|
|
155
168
|
});
|
|
@@ -160,7 +173,8 @@ async function addMessagesHandler(
|
|
|
160
173
|
return { messages: toReturn };
|
|
161
174
|
}
|
|
162
175
|
|
|
163
|
-
|
|
176
|
+
// exported for tests
|
|
177
|
+
export async function getMaxMessage(
|
|
164
178
|
ctx: QueryCtx,
|
|
165
179
|
threadId: Id<"threads"> | undefined,
|
|
166
180
|
userId: string | undefined
|
|
@@ -168,39 +182,32 @@ async function getMaxMessage(
|
|
|
168
182
|
assert(threadId || userId, "One of threadId or userId is required");
|
|
169
183
|
if (threadId) {
|
|
170
184
|
return mergedStream(
|
|
171
|
-
[
|
|
172
|
-
|
|
173
|
-
.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
[true, false].flatMap((tool) =>
|
|
186
|
+
["success" as const, "pending" as const].map((status) =>
|
|
187
|
+
stream(ctx.db, schema)
|
|
188
|
+
.query("messages")
|
|
189
|
+
.withIndex("threadId_status_tool_order_stepOrder", (q) =>
|
|
190
|
+
q.eq("threadId", threadId).eq("status", status).eq("tool", tool)
|
|
191
|
+
)
|
|
192
|
+
.order("desc")
|
|
193
|
+
)
|
|
178
194
|
),
|
|
179
195
|
["order", "stepOrder"]
|
|
180
196
|
).first();
|
|
181
197
|
} else {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
.order("desc")
|
|
196
|
-
.first();
|
|
197
|
-
return maxPending
|
|
198
|
-
? maxSuccess
|
|
199
|
-
? maxPending.order > maxSuccess.order
|
|
200
|
-
? maxPending
|
|
201
|
-
: maxSuccess
|
|
202
|
-
: maxPending
|
|
203
|
-
: maxSuccess ?? null;
|
|
198
|
+
return mergedStream(
|
|
199
|
+
[true, false].flatMap((tool) =>
|
|
200
|
+
["success" as const, "pending" as const].map((status) =>
|
|
201
|
+
stream(ctx.db, schema)
|
|
202
|
+
.query("messages")
|
|
203
|
+
.withIndex("userId_status_tool_order_stepOrder", (q) =>
|
|
204
|
+
q.eq("userId", userId).eq("status", status).eq("tool", tool)
|
|
205
|
+
)
|
|
206
|
+
.order("desc")
|
|
207
|
+
)
|
|
208
|
+
),
|
|
209
|
+
["order", "stepOrder"]
|
|
210
|
+
).first();
|
|
204
211
|
}
|
|
205
212
|
}
|
|
206
213
|
|
|
@@ -225,6 +232,7 @@ async function addStepHandler(
|
|
|
225
232
|
assert(parentMessage, `Message ${args.parentMessageId} not found`);
|
|
226
233
|
const order = parentMessage.order;
|
|
227
234
|
assert(order !== undefined, `${args.parentMessageId} has no order`);
|
|
235
|
+
// TODO: only fetch the last one if we aren't failing pending steps
|
|
228
236
|
let steps = await ctx.db
|
|
229
237
|
.query("steps")
|
|
230
238
|
.withIndex("parentMessageId_order_stepOrder", (q) =>
|
|
@@ -376,6 +384,12 @@ export const listMessagesByThreadId = query({
|
|
|
376
384
|
return qq;
|
|
377
385
|
})
|
|
378
386
|
.order(order)
|
|
387
|
+
.filterWith(
|
|
388
|
+
async (m) =>
|
|
389
|
+
!before ||
|
|
390
|
+
m.order < before.order ||
|
|
391
|
+
(m.order === before.order && m.stepOrder < before.stepOrder)
|
|
392
|
+
)
|
|
379
393
|
)
|
|
380
394
|
);
|
|
381
395
|
const messages = await mergedStream(streams, [
|
|
@@ -401,7 +415,6 @@ export const getThreadMessages = query({
|
|
|
401
415
|
returns: paginationResultValidator(v.doc("messages")),
|
|
402
416
|
});
|
|
403
417
|
|
|
404
|
-
|
|
405
418
|
export const searchMessages = action({
|
|
406
419
|
args: {
|
|
407
420
|
userId: v.optional(v.string()),
|
|
@@ -420,6 +433,7 @@ export const searchMessages = action({
|
|
|
420
433
|
threadId: args.threadId,
|
|
421
434
|
text: args.text,
|
|
422
435
|
limit,
|
|
436
|
+
beforeMessageId: args.beforeMessageId,
|
|
423
437
|
});
|
|
424
438
|
}
|
|
425
439
|
if (args.vector) {
|
|
@@ -506,7 +520,10 @@ export const _fetchSearchMessages = internalQuery({
|
|
|
506
520
|
m !== undefined &&
|
|
507
521
|
m !== null &&
|
|
508
522
|
!m.tool &&
|
|
509
|
-
(!beforeMessage ||
|
|
523
|
+
(!beforeMessage ||
|
|
524
|
+
m.order < beforeMessage.order ||
|
|
525
|
+
(m.order === beforeMessage.order &&
|
|
526
|
+
m.stepOrder < beforeMessage.stepOrder))
|
|
510
527
|
);
|
|
511
528
|
messages.push(...(args.textSearchMessages ?? []));
|
|
512
529
|
// TODO: prioritize more recent messages
|
|
@@ -597,9 +614,13 @@ export const textSearch = query({
|
|
|
597
614
|
userId: v.optional(v.string()),
|
|
598
615
|
text: v.string(),
|
|
599
616
|
limit: v.number(),
|
|
617
|
+
beforeMessageId: v.optional(v.id("messages")),
|
|
600
618
|
},
|
|
601
619
|
handler: async (ctx, args) => {
|
|
602
620
|
assert(args.userId || args.threadId, "Specify userId or threadId");
|
|
621
|
+
const beforeMessage =
|
|
622
|
+
args.beforeMessageId && (await ctx.db.get(args.beforeMessageId));
|
|
623
|
+
const order = beforeMessage?.order;
|
|
603
624
|
const messages = await ctx.db
|
|
604
625
|
.query("messages")
|
|
605
626
|
.withSearchIndex("text_search", (q) =>
|
|
@@ -608,9 +629,21 @@ export const textSearch = query({
|
|
|
608
629
|
: q.search("text", args.text).eq("threadId", args.threadId!)
|
|
609
630
|
)
|
|
610
631
|
// Just in case tool messages slip through
|
|
611
|
-
.filter((q) =>
|
|
632
|
+
.filter((q) => {
|
|
633
|
+
const qq = q.eq(q.field("tool"), false);
|
|
634
|
+
if (order) {
|
|
635
|
+
return q.and(qq, q.lte(q.field("order"), order));
|
|
636
|
+
}
|
|
637
|
+
return qq;
|
|
638
|
+
})
|
|
612
639
|
.take(args.limit);
|
|
613
|
-
return messages
|
|
640
|
+
return messages.filter(
|
|
641
|
+
(m) =>
|
|
642
|
+
!beforeMessage ||
|
|
643
|
+
m.order < beforeMessage.order ||
|
|
644
|
+
(m.order === beforeMessage.order &&
|
|
645
|
+
m.stepOrder < beforeMessage.stepOrder)
|
|
646
|
+
);
|
|
614
647
|
},
|
|
615
648
|
returns: v.array(v.doc("messages")),
|
|
616
649
|
});
|
package/src/component/schema.ts
CHANGED
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
vSource,
|
|
10
10
|
vLanguageModelV1CallWarning,
|
|
11
11
|
vFinishReason,
|
|
12
|
+
vProviderOptions,
|
|
13
|
+
vProviderMetadata,
|
|
14
|
+
vReasoningDetails,
|
|
15
|
+
vFile,
|
|
12
16
|
} from "../validators.js";
|
|
13
17
|
import { typedV } from "convex-helpers/validators";
|
|
14
18
|
import vectorTables, { vVectorId } from "./vector/tables.js";
|
|
@@ -29,35 +33,44 @@ export const schema = defineSchema({
|
|
|
29
33
|
id: v.optional(v.string()), // external id, e.g. from Vercel AI SDK
|
|
30
34
|
userId: v.optional(v.string()), // useful for future indexes (text search)
|
|
31
35
|
threadId: v.id("threads"),
|
|
36
|
+
// TODO: is this redunant with message at last step @ order - 1?
|
|
32
37
|
parentMessageId: v.optional(v.id("messages")),
|
|
33
38
|
stepId: v.optional(v.id("steps")),
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
// Repeats until a non-tool message.
|
|
40
|
+
order: v.number(),
|
|
41
|
+
stepOrder: v.number(),
|
|
42
|
+
embeddingId: v.optional(vVectorId),
|
|
36
43
|
error: v.optional(v.string()),
|
|
44
|
+
status: vMessageStatus,
|
|
45
|
+
|
|
46
|
+
// Context on how it was generated
|
|
47
|
+
agentName: v.optional(v.string()),
|
|
37
48
|
model: v.optional(v.string()),
|
|
38
49
|
provider: v.optional(v.string()),
|
|
50
|
+
providerOptions: v.optional(vProviderOptions), // Sent to model
|
|
51
|
+
|
|
52
|
+
// The result
|
|
53
|
+
message: v.optional(vMessage),
|
|
54
|
+
// Convenience fields extracted from the message
|
|
55
|
+
tool: v.boolean(), // either tool call (assistant) or tool result (tool)
|
|
39
56
|
text: v.optional(v.string()),
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Repeats until a non-tool message.
|
|
44
|
-
// Unset if it's not in a thread.
|
|
45
|
-
order: v.number(),
|
|
46
|
-
stepOrder: v.number(),
|
|
57
|
+
files: v.optional(v.array(vFile)),
|
|
58
|
+
|
|
59
|
+
// Result metadata
|
|
47
60
|
usage: v.optional(vUsage),
|
|
48
|
-
|
|
49
|
-
providerMetadata: v.optional(v.record(v.string(), v.any())),
|
|
61
|
+
providerMetadata: v.optional(vProviderMetadata), // Received from model
|
|
50
62
|
sources: v.optional(v.array(vSource)),
|
|
51
63
|
reasoning: v.optional(v.string()),
|
|
64
|
+
reasoningDetails: v.optional(vReasoningDetails),
|
|
52
65
|
warnings: v.optional(v.array(vLanguageModelV1CallWarning)),
|
|
53
|
-
|
|
54
|
-
status: vMessageStatus,
|
|
66
|
+
finishReason: v.optional(vFinishReason),
|
|
55
67
|
})
|
|
56
68
|
// Allows finding successful visible messages in order
|
|
57
69
|
// Also surface pending messages separately to e.g. stream
|
|
58
70
|
.index("threadId_status_tool_order_stepOrder", [
|
|
59
71
|
"threadId",
|
|
60
72
|
"status",
|
|
73
|
+
// TODO: we might not need this to be in the index..
|
|
61
74
|
"tool",
|
|
62
75
|
"order",
|
|
63
76
|
"stepOrder",
|
|
@@ -65,14 +65,13 @@ export const deleteBatchForThread = mutation({
|
|
|
65
65
|
handler: async (ctx, args) => {
|
|
66
66
|
const tableName = getVectorTableName(args.vectorDimension);
|
|
67
67
|
const vectors = await mergedStream(
|
|
68
|
-
["thread", "memory"].map((
|
|
68
|
+
["thread", "memory"].map((table) =>
|
|
69
69
|
stream(ctx.db, schema)
|
|
70
70
|
.query(tableName)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.eq("kind", kind)
|
|
71
|
+
.withIndex("model_table_threadId", (q) =>
|
|
72
|
+
q
|
|
73
|
+
.eq("model", args.model)
|
|
74
|
+
.eq("table", table)
|
|
76
75
|
.eq("threadId", args.threadId)
|
|
77
76
|
)
|
|
78
77
|
),
|
|
@@ -47,6 +47,21 @@ function table<D extends number>(dimensions: D): Table<D> {
|
|
|
47
47
|
.index("model_table_threadId", ["model", "table", "threadId"]);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
type Table<D extends number> = TableDefinition<
|
|
51
|
+
VObject<ObjectType<typeof embeddings>, typeof embeddings>,
|
|
52
|
+
{ model_table_threadId: ["model", "table", "threadId", "_creationTime"] },
|
|
53
|
+
GenericTableSearchIndexes,
|
|
54
|
+
VectorIndex<D>
|
|
55
|
+
>;
|
|
56
|
+
|
|
57
|
+
type VectorIndex<D extends number> = {
|
|
58
|
+
vector: {
|
|
59
|
+
vectorField: "vector";
|
|
60
|
+
dimensions: D;
|
|
61
|
+
filterFields: "model_table_userId" | "model_table_threadId";
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
50
65
|
export type VectorSchema = SchemaDefinition<
|
|
51
66
|
{ [key in VectorTableName]: Table<128> },
|
|
52
67
|
true
|
|
@@ -80,21 +95,6 @@ export const vVectorId = v.union(
|
|
|
80
95
|
VId<(typeof VectorTableNames)[number]>[]
|
|
81
96
|
>;
|
|
82
97
|
|
|
83
|
-
type Table<D extends number> = TableDefinition<
|
|
84
|
-
VObject<ObjectType<typeof embeddings>, typeof embeddings>,
|
|
85
|
-
{ id: ["id"] },
|
|
86
|
-
GenericTableSearchIndexes,
|
|
87
|
-
VectorIndex<D>
|
|
88
|
-
>;
|
|
89
|
-
|
|
90
|
-
type VectorIndex<D extends number> = {
|
|
91
|
-
vector: {
|
|
92
|
-
vectorField: "vector";
|
|
93
|
-
dimensions: D;
|
|
94
|
-
filterFields: string;
|
|
95
|
-
};
|
|
96
|
-
};
|
|
97
|
-
|
|
98
98
|
export function getVectorTableName(dimension: VectorDimension) {
|
|
99
99
|
return `embeddings_${dimension}` as VectorTableName;
|
|
100
100
|
}
|
package/src/mapping.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type Message as AIMessage,
|
|
12
12
|
type UserContent,
|
|
13
13
|
} from "ai";
|
|
14
|
+
import type { FileUIPart } from "@ai-sdk/ui-utils";
|
|
14
15
|
import { assert } from "convex-helpers";
|
|
15
16
|
import {
|
|
16
17
|
MessageWithMetadata,
|
|
@@ -39,9 +40,11 @@ export type SerializedMessage = SerializeUrlsAndUint8Arrays<CoreMessage>;
|
|
|
39
40
|
export function serializeMessage(
|
|
40
41
|
messageWithId: CoreMessage & { id?: string }
|
|
41
42
|
): SerializedMessage {
|
|
42
|
-
const { id: _, ...message } = messageWithId;
|
|
43
|
+
const { id: _, experimental_providerMetadata, ...message } = messageWithId;
|
|
43
44
|
const content = message.content;
|
|
44
45
|
return {
|
|
46
|
+
// for backwards compatibility
|
|
47
|
+
providerOptions: experimental_providerMetadata,
|
|
45
48
|
...message,
|
|
46
49
|
content: serializeContent(content),
|
|
47
50
|
} as SerializedMessage;
|
|
@@ -90,6 +93,7 @@ export function serializeNewMessagesInStep<TOOLS extends ToolSet>(
|
|
|
90
93
|
provider: metadata.provider,
|
|
91
94
|
providerMetadata: step.providerMetadata,
|
|
92
95
|
reasoning: step.reasoning,
|
|
96
|
+
reasoningDetails: step.reasoningDetails,
|
|
93
97
|
usage: step.usage,
|
|
94
98
|
warnings: step.warnings,
|
|
95
99
|
finishReason: step.finishReason,
|
|
@@ -101,33 +105,41 @@ export function serializeNewMessagesInStep<TOOLS extends ToolSet>(
|
|
|
101
105
|
step.toolResults.length > 0
|
|
102
106
|
? step.response.messages.slice(-2)
|
|
103
107
|
: step.response.messages.slice(-1)
|
|
104
|
-
).map(
|
|
105
|
-
message:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
).map(
|
|
109
|
+
(message): MessageWithMetadata => ({
|
|
110
|
+
message: serializeMessage(message),
|
|
111
|
+
id: message.id,
|
|
112
|
+
...(message.role === "tool" ? toolFields : assistantFields),
|
|
113
|
+
text: step.text,
|
|
114
|
+
// fileId: message.fileId,
|
|
115
|
+
files: step.files.map((file) => ({
|
|
116
|
+
mimeType: file.mimeType,
|
|
117
|
+
data: serializeDataOrUrl(file.uint8Array ?? file.base64),
|
|
118
|
+
// TODO: if the file is big, store it and populate url, fileId
|
|
119
|
+
})),
|
|
120
|
+
})
|
|
121
|
+
);
|
|
110
122
|
return messages;
|
|
111
123
|
}
|
|
112
124
|
|
|
113
125
|
export function serializeObjectResult(
|
|
114
|
-
|
|
126
|
+
result: GenerateObjectResult<unknown>,
|
|
115
127
|
metadata: { model: string; provider: string }
|
|
116
128
|
): StepWithMessagesWithMetadata {
|
|
117
|
-
const text = JSON.stringify(
|
|
129
|
+
const text = JSON.stringify(result.object);
|
|
118
130
|
|
|
119
131
|
return {
|
|
120
132
|
messages: [
|
|
121
133
|
{
|
|
122
134
|
message: { role: "assistant" as const, content: text },
|
|
123
|
-
id:
|
|
135
|
+
id: result.response.id,
|
|
124
136
|
model: metadata.model,
|
|
125
137
|
provider: metadata.provider,
|
|
126
|
-
providerMetadata:
|
|
127
|
-
finishReason:
|
|
138
|
+
providerMetadata: result.providerMetadata,
|
|
139
|
+
finishReason: result.finishReason,
|
|
128
140
|
text,
|
|
129
|
-
usage:
|
|
130
|
-
warnings:
|
|
141
|
+
usage: result.usage,
|
|
142
|
+
warnings: result.warnings,
|
|
131
143
|
},
|
|
132
144
|
],
|
|
133
145
|
step: {
|
|
@@ -136,23 +148,22 @@ export function serializeObjectResult(
|
|
|
136
148
|
stepType: "initial",
|
|
137
149
|
toolCalls: [],
|
|
138
150
|
toolResults: [],
|
|
139
|
-
usage:
|
|
140
|
-
warnings:
|
|
141
|
-
finishReason:
|
|
142
|
-
|
|
151
|
+
usage: result.usage,
|
|
152
|
+
warnings: result.warnings,
|
|
153
|
+
finishReason: result.finishReason,
|
|
154
|
+
providerMetadata: result.providerMetadata,
|
|
155
|
+
request: result.request,
|
|
143
156
|
response: {
|
|
144
|
-
...
|
|
145
|
-
timestamp:
|
|
157
|
+
...result.response,
|
|
158
|
+
timestamp: result.response.timestamp.getTime(),
|
|
146
159
|
messages: [
|
|
147
160
|
serializeMessageWithId({
|
|
148
161
|
role: "assistant" as const,
|
|
149
162
|
content: text,
|
|
150
|
-
id:
|
|
163
|
+
id: result.response.id,
|
|
151
164
|
}),
|
|
152
165
|
],
|
|
153
166
|
},
|
|
154
|
-
providerMetadata: step.providerMetadata,
|
|
155
|
-
experimental_providerMetadata: step.experimental_providerMetadata,
|
|
156
167
|
},
|
|
157
168
|
};
|
|
158
169
|
}
|
|
@@ -161,16 +172,19 @@ export function serializeContent(content: Content): SerializedContent {
|
|
|
161
172
|
if (typeof content === "string") {
|
|
162
173
|
return content;
|
|
163
174
|
}
|
|
164
|
-
const serialized = content.map(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
const serialized = content.map(
|
|
176
|
+
({ experimental_providerMetadata, ...rest }) => {
|
|
177
|
+
const part = { providerOptions: experimental_providerMetadata, ...rest };
|
|
178
|
+
switch (part.type) {
|
|
179
|
+
case "image":
|
|
180
|
+
return { ...part, image: serializeDataOrUrl(part.image) };
|
|
181
|
+
case "file":
|
|
182
|
+
return { ...part, file: serializeDataOrUrl(part.data) };
|
|
183
|
+
default:
|
|
184
|
+
return part;
|
|
185
|
+
}
|
|
172
186
|
}
|
|
173
|
-
|
|
187
|
+
);
|
|
174
188
|
return serialized as SerializedContent;
|
|
175
189
|
}
|
|
176
190
|
|
|
@@ -222,6 +236,25 @@ function deserializeUrl(urlOrString: string | ArrayBuffer): URL | DataContent {
|
|
|
222
236
|
return urlOrString;
|
|
223
237
|
}
|
|
224
238
|
|
|
239
|
+
export function toUIFilePart(file: {
|
|
240
|
+
data?: ArrayBuffer | string;
|
|
241
|
+
url?: string;
|
|
242
|
+
mimeType: string;
|
|
243
|
+
}): FileUIPart {
|
|
244
|
+
return {
|
|
245
|
+
type: "file",
|
|
246
|
+
data:
|
|
247
|
+
file.data instanceof ArrayBuffer
|
|
248
|
+
? encodeBase64(file.data)
|
|
249
|
+
: file.url ?? file.data ?? "",
|
|
250
|
+
mimeType: file.mimeType,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function encodeBase64(data: ArrayBuffer): string {
|
|
255
|
+
return Buffer.from(data).toString("base64");
|
|
256
|
+
}
|
|
257
|
+
|
|
225
258
|
export function promptOrMessagesToCoreMessages(args: {
|
|
226
259
|
prompt?: string;
|
|
227
260
|
messages?: CoreMessage[] | AIMessageWithoutId[];
|