@convex-dev/agent 0.1.16-alpha.1 → 0.1.16-alpha.3
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 +26 -15
- package/dist/client/createTool.d.ts +2 -2
- package/dist/client/createTool.d.ts.map +1 -1
- package/dist/client/createTool.js.map +1 -1
- package/dist/client/files.js +2 -2
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +12 -11
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +58 -36
- package/dist/client/index.js.map +1 -1
- package/dist/client/search.d.ts +2 -3
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +2 -1
- package/dist/client/search.js.map +1 -1
- package/dist/client/types.d.ts +7 -9
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/messages.d.ts +1 -1
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +5 -5
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js.map +1 -1
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +3 -1
- package/dist/mapping.js.map +1 -1
- package/dist/validators.js +1 -2
- package/dist/validators.js.map +1 -1
- package/package.json +1 -1
- package/src/client/createTool.ts +9 -6
- package/src/client/files.ts +8 -8
- package/src/client/index.test.ts +14 -12
- package/src/client/index.ts +156 -116
- package/src/client/listMessages.ts +1 -1
- package/src/client/search.ts +14 -14
- package/src/client/streaming.ts +7 -7
- package/src/client/types.ts +29 -23
- package/src/component/apiKeys.ts +1 -1
- package/src/component/files.test.ts +1 -1
- package/src/component/files.ts +6 -6
- package/src/component/messages.test.ts +6 -6
- package/src/component/messages.ts +44 -43
- package/src/component/schema.ts +1 -2
- package/src/component/streams.ts +27 -27
- package/src/component/threads.test.ts +4 -4
- package/src/component/threads.ts +4 -4
- package/src/component/users.test.ts +2 -2
- package/src/component/users.ts +4 -4
- package/src/component/vector/index.ts +11 -11
- package/src/component/vector/tables.ts +6 -6
- package/src/mapping.test.ts +4 -4
- package/src/mapping.ts +18 -17
- package/src/react/deltas.test.ts +16 -16
- package/src/react/deltas.ts +13 -13
- package/src/react/index.ts +11 -11
- package/src/react/optimisticallySendMessage.ts +2 -2
- package/src/react/toUIMessages.test.ts +10 -10
- package/src/react/toUIMessages.ts +6 -6
- package/src/react/useSmoothText.ts +6 -6
- package/src/validators.ts +24 -24
|
@@ -40,7 +40,7 @@ export const paginate = query({
|
|
|
40
40
|
args.table
|
|
41
41
|
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
42
|
(q.eq("model", args.targetModel) as any).eq("table", args.table)
|
|
43
|
-
: q.eq("model", args.targetModel)
|
|
43
|
+
: q.eq("model", args.targetModel),
|
|
44
44
|
)
|
|
45
45
|
.paginate({
|
|
46
46
|
cursor: args.cursor ?? null,
|
|
@@ -77,10 +77,10 @@ export const deleteBatchForThread = mutation({
|
|
|
77
77
|
q
|
|
78
78
|
.eq("model", args.model)
|
|
79
79
|
.eq("table", table)
|
|
80
|
-
.eq("threadId", args.threadId)
|
|
81
|
-
)
|
|
80
|
+
.eq("threadId", args.threadId),
|
|
81
|
+
),
|
|
82
82
|
),
|
|
83
|
-
["threadId"]
|
|
83
|
+
["threadId"],
|
|
84
84
|
).paginate({
|
|
85
85
|
cursor: args.cursor ?? null,
|
|
86
86
|
numItems: args.limit,
|
|
@@ -101,7 +101,7 @@ export const insertBatch = mutation({
|
|
|
101
101
|
v.object({
|
|
102
102
|
...vEmbeddingsWithoutDenormalizedFields.fields,
|
|
103
103
|
messageId: v.optional(v.id("messages")),
|
|
104
|
-
})
|
|
104
|
+
}),
|
|
105
105
|
),
|
|
106
106
|
},
|
|
107
107
|
returns: v.array(vVectorId),
|
|
@@ -113,7 +113,7 @@ export const insertBatch = mutation({
|
|
|
113
113
|
await ctx.db.patch(messageId, { embeddingId });
|
|
114
114
|
}
|
|
115
115
|
return embeddingId;
|
|
116
|
-
})
|
|
116
|
+
}),
|
|
117
117
|
);
|
|
118
118
|
},
|
|
119
119
|
});
|
|
@@ -121,7 +121,7 @@ export const insertBatch = mutation({
|
|
|
121
121
|
export async function insertVector(
|
|
122
122
|
ctx: MutationCtx,
|
|
123
123
|
dimension: VectorDimension,
|
|
124
|
-
v: EmbeddingsWithoutDenormalizedFields
|
|
124
|
+
v: EmbeddingsWithoutDenormalizedFields,
|
|
125
125
|
) {
|
|
126
126
|
return ctx.db.insert(getVectorTableName(dimension), {
|
|
127
127
|
...v,
|
|
@@ -143,7 +143,7 @@ export function searchVectors(
|
|
|
143
143
|
threadId?: Id<"threads">;
|
|
144
144
|
searchAllMessagesForUserId?: string;
|
|
145
145
|
limit?: number;
|
|
146
|
-
}
|
|
146
|
+
},
|
|
147
147
|
) {
|
|
148
148
|
const tableName = getVectorTableName(args.dimension);
|
|
149
149
|
return ctx.vectorSearch(tableName, "vector", {
|
|
@@ -176,7 +176,7 @@ export const updateBatch = mutation({
|
|
|
176
176
|
// deleting from one table and inserting into another.
|
|
177
177
|
// However this requires updating all the messages that reference
|
|
178
178
|
// the vector.
|
|
179
|
-
})
|
|
179
|
+
}),
|
|
180
180
|
),
|
|
181
181
|
},
|
|
182
182
|
returns: v.null(),
|
|
@@ -186,8 +186,8 @@ export const updateBatch = mutation({
|
|
|
186
186
|
ctx.db.patch(embedding.id, {
|
|
187
187
|
model: embedding.model,
|
|
188
188
|
vector: embedding.vector,
|
|
189
|
-
})
|
|
190
|
-
)
|
|
189
|
+
}),
|
|
190
|
+
),
|
|
191
191
|
);
|
|
192
192
|
},
|
|
193
193
|
});
|
|
@@ -31,7 +31,7 @@ const embeddings = {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export const vEmbeddingsWithoutDenormalizedFields = v.object(
|
|
34
|
-
omit(embeddings, ["model_table_userId", "model_table_threadId"])
|
|
34
|
+
omit(embeddings, ["model_table_userId", "model_table_threadId"]),
|
|
35
35
|
);
|
|
36
36
|
export type EmbeddingsWithoutDenormalizedFields = Infer<
|
|
37
37
|
typeof vEmbeddingsWithoutDenormalizedFields
|
|
@@ -71,17 +71,17 @@ export const VectorDimensions = [
|
|
|
71
71
|
128, 256, 512, 768, 1024, 1408, 1536, 2048, 3072, 4096,
|
|
72
72
|
] as const;
|
|
73
73
|
export function validateVectorDimension(
|
|
74
|
-
dimension: number
|
|
74
|
+
dimension: number,
|
|
75
75
|
): asserts dimension is VectorDimension {
|
|
76
76
|
if (!VectorDimensions.includes(dimension as VectorDimension)) {
|
|
77
77
|
throw new Error(
|
|
78
|
-
`Unsupported vector dimension${dimension}. Supported: ${VectorDimensions.join(", ")}
|
|
78
|
+
`Unsupported vector dimension${dimension}. Supported: ${VectorDimensions.join(", ")}`,
|
|
79
79
|
);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
export type VectorDimension = (typeof VectorDimensions)[number];
|
|
83
83
|
export const VectorTableNames = VectorDimensions.map(
|
|
84
|
-
(d) => `embeddings_${d}
|
|
84
|
+
(d) => `embeddings_${d}`,
|
|
85
85
|
) as `embeddings_${(typeof VectorDimensions)[number]}`[];
|
|
86
86
|
export type VectorTableName = (typeof VectorTableNames)[number];
|
|
87
87
|
export type VectorTableId = GenericId<(typeof VectorTableNames)[number]>;
|
|
@@ -89,7 +89,7 @@ export type VectorTableId = GenericId<(typeof VectorTableNames)[number]>;
|
|
|
89
89
|
export const vVectorDimension = literals(...VectorDimensions);
|
|
90
90
|
export const vVectorTableName = literals(...VectorTableNames);
|
|
91
91
|
export const vVectorId = v.union(
|
|
92
|
-
...VectorTableNames.map((name) => v.id(name))
|
|
92
|
+
...VectorTableNames.map((name) => v.id(name)),
|
|
93
93
|
) as VUnion<
|
|
94
94
|
GenericId<(typeof VectorTableNames)[number]>,
|
|
95
95
|
VId<(typeof VectorTableNames)[number]>[]
|
|
@@ -117,7 +117,7 @@ const tables: {
|
|
|
117
117
|
VectorDimensions.map((dimensions) => [
|
|
118
118
|
`embeddings_${dimensions}`,
|
|
119
119
|
table(dimensions),
|
|
120
|
-
])
|
|
120
|
+
]),
|
|
121
121
|
) as Record<
|
|
122
122
|
`embeddings_${(typeof VectorDimensions)[number]}`,
|
|
123
123
|
VectorTable<(typeof VectorDimensions)[number]>
|
package/src/mapping.test.ts
CHANGED
|
@@ -89,7 +89,7 @@ describe("mapping", () => {
|
|
|
89
89
|
const bigArr = new Uint8Array(1024 * 65).fill(1);
|
|
90
90
|
const ab = bigArr.buffer.slice(
|
|
91
91
|
bigArr.byteOffset,
|
|
92
|
-
bigArr.byteOffset + bigArr.byteLength
|
|
92
|
+
bigArr.byteOffset + bigArr.byteLength,
|
|
93
93
|
);
|
|
94
94
|
let called = false;
|
|
95
95
|
const ctx = {
|
|
@@ -117,7 +117,7 @@ describe("mapping", () => {
|
|
|
117
117
|
const { content: ser, fileIds } = await serializeContent(
|
|
118
118
|
ctx,
|
|
119
119
|
component,
|
|
120
|
-
content
|
|
120
|
+
content,
|
|
121
121
|
);
|
|
122
122
|
expect(called).toBe(true);
|
|
123
123
|
expect(fileIds).toEqual(["file-123"]);
|
|
@@ -125,7 +125,7 @@ describe("mapping", () => {
|
|
|
125
125
|
const serArr = ser as SerializedContent;
|
|
126
126
|
expect(typeof (serArr as { data: unknown }[])[0].data).toBe("string");
|
|
127
127
|
expect((serArr as { data: unknown }[])[0].data as string).toMatch(
|
|
128
|
-
/^https
|
|
128
|
+
/^https?:\/\//,
|
|
129
129
|
);
|
|
130
130
|
});
|
|
131
131
|
|
|
@@ -133,7 +133,7 @@ describe("mapping", () => {
|
|
|
133
133
|
const arr = new Uint8Array([1, 2, 3, 4, 5]);
|
|
134
134
|
const ab = arr.buffer.slice(
|
|
135
135
|
arr.byteOffset,
|
|
136
|
-
arr.byteOffset + arr.byteLength
|
|
136
|
+
arr.byteOffset + arr.byteLength,
|
|
137
137
|
);
|
|
138
138
|
const ctx = {
|
|
139
139
|
runAction: async () => undefined,
|
package/src/mapping.ts
CHANGED
|
@@ -20,7 +20,6 @@ import type { ActionCtx, AgentComponent } from "./client/types.js";
|
|
|
20
20
|
import type { RunMutationCtx } from "./client/types.js";
|
|
21
21
|
import { MAX_FILE_SIZE, storeFile } from "./client/files.js";
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
export type AIMessageWithoutId = Omit<AIMessage, "id">;
|
|
25
24
|
|
|
26
25
|
export type SerializeUrlsAndUint8Arrays<T> = T extends URL
|
|
@@ -42,13 +41,13 @@ export type SerializedMessage = SerializeUrlsAndUint8Arrays<CoreMessage>;
|
|
|
42
41
|
export async function serializeMessage(
|
|
43
42
|
ctx: ActionCtx | RunMutationCtx,
|
|
44
43
|
component: AgentComponent,
|
|
45
|
-
messageWithId: CoreMessage & { id?: string }
|
|
44
|
+
messageWithId: CoreMessage & { id?: string },
|
|
46
45
|
): Promise<{ message: SerializedMessage; fileIds?: string[] }> {
|
|
47
46
|
const { id: _, experimental_providerMetadata, ...message } = messageWithId;
|
|
48
47
|
const { content, fileIds } = await serializeContent(
|
|
49
48
|
ctx,
|
|
50
49
|
component,
|
|
51
|
-
message.content
|
|
50
|
+
message.content,
|
|
52
51
|
);
|
|
53
52
|
return {
|
|
54
53
|
message: {
|
|
@@ -72,7 +71,7 @@ export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
|
|
|
72
71
|
ctx: ActionCtx,
|
|
73
72
|
component: AgentComponent,
|
|
74
73
|
step: StepResult<TOOLS>,
|
|
75
|
-
metadata: { model: string; provider: string }
|
|
74
|
+
metadata: { model: string; provider: string },
|
|
76
75
|
): Promise<MessageWithMetadata[]> {
|
|
77
76
|
// If there are tool results, there's another message with the tool results
|
|
78
77
|
// ref: https://github.com/vercel/ai/blob/main/packages/ai/core/generate-text/to-response-messages.ts
|
|
@@ -98,7 +97,7 @@ export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
|
|
|
98
97
|
const { message, fileIds } = await serializeMessage(
|
|
99
98
|
ctx,
|
|
100
99
|
component,
|
|
101
|
-
messageWithId
|
|
100
|
+
messageWithId,
|
|
102
101
|
);
|
|
103
102
|
return {
|
|
104
103
|
message,
|
|
@@ -109,7 +108,7 @@ export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
|
|
|
109
108
|
text: step.text,
|
|
110
109
|
fileIds,
|
|
111
110
|
};
|
|
112
|
-
})
|
|
111
|
+
}),
|
|
113
112
|
);
|
|
114
113
|
return messages;
|
|
115
114
|
}
|
|
@@ -118,7 +117,7 @@ export async function serializeObjectResult(
|
|
|
118
117
|
ctx: ActionCtx,
|
|
119
118
|
component: AgentComponent,
|
|
120
119
|
result: GenerateObjectResult<unknown>,
|
|
121
|
-
metadata: { model: string; provider: string }
|
|
120
|
+
metadata: { model: string; provider: string },
|
|
122
121
|
): Promise<{ messages: MessageWithMetadata[] }> {
|
|
123
122
|
const text = JSON.stringify(result.object);
|
|
124
123
|
|
|
@@ -147,7 +146,7 @@ export async function serializeObjectResult(
|
|
|
147
146
|
export async function serializeContent(
|
|
148
147
|
ctx: ActionCtx | RunMutationCtx,
|
|
149
148
|
component: AgentComponent,
|
|
150
|
-
content: Content
|
|
149
|
+
content: Content,
|
|
151
150
|
): Promise<{ content: SerializedContent; fileIds?: string[] }> {
|
|
152
151
|
if (typeof content === "string") {
|
|
153
152
|
return { content };
|
|
@@ -166,7 +165,9 @@ export async function serializeContent(
|
|
|
166
165
|
const { file } = await storeFile(
|
|
167
166
|
ctx,
|
|
168
167
|
component,
|
|
169
|
-
new Blob([image], {
|
|
168
|
+
new Blob([image], {
|
|
169
|
+
type: part.mimeType || guessMimeType(image),
|
|
170
|
+
}),
|
|
170
171
|
);
|
|
171
172
|
image = file.url;
|
|
172
173
|
fileIds.push(file.fileId);
|
|
@@ -179,7 +180,7 @@ export async function serializeContent(
|
|
|
179
180
|
const { file } = await storeFile(
|
|
180
181
|
ctx,
|
|
181
182
|
component,
|
|
182
|
-
new Blob([data], { type: part.mimeType })
|
|
183
|
+
new Blob([data], { type: part.mimeType }),
|
|
183
184
|
);
|
|
184
185
|
data = file.url;
|
|
185
186
|
fileIds.push(file.fileId);
|
|
@@ -192,7 +193,7 @@ export async function serializeContent(
|
|
|
192
193
|
default:
|
|
193
194
|
return part;
|
|
194
195
|
}
|
|
195
|
-
})
|
|
196
|
+
}),
|
|
196
197
|
);
|
|
197
198
|
return {
|
|
198
199
|
content: serialized as SerializedContent,
|
|
@@ -289,7 +290,7 @@ export function guessMimeType(buf: ArrayBuffer | string): string {
|
|
|
289
290
|
* @returns The serialized data as an ArrayBuffer or the URL as a string.
|
|
290
291
|
*/
|
|
291
292
|
export function serializeDataOrUrl(
|
|
292
|
-
dataOrUrl: DataContent | URL
|
|
293
|
+
dataOrUrl: DataContent | URL,
|
|
293
294
|
): ArrayBuffer | string {
|
|
294
295
|
if (typeof dataOrUrl === "string") {
|
|
295
296
|
return dataOrUrl;
|
|
@@ -302,12 +303,12 @@ export function serializeDataOrUrl(
|
|
|
302
303
|
}
|
|
303
304
|
return dataOrUrl.buffer.slice(
|
|
304
305
|
dataOrUrl.byteOffset,
|
|
305
|
-
dataOrUrl.byteOffset + dataOrUrl.byteLength
|
|
306
|
+
dataOrUrl.byteOffset + dataOrUrl.byteLength,
|
|
306
307
|
) as ArrayBuffer;
|
|
307
308
|
}
|
|
308
309
|
|
|
309
310
|
export function deserializeUrl(
|
|
310
|
-
urlOrString: string | ArrayBuffer
|
|
311
|
+
urlOrString: string | ArrayBuffer,
|
|
311
312
|
): URL | DataContent {
|
|
312
313
|
if (typeof urlOrString === "string") {
|
|
313
314
|
if (
|
|
@@ -323,7 +324,7 @@ export function deserializeUrl(
|
|
|
323
324
|
|
|
324
325
|
export function toUIFilePart(part: ImagePart | FilePart): FileUIPart {
|
|
325
326
|
const dataOrUrl = serializeDataOrUrl(
|
|
326
|
-
part.type === "image" ? part.image : part.data
|
|
327
|
+
part.type === "image" ? part.image : part.data,
|
|
327
328
|
);
|
|
328
329
|
|
|
329
330
|
return {
|
|
@@ -346,7 +347,7 @@ export function promptOrMessagesToCoreMessages(args: {
|
|
|
346
347
|
const messages: CoreMessage[] = [];
|
|
347
348
|
assert(
|
|
348
349
|
args.prompt || args.messages || args.promptMessageId,
|
|
349
|
-
"messages or prompt or promptMessageId is required"
|
|
350
|
+
"messages or prompt or promptMessageId is required",
|
|
350
351
|
);
|
|
351
352
|
if (args.messages) {
|
|
352
353
|
if (
|
|
@@ -357,7 +358,7 @@ export function promptOrMessagesToCoreMessages(args: {
|
|
|
357
358
|
(m.role === "data" || // UI-only role
|
|
358
359
|
"toolInvocations" in m || // UI-specific field
|
|
359
360
|
"parts" in m || // UI-specific field
|
|
360
|
-
"experimental_attachments" in m)
|
|
361
|
+
"experimental_attachments" in m),
|
|
361
362
|
)
|
|
362
363
|
) {
|
|
363
364
|
messages.push(...convertToCoreMessages(args.messages as AIMessage[]));
|
package/src/react/deltas.test.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { omit } from "convex-helpers";
|
|
|
10
10
|
function makeStreamMessage(
|
|
11
11
|
streamId: string,
|
|
12
12
|
order: number,
|
|
13
|
-
stepOrder: number
|
|
13
|
+
stepOrder: number,
|
|
14
14
|
): StreamMessage {
|
|
15
15
|
return {
|
|
16
16
|
streamId,
|
|
@@ -23,7 +23,7 @@ function makeDelta(
|
|
|
23
23
|
streamId: string,
|
|
24
24
|
start: number,
|
|
25
25
|
end: number,
|
|
26
|
-
parts: TextStreamPart[]
|
|
26
|
+
parts: TextStreamPart[],
|
|
27
27
|
): StreamDelta {
|
|
28
28
|
return {
|
|
29
29
|
streamId,
|
|
@@ -44,7 +44,7 @@ describe("mergeDeltas", () => {
|
|
|
44
44
|
"thread1",
|
|
45
45
|
streamMessages,
|
|
46
46
|
[],
|
|
47
|
-
deltas
|
|
47
|
+
deltas,
|
|
48
48
|
);
|
|
49
49
|
expect(messages).toHaveLength(1);
|
|
50
50
|
expect(messages[0].text).toBe("Hello");
|
|
@@ -66,7 +66,7 @@ describe("mergeDeltas", () => {
|
|
|
66
66
|
"thread1",
|
|
67
67
|
streamMessages,
|
|
68
68
|
[],
|
|
69
|
-
deltas
|
|
69
|
+
deltas,
|
|
70
70
|
);
|
|
71
71
|
expect(messages).toHaveLength(1);
|
|
72
72
|
expect(messages[0].text).toBe("Hello World!");
|
|
@@ -99,7 +99,7 @@ describe("mergeDeltas", () => {
|
|
|
99
99
|
"thread1",
|
|
100
100
|
streamMessages,
|
|
101
101
|
[],
|
|
102
|
-
deltas
|
|
102
|
+
deltas,
|
|
103
103
|
);
|
|
104
104
|
expect(messages).toHaveLength(2);
|
|
105
105
|
expect(messages[0].message?.role).toBe("assistant");
|
|
@@ -134,7 +134,7 @@ describe("mergeDeltas", () => {
|
|
|
134
134
|
"thread1",
|
|
135
135
|
streamMessages,
|
|
136
136
|
[],
|
|
137
|
-
deltas
|
|
137
|
+
deltas,
|
|
138
138
|
);
|
|
139
139
|
expect(messages).toHaveLength(0);
|
|
140
140
|
expect(changed).toBe(false);
|
|
@@ -195,7 +195,7 @@ describe("mergeDeltas", () => {
|
|
|
195
195
|
expect(messages).toHaveLength(1);
|
|
196
196
|
if (Array.isArray(messages[0].message?.content)) {
|
|
197
197
|
const reasoningParts = messages[0].message.content.filter(
|
|
198
|
-
(p) => p.type === "reasoning"
|
|
198
|
+
(p) => p.type === "reasoning",
|
|
199
199
|
);
|
|
200
200
|
expect(reasoningParts).toHaveLength(1);
|
|
201
201
|
expect(reasoningParts[0].text).toBe("I'm thinking... Still thinking...");
|
|
@@ -216,7 +216,7 @@ describe("mergeDeltas", () => {
|
|
|
216
216
|
"thread1",
|
|
217
217
|
streamMessage,
|
|
218
218
|
undefined,
|
|
219
|
-
deltas
|
|
219
|
+
deltas,
|
|
220
220
|
);
|
|
221
221
|
expect(result.messages).toHaveLength(1);
|
|
222
222
|
expect(result.messages[0].text).toBe("Hello World!");
|
|
@@ -225,7 +225,7 @@ describe("mergeDeltas", () => {
|
|
|
225
225
|
"thread1",
|
|
226
226
|
streamMessage,
|
|
227
227
|
result,
|
|
228
|
-
deltas
|
|
228
|
+
deltas,
|
|
229
229
|
);
|
|
230
230
|
expect(result.messages).toHaveLength(1);
|
|
231
231
|
expect(result.messages[0].text).toBe("Hello World!");
|
|
@@ -238,7 +238,7 @@ describe("mergeDeltas", () => {
|
|
|
238
238
|
"thread1",
|
|
239
239
|
streamMessage,
|
|
240
240
|
result,
|
|
241
|
-
moreDeltas
|
|
241
|
+
moreDeltas,
|
|
242
242
|
);
|
|
243
243
|
expect(changed).toBe(true);
|
|
244
244
|
expect(result.messages).toHaveLength(1);
|
|
@@ -248,7 +248,7 @@ describe("mergeDeltas", () => {
|
|
|
248
248
|
"thread1",
|
|
249
249
|
streamMessage,
|
|
250
250
|
result,
|
|
251
|
-
moreDeltas
|
|
251
|
+
moreDeltas,
|
|
252
252
|
);
|
|
253
253
|
expect(changed).toBe(false);
|
|
254
254
|
expect(result.messages).toHaveLength(1);
|
|
@@ -280,27 +280,27 @@ describe("mergeDeltas", () => {
|
|
|
280
280
|
"thread1",
|
|
281
281
|
streamMessages,
|
|
282
282
|
[],
|
|
283
|
-
deltas
|
|
283
|
+
deltas,
|
|
284
284
|
);
|
|
285
285
|
const [messages2, streams2, changed2] = mergeDeltas(
|
|
286
286
|
"thread1",
|
|
287
287
|
streamMessages,
|
|
288
288
|
[],
|
|
289
|
-
deltas
|
|
289
|
+
deltas,
|
|
290
290
|
);
|
|
291
291
|
expect(messages1.map((m) => omit(m, ["_creationTime"]))).toEqual(
|
|
292
|
-
messages2.map((m) => omit(m, ["_creationTime"]))
|
|
292
|
+
messages2.map((m) => omit(m, ["_creationTime"])),
|
|
293
293
|
);
|
|
294
294
|
expect(
|
|
295
295
|
streams1.map((s) => ({
|
|
296
296
|
...s,
|
|
297
297
|
messages: s.messages.map((m) => omit(m, ["_creationTime"])),
|
|
298
|
-
}))
|
|
298
|
+
})),
|
|
299
299
|
).toEqual(
|
|
300
300
|
streams2.map((s) => ({
|
|
301
301
|
...s,
|
|
302
302
|
messages: s.messages.map((m) => omit(m, ["_creationTime"])),
|
|
303
|
-
}))
|
|
303
|
+
})),
|
|
304
304
|
);
|
|
305
305
|
expect(changed1).toBe(changed2);
|
|
306
306
|
// Inputs should remain unchanged
|
package/src/react/deltas.ts
CHANGED
|
@@ -20,7 +20,7 @@ export function mergeDeltas(
|
|
|
20
20
|
cursor: number;
|
|
21
21
|
messages: MessageDoc[];
|
|
22
22
|
}>,
|
|
23
|
-
allDeltas: StreamDelta[]
|
|
23
|
+
allDeltas: StreamDelta[],
|
|
24
24
|
): [
|
|
25
25
|
MessageDoc[],
|
|
26
26
|
Array<{ streamId: string; cursor: number; messages: MessageDoc[] }>,
|
|
@@ -35,16 +35,16 @@ export function mergeDeltas(
|
|
|
35
35
|
let changed = false;
|
|
36
36
|
for (const streamMessage of streamMessages) {
|
|
37
37
|
const deltas = allDeltas.filter(
|
|
38
|
-
(d) => d.streamId === streamMessage.streamId
|
|
38
|
+
(d) => d.streamId === streamMessage.streamId,
|
|
39
39
|
);
|
|
40
40
|
const existing = existingStreams.find(
|
|
41
|
-
(s) => s.streamId === streamMessage.streamId
|
|
41
|
+
(s) => s.streamId === streamMessage.streamId,
|
|
42
42
|
);
|
|
43
43
|
const [newStream, messageChanged] = applyDeltasToStreamMessage(
|
|
44
44
|
threadId,
|
|
45
45
|
streamMessage,
|
|
46
46
|
existing,
|
|
47
|
-
deltas
|
|
47
|
+
deltas,
|
|
48
48
|
);
|
|
49
49
|
newStreams.push(newStream);
|
|
50
50
|
if (messageChanged) changed = true;
|
|
@@ -69,7 +69,7 @@ export function applyDeltasToStreamMessage(
|
|
|
69
69
|
existing:
|
|
70
70
|
| { streamId: string; cursor: number; messages: MessageDoc[] }
|
|
71
71
|
| undefined,
|
|
72
|
-
deltas: StreamDelta[]
|
|
72
|
+
deltas: StreamDelta[],
|
|
73
73
|
): [{ streamId: string; cursor: number; messages: MessageDoc[] }, boolean] {
|
|
74
74
|
let changed = false;
|
|
75
75
|
let cursor = existing?.cursor ?? 0;
|
|
@@ -82,17 +82,17 @@ export function applyDeltasToStreamMessage(
|
|
|
82
82
|
if (cursor !== delta.start) {
|
|
83
83
|
if (cursor >= delta.end) {
|
|
84
84
|
console.debug(
|
|
85
|
-
`Got duplicate delta for stream ${delta.streamId} at ${delta.start}
|
|
85
|
+
`Got duplicate delta for stream ${delta.streamId} at ${delta.start}`,
|
|
86
86
|
);
|
|
87
87
|
continue;
|
|
88
88
|
} else if (cursor < delta.start) {
|
|
89
89
|
console.warn(
|
|
90
|
-
`Got delta for stream ${delta.streamId} that has a gap ${cursor} -> ${delta.start}
|
|
90
|
+
`Got delta for stream ${delta.streamId} that has a gap ${cursor} -> ${delta.start}`,
|
|
91
91
|
);
|
|
92
92
|
continue;
|
|
93
93
|
} else {
|
|
94
94
|
throw new Error(
|
|
95
|
-
`Got unexpected delta for stream ${delta.streamId}: delta: ${delta.start} -> ${delta.end} existing cursor: ${cursor}
|
|
95
|
+
`Got unexpected delta for stream ${delta.streamId}: delta: ${delta.start} -> ${delta.end} existing cursor: ${cursor}`,
|
|
96
96
|
);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -129,7 +129,7 @@ export function applyDeltasToStreamMessage(
|
|
|
129
129
|
threadId,
|
|
130
130
|
streamMessage,
|
|
131
131
|
parts[0]!,
|
|
132
|
-
existingMessages.length
|
|
132
|
+
existingMessages.length,
|
|
133
133
|
);
|
|
134
134
|
parts = parts.slice(1);
|
|
135
135
|
currentMessage = newMessage;
|
|
@@ -153,7 +153,7 @@ export function applyDeltasToStreamMessage(
|
|
|
153
153
|
threadId,
|
|
154
154
|
streamMessage,
|
|
155
155
|
part,
|
|
156
|
-
newStream.messages.length
|
|
156
|
+
newStream.messages.length,
|
|
157
157
|
);
|
|
158
158
|
lastContent = getLastContent(currentMessage);
|
|
159
159
|
newStream.messages.push(currentMessage);
|
|
@@ -237,7 +237,7 @@ export function applyDeltasToStreamMessage(
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
function cloneMessageAndContent(
|
|
240
|
-
message: Message | undefined
|
|
240
|
+
message: Message | undefined,
|
|
241
241
|
): Message | undefined {
|
|
242
242
|
return (
|
|
243
243
|
message &&
|
|
@@ -258,7 +258,7 @@ function getLastContent(message: MessageDoc) {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
function statusFromStreamStatus(
|
|
261
|
-
status: StreamMessage["status"]
|
|
261
|
+
status: StreamMessage["status"],
|
|
262
262
|
): MessageStatus {
|
|
263
263
|
switch (status) {
|
|
264
264
|
case "streaming":
|
|
@@ -276,7 +276,7 @@ export function createStreamingMessage(
|
|
|
276
276
|
threadId: string,
|
|
277
277
|
message: StreamMessage,
|
|
278
278
|
part: TextStreamPart,
|
|
279
|
-
index: number
|
|
279
|
+
index: number,
|
|
280
280
|
): MessageDoc {
|
|
281
281
|
const { streamId, ...rest } = message;
|
|
282
282
|
const metadata: MessageDoc = {
|
package/src/react/index.ts
CHANGED
|
@@ -86,7 +86,7 @@ export function useThreadMessages<
|
|
|
86
86
|
stream?: Query extends ThreadStreamQuery
|
|
87
87
|
? boolean
|
|
88
88
|
: ErrorMessage<"To enable streaming, your query must take in streamArgs: vStreamArgs and return a streams object returned from agent.syncStreams. See docs.">;
|
|
89
|
-
}
|
|
89
|
+
},
|
|
90
90
|
): UsePaginatedQueryResult<
|
|
91
91
|
ThreadMessagesResult<Query> & { streaming?: boolean }
|
|
92
92
|
> {
|
|
@@ -94,7 +94,7 @@ export function useThreadMessages<
|
|
|
94
94
|
const paginated = usePaginatedQuery(
|
|
95
95
|
query,
|
|
96
96
|
args as PaginatedQueryArgs<Query> | "skip",
|
|
97
|
-
{ initialNumItems: options.initialNumItems }
|
|
97
|
+
{ initialNumItems: options.initialNumItems },
|
|
98
98
|
);
|
|
99
99
|
|
|
100
100
|
// These are streaming messages that will not include full messages.
|
|
@@ -107,7 +107,7 @@ export function useThreadMessages<
|
|
|
107
107
|
args === "skip" ||
|
|
108
108
|
paginated.status === "LoadingFirstPage"
|
|
109
109
|
? "skip"
|
|
110
|
-
: { ...args, startOrder: paginated.results.at(-1)?.order }
|
|
110
|
+
: { ...args, startOrder: paginated.results.at(-1)?.order },
|
|
111
111
|
);
|
|
112
112
|
|
|
113
113
|
const merged = useMemo(() => {
|
|
@@ -122,14 +122,14 @@ export function useThreadMessages<
|
|
|
122
122
|
.map((m) => ({ ...m, streaming: false }))
|
|
123
123
|
.concat(streamListMessages)
|
|
124
124
|
.sort((a, b) =>
|
|
125
|
-
a.order === b.order ? a.stepOrder - b.stepOrder : a.order - b.order
|
|
125
|
+
a.order === b.order ? a.stepOrder - b.stepOrder : a.order - b.order,
|
|
126
126
|
)
|
|
127
127
|
// They shouldn't overlap, but check for duplicates just in case.
|
|
128
128
|
.filter(
|
|
129
129
|
(m, i, arr) =>
|
|
130
130
|
!arr[i - 1] ||
|
|
131
131
|
m.order !== arr[i - 1].order ||
|
|
132
|
-
m.stepOrder !== arr[i - 1].stepOrder
|
|
132
|
+
m.stepOrder !== arr[i - 1].stepOrder,
|
|
133
133
|
),
|
|
134
134
|
};
|
|
135
135
|
}, [paginated, streamMessages]);
|
|
@@ -154,7 +154,7 @@ export function useStreamingThreadMessages<
|
|
|
154
154
|
Query extends ThreadStreamQuery<any, any>,
|
|
155
155
|
>(
|
|
156
156
|
query: Query,
|
|
157
|
-
args: (ThreadMessagesArgs<Query> & { startOrder?: number }) | "skip"
|
|
157
|
+
args: (ThreadMessagesArgs<Query> & { startOrder?: number }) | "skip",
|
|
158
158
|
): Array<ThreadMessagesResult<Query>> | undefined {
|
|
159
159
|
// Invariant: streamMessages[streamId] is comprised of all deltas up to the
|
|
160
160
|
// cursor. There can be multiple messages in the same stream, e.g. for tool
|
|
@@ -179,7 +179,7 @@ export function useStreamingThreadMessages<
|
|
|
179
179
|
kind: "list",
|
|
180
180
|
startOrder: startOrderRef.current,
|
|
181
181
|
} as StreamArgs,
|
|
182
|
-
} as FunctionArgs<Query>)
|
|
182
|
+
} as FunctionArgs<Query>),
|
|
183
183
|
) as
|
|
184
184
|
| { streams: Extract<SyncStreamsReturnValue, { kind: "list" }> }
|
|
185
185
|
| undefined;
|
|
@@ -204,7 +204,7 @@ export function useStreamingThreadMessages<
|
|
|
204
204
|
...queryArgs,
|
|
205
205
|
paginationOpts: { cursor: null, numItems: 0 },
|
|
206
206
|
streamArgs: { kind: "deltas", cursors } as StreamArgs,
|
|
207
|
-
} as FunctionArgs<Query>)
|
|
207
|
+
} as FunctionArgs<Query>),
|
|
208
208
|
) as
|
|
209
209
|
| { streams: Extract<SyncStreamsReturnValue, { kind: "deltas" }> }
|
|
210
210
|
| undefined;
|
|
@@ -220,7 +220,7 @@ export function useStreamingThreadMessages<
|
|
|
220
220
|
threadId,
|
|
221
221
|
streamList.streams.messages,
|
|
222
222
|
streams,
|
|
223
|
-
cursorQuery?.streams?.deltas ?? []
|
|
223
|
+
cursorQuery?.streams?.deltas ?? [],
|
|
224
224
|
);
|
|
225
225
|
}, [threadId, cursorQuery, streams, streamList]);
|
|
226
226
|
// Now assemble the chunks into messages
|
|
@@ -248,7 +248,7 @@ export function useStreamingThreadMessages<
|
|
|
248
248
|
export function useStreamingText(
|
|
249
249
|
url: string,
|
|
250
250
|
threadId: string | null,
|
|
251
|
-
token?: string
|
|
251
|
+
token?: string,
|
|
252
252
|
) {
|
|
253
253
|
const [text, setText] = useState("");
|
|
254
254
|
const [loading, setLoading] = useState(false);
|
|
@@ -295,7 +295,7 @@ export function useStreamingText(
|
|
|
295
295
|
setLoading(false);
|
|
296
296
|
}
|
|
297
297
|
},
|
|
298
|
-
[threadId, token, url]
|
|
298
|
+
[threadId, token, url],
|
|
299
299
|
);
|
|
300
300
|
return [{ text, loading, error }, readStream] as const;
|
|
301
301
|
}
|
|
@@ -4,10 +4,10 @@ import type { OptimisticLocalStore } from "convex/browser";
|
|
|
4
4
|
import type { ThreadQuery } from "./types.js";
|
|
5
5
|
|
|
6
6
|
export function optimisticallySendMessage(
|
|
7
|
-
query: ThreadQuery<unknown, MessageDoc
|
|
7
|
+
query: ThreadQuery<unknown, MessageDoc>,
|
|
8
8
|
): (
|
|
9
9
|
store: OptimisticLocalStore,
|
|
10
|
-
args: { threadId: string; prompt: string }
|
|
10
|
+
args: { threadId: string; prompt: string },
|
|
11
11
|
) => void {
|
|
12
12
|
return (store, args) => {
|
|
13
13
|
const queries = store.getAllQueries(query);
|