@convex-dev/agent 0.1.18 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/createTool.d.ts +30 -11
- package/dist/client/createTool.d.ts.map +1 -1
- package/dist/client/createTool.js +24 -10
- package/dist/client/createTool.js.map +1 -1
- package/dist/client/defaultComponent.d.ts +11 -0
- package/dist/client/defaultComponent.d.ts.map +1 -0
- package/dist/client/defaultComponent.js +7 -0
- package/dist/client/defaultComponent.js.map +1 -0
- package/dist/client/definePlaygroundAPI.d.ts +68 -23
- package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/client/definePlaygroundAPI.js +7 -20
- package/dist/client/definePlaygroundAPI.js.map +1 -1
- package/dist/client/files.d.ts +14 -4
- package/dist/client/files.d.ts.map +1 -1
- package/dist/client/files.js +66 -11
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +323 -780
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +445 -543
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +256 -0
- package/dist/client/messages.d.ts.map +1 -0
- package/dist/client/messages.js +97 -0
- package/dist/client/messages.js.map +1 -0
- package/dist/client/search.d.ts +35 -12
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +21 -4
- package/dist/client/search.js.map +1 -1
- package/dist/client/streaming.d.ts +21 -21
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/streaming.js +35 -40
- package/dist/client/streaming.js.map +1 -1
- package/dist/client/threads.d.ts +46 -0
- package/dist/client/threads.d.ts.map +1 -0
- package/dist/client/threads.js +49 -0
- package/dist/client/threads.js.map +1 -0
- package/dist/client/types.d.ts +159 -75
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +261 -209
- package/dist/component/messages.d.ts +314 -105
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +141 -83
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +631 -870
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +11 -16
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.d.ts +29 -149
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +104 -60
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts +4 -4
- package/dist/component/users.d.ts +3 -3
- package/dist/mapping.d.ts +15 -16
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +208 -62
- package/dist/mapping.js.map +1 -1
- package/dist/parts.d.ts +3 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +34 -0
- package/dist/parts.js.map +1 -0
- package/dist/react/SmoothText.d.ts +5 -0
- package/dist/react/SmoothText.d.ts.map +1 -0
- package/dist/react/SmoothText.js +6 -0
- package/dist/react/SmoothText.js.map +1 -0
- package/dist/react/deltas.d.ts +3 -5
- package/dist/react/deltas.d.ts.map +1 -1
- package/dist/react/deltas.js +89 -53
- package/dist/react/deltas.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +31 -11
- package/dist/react/index.js.map +1 -1
- package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
- package/dist/react/optimisticallySendMessage.js +2 -8
- package/dist/react/optimisticallySendMessage.js.map +1 -1
- package/dist/react/toUIMessages.d.ts +5 -4
- package/dist/react/toUIMessages.d.ts.map +1 -1
- package/dist/react/toUIMessages.js +103 -40
- package/dist/react/toUIMessages.js.map +1 -1
- package/dist/react/useSmoothText.d.ts +13 -12
- package/dist/react/useSmoothText.d.ts.map +1 -1
- package/dist/react/useSmoothText.js +1 -1
- package/dist/react/useSmoothText.js.map +1 -1
- package/dist/validators.d.ts +751 -1464
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +39 -80
- package/dist/validators.js.map +1 -1
- package/package.json +34 -30
- package/src/client/createTool.ts +66 -40
- package/src/client/defaultComponent.ts +17 -0
- package/src/client/definePlaygroundAPI.ts +17 -31
- package/src/client/files.ts +94 -17
- package/src/client/index.test.ts +28 -38
- package/src/client/index.ts +785 -977
- package/src/client/messages.ts +205 -0
- package/src/client/search.ts +37 -10
- package/src/client/streaming.ts +53 -56
- package/src/client/threads.ts +78 -0
- package/src/client/types.ts +239 -131
- package/src/component/_generated/api.d.ts +261 -209
- package/src/component/messages.test.ts +182 -40
- package/src/component/messages.ts +165 -108
- package/src/component/schema.ts +11 -17
- package/src/component/streams.ts +180 -69
- package/src/mapping.ts +253 -96
- package/src/parts.ts +39 -0
- package/src/react/SmoothText.tsx +9 -0
- package/src/react/deltas.test.ts +38 -42
- package/src/react/deltas.ts +116 -65
- package/src/react/index.ts +35 -13
- package/src/react/optimisticallySendMessage.ts +2 -8
- package/src/react/toUIMessages.test.ts +154 -36
- package/src/react/toUIMessages.ts +136 -57
- package/src/react/useSmoothText.ts +14 -15
- package/src/validators.test.ts +1 -117
- package/src/validators.ts +57 -107
- package/dist/client/listMessages.d.ts +0 -22
- package/dist/client/listMessages.d.ts.map +0 -1
- package/dist/client/listMessages.js +0 -25
- package/dist/client/listMessages.js.map +0 -1
- package/src/client/listMessages.ts +0 -38
package/src/react/deltas.test.ts
CHANGED
|
@@ -1,36 +1,24 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { mergeDeltas, applyDeltasToStreamMessage } from "./deltas.js";
|
|
3
|
-
import type {
|
|
4
|
-
StreamMessage,
|
|
5
|
-
StreamDelta,
|
|
6
|
-
TextStreamPart,
|
|
7
|
-
} from "../validators.js";
|
|
3
|
+
import type { StreamMessage, StreamDelta } from "../validators.js";
|
|
8
4
|
import { omit } from "convex-helpers";
|
|
5
|
+
import type { TextStreamPart, ToolSet } from "ai";
|
|
9
6
|
|
|
10
7
|
function makeStreamMessage(
|
|
11
8
|
streamId: string,
|
|
12
9
|
order: number,
|
|
13
10
|
stepOrder: number,
|
|
14
11
|
): StreamMessage {
|
|
15
|
-
return {
|
|
16
|
-
streamId,
|
|
17
|
-
order,
|
|
18
|
-
stepOrder,
|
|
19
|
-
} as StreamMessage;
|
|
12
|
+
return { streamId, order, stepOrder } as StreamMessage;
|
|
20
13
|
}
|
|
21
14
|
|
|
22
15
|
function makeDelta(
|
|
23
16
|
streamId: string,
|
|
24
17
|
start: number,
|
|
25
18
|
end: number,
|
|
26
|
-
parts: TextStreamPart[],
|
|
19
|
+
parts: TextStreamPart<ToolSet>[],
|
|
27
20
|
): StreamDelta {
|
|
28
|
-
return {
|
|
29
|
-
streamId,
|
|
30
|
-
start,
|
|
31
|
-
end,
|
|
32
|
-
parts,
|
|
33
|
-
};
|
|
21
|
+
return { streamId, start, end, parts };
|
|
34
22
|
}
|
|
35
23
|
|
|
36
24
|
describe("mergeDeltas", () => {
|
|
@@ -38,7 +26,9 @@ describe("mergeDeltas", () => {
|
|
|
38
26
|
const streamId = "s1";
|
|
39
27
|
const streamMessages = [makeStreamMessage(streamId, 1, 0)];
|
|
40
28
|
const deltas = [
|
|
41
|
-
makeDelta(streamId, 0, 5, [
|
|
29
|
+
makeDelta(streamId, 0, 5, [
|
|
30
|
+
{ type: "text-delta", id: "1", text: "Hello" },
|
|
31
|
+
]),
|
|
42
32
|
];
|
|
43
33
|
const [messages, newStreams, changed] = mergeDeltas(
|
|
44
34
|
"thread1",
|
|
@@ -57,9 +47,11 @@ describe("mergeDeltas", () => {
|
|
|
57
47
|
const streamId = "s1";
|
|
58
48
|
const streamMessages = [makeStreamMessage(streamId, 1, 0)];
|
|
59
49
|
const deltas = [
|
|
60
|
-
makeDelta(streamId, 0, 5, [
|
|
50
|
+
makeDelta(streamId, 0, 5, [
|
|
51
|
+
{ type: "text-delta", id: "1", text: "Hello" },
|
|
52
|
+
]),
|
|
61
53
|
makeDelta(streamId, 5, 11, [
|
|
62
|
-
{ type: "text-delta",
|
|
54
|
+
{ type: "text-delta", id: "2", text: " World!" },
|
|
63
55
|
]),
|
|
64
56
|
];
|
|
65
57
|
const [messages, newStreams, changed] = mergeDeltas(
|
|
@@ -83,7 +75,7 @@ describe("mergeDeltas", () => {
|
|
|
83
75
|
type: "tool-call",
|
|
84
76
|
toolCallId: "call1",
|
|
85
77
|
toolName: "myTool",
|
|
86
|
-
|
|
78
|
+
input: "",
|
|
87
79
|
},
|
|
88
80
|
]),
|
|
89
81
|
makeDelta(streamId, 1, 2, [
|
|
@@ -91,7 +83,8 @@ describe("mergeDeltas", () => {
|
|
|
91
83
|
type: "tool-result",
|
|
92
84
|
toolCallId: "call1",
|
|
93
85
|
toolName: "myTool",
|
|
94
|
-
|
|
86
|
+
input: undefined,
|
|
87
|
+
output: "42",
|
|
95
88
|
},
|
|
96
89
|
]),
|
|
97
90
|
];
|
|
@@ -106,12 +99,7 @@ describe("mergeDeltas", () => {
|
|
|
106
99
|
expect(messages[0].tool).toBe(true);
|
|
107
100
|
const content = messages[0].message?.content;
|
|
108
101
|
expect(content).toEqual([
|
|
109
|
-
{
|
|
110
|
-
type: "tool-call",
|
|
111
|
-
toolCallId: "call1",
|
|
112
|
-
toolName: "myTool",
|
|
113
|
-
args: "",
|
|
114
|
-
},
|
|
102
|
+
{ type: "tool-call", toolCallId: "call1", toolName: "myTool", args: "" },
|
|
115
103
|
]);
|
|
116
104
|
expect(messages[1].message?.role).toBe("tool");
|
|
117
105
|
expect(messages[1].tool).toBe(true);
|
|
@@ -145,8 +133,8 @@ describe("mergeDeltas", () => {
|
|
|
145
133
|
const s1 = makeStreamMessage("s1", 1, 0);
|
|
146
134
|
const s2 = makeStreamMessage("s2", 2, 0);
|
|
147
135
|
const deltas = [
|
|
148
|
-
makeDelta("s2", 0, 3, [{ type: "text-delta",
|
|
149
|
-
makeDelta("s1", 0, 3, [{ type: "text-delta",
|
|
136
|
+
makeDelta("s2", 0, 3, [{ type: "text-delta", id: "1", text: "B" }]),
|
|
137
|
+
makeDelta("s1", 0, 3, [{ type: "text-delta", id: "2", text: "A" }]),
|
|
150
138
|
];
|
|
151
139
|
const [messages, _, changed] = mergeDeltas("thread1", [s2, s1], [], deltas);
|
|
152
140
|
expect(messages).toHaveLength(2);
|
|
@@ -162,11 +150,13 @@ describe("mergeDeltas", () => {
|
|
|
162
150
|
const streamId = "s4";
|
|
163
151
|
const streamMessages = [makeStreamMessage(streamId, 4, 0)];
|
|
164
152
|
const deltas = [
|
|
165
|
-
makeDelta(streamId, 0, 5, [
|
|
153
|
+
makeDelta(streamId, 0, 5, [
|
|
154
|
+
{ type: "text-delta", id: "1", text: "Hello" },
|
|
155
|
+
]),
|
|
166
156
|
makeDelta(streamId, 5, 11, [
|
|
167
|
-
{ type: "text-delta",
|
|
157
|
+
{ type: "text-delta", id: "2", text: " World!" },
|
|
168
158
|
]),
|
|
169
|
-
makeDelta(streamId, 11, 12, [{ type: "text-delta",
|
|
159
|
+
makeDelta(streamId, 11, 12, [{ type: "text-delta", id: "3", text: "!" }]),
|
|
170
160
|
];
|
|
171
161
|
const [messages] = mergeDeltas("thread1", streamMessages, [], deltas);
|
|
172
162
|
expect(messages).toHaveLength(1);
|
|
@@ -185,10 +175,10 @@ describe("mergeDeltas", () => {
|
|
|
185
175
|
const streamMessages = [makeStreamMessage(streamId, 6, 0)];
|
|
186
176
|
const deltas = [
|
|
187
177
|
makeDelta(streamId, 0, 1, [
|
|
188
|
-
{ type: "reasoning",
|
|
178
|
+
{ type: "reasoning-delta", id: "1", text: "I'm thinking..." },
|
|
189
179
|
]),
|
|
190
180
|
makeDelta(streamId, 1, 2, [
|
|
191
|
-
{ type: "reasoning",
|
|
181
|
+
{ type: "reasoning-delta", id: "2", text: " Still thinking..." },
|
|
192
182
|
]),
|
|
193
183
|
];
|
|
194
184
|
const [messages] = mergeDeltas("thread1", streamMessages, [], deltas);
|
|
@@ -206,9 +196,11 @@ describe("mergeDeltas", () => {
|
|
|
206
196
|
const streamId = "s7";
|
|
207
197
|
const streamMessage = makeStreamMessage(streamId, 7, 0);
|
|
208
198
|
const deltas = [
|
|
209
|
-
makeDelta(streamId, 0, 5, [
|
|
199
|
+
makeDelta(streamId, 0, 5, [
|
|
200
|
+
{ type: "text-delta", id: "1", text: "Hello" },
|
|
201
|
+
]),
|
|
210
202
|
makeDelta(streamId, 5, 11, [
|
|
211
|
-
{ type: "text-delta",
|
|
203
|
+
{ type: "text-delta", id: "2", text: " World!" },
|
|
212
204
|
]),
|
|
213
205
|
];
|
|
214
206
|
// First call: apply both deltas
|
|
@@ -232,7 +224,7 @@ describe("mergeDeltas", () => {
|
|
|
232
224
|
// Third call: add a new delta
|
|
233
225
|
const moreDeltas = [
|
|
234
226
|
...deltas,
|
|
235
|
-
makeDelta(streamId, 11, 12, [{ type: "text-delta",
|
|
227
|
+
makeDelta(streamId, 11, 12, [{ type: "text-delta", id: "3", text: "!" }]),
|
|
236
228
|
];
|
|
237
229
|
[result, changed] = applyDeltasToStreamMessage(
|
|
238
230
|
"thread1",
|
|
@@ -259,9 +251,11 @@ describe("mergeDeltas", () => {
|
|
|
259
251
|
const streamId = "s8";
|
|
260
252
|
const streamMessages = [makeStreamMessage(streamId, 8, 0)];
|
|
261
253
|
const deltas = [
|
|
262
|
-
makeDelta(streamId, 0, 5, [
|
|
254
|
+
makeDelta(streamId, 0, 5, [
|
|
255
|
+
{ type: "text-delta", id: "1", text: "Hello" },
|
|
256
|
+
]),
|
|
263
257
|
makeDelta(streamId, 5, 11, [
|
|
264
|
-
{ type: "text-delta",
|
|
258
|
+
{ type: "text-delta", id: "2", text: " World!" },
|
|
265
259
|
]),
|
|
266
260
|
];
|
|
267
261
|
// Deep freeze inputs to catch mutation
|
|
@@ -306,9 +300,11 @@ describe("mergeDeltas", () => {
|
|
|
306
300
|
// Inputs should remain unchanged
|
|
307
301
|
expect(streamMessages).toEqual([makeStreamMessage(streamId, 8, 0)]);
|
|
308
302
|
expect(deltas).toEqual([
|
|
309
|
-
makeDelta(streamId, 0, 5, [
|
|
303
|
+
makeDelta(streamId, 0, 5, [
|
|
304
|
+
{ type: "text-delta", id: "1", text: "Hello" },
|
|
305
|
+
]),
|
|
310
306
|
makeDelta(streamId, 5, 11, [
|
|
311
|
-
{ type: "text-delta",
|
|
307
|
+
{ type: "text-delta", id: "2", text: " World!" },
|
|
312
308
|
]),
|
|
313
309
|
]);
|
|
314
310
|
});
|
package/src/react/deltas.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TextStreamPart, ToolSet } from "ai";
|
|
2
2
|
import type { MessageDoc } from "../client/index.js";
|
|
3
3
|
import type {
|
|
4
4
|
Message,
|
|
5
5
|
MessageStatus,
|
|
6
6
|
StreamDelta,
|
|
7
7
|
StreamMessage,
|
|
8
|
-
|
|
8
|
+
vReasoningPart,
|
|
9
|
+
vTextPart,
|
|
10
|
+
vToolCallPart,
|
|
11
|
+
vToolResultPart,
|
|
9
12
|
} from "../validators.js";
|
|
10
|
-
import type {
|
|
11
|
-
import { toUIMessages } from "./toUIMessages.js";
|
|
12
|
-
|
|
13
|
-
export { toUIMessages, type UIMessage };
|
|
13
|
+
import type { Infer } from "convex/values";
|
|
14
14
|
|
|
15
15
|
export function mergeDeltas(
|
|
16
16
|
threadId: string,
|
|
@@ -73,7 +73,7 @@ export function applyDeltasToStreamMessage(
|
|
|
73
73
|
): [{ streamId: string; cursor: number; messages: MessageDoc[] }, boolean] {
|
|
74
74
|
let changed = false;
|
|
75
75
|
let cursor = existing?.cursor ?? 0;
|
|
76
|
-
let parts: TextStreamPart[] = [];
|
|
76
|
+
let parts: TextStreamPart<ToolSet>[] = [];
|
|
77
77
|
for (const delta of deltas.sort((a, b) => a.start - b.start)) {
|
|
78
78
|
if (delta.parts.length === 0) {
|
|
79
79
|
console.warn(`Got delta with no parts: ${JSON.stringify(delta)}`);
|
|
@@ -142,10 +142,10 @@ export function applyDeltasToStreamMessage(
|
|
|
142
142
|
let lastContent = getLastContent(currentMessage);
|
|
143
143
|
for (const part of parts) {
|
|
144
144
|
let contentToAdd:
|
|
145
|
-
|
|
|
146
|
-
|
|
|
147
|
-
|
|
|
148
|
-
|
|
|
145
|
+
| Infer<typeof vTextPart>
|
|
146
|
+
| Infer<typeof vToolCallPart>
|
|
147
|
+
| Infer<typeof vToolResultPart>
|
|
148
|
+
| Infer<typeof vReasoningPart>
|
|
149
149
|
| undefined;
|
|
150
150
|
const isToolRole = part.type === "source" || part.type === "tool-result";
|
|
151
151
|
if (isToolRole !== (currentMessage.message!.role === "tool")) {
|
|
@@ -160,27 +160,34 @@ export function applyDeltasToStreamMessage(
|
|
|
160
160
|
continue;
|
|
161
161
|
}
|
|
162
162
|
switch (part.type) {
|
|
163
|
-
case "text-delta":
|
|
164
|
-
|
|
163
|
+
case "text-delta": {
|
|
164
|
+
const text = part.text;
|
|
165
|
+
currentMessage.text = (currentMessage.text ?? "") + text;
|
|
165
166
|
if (lastContent?.type === "text") {
|
|
166
|
-
lastContent.text = (lastContent.text ?? "") +
|
|
167
|
+
lastContent.text = (lastContent.text ?? "") + text;
|
|
167
168
|
} else {
|
|
168
|
-
contentToAdd = {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
};
|
|
169
|
+
contentToAdd = { type: "text", text } satisfies Infer<
|
|
170
|
+
typeof vTextPart
|
|
171
|
+
>;
|
|
172
172
|
}
|
|
173
173
|
break;
|
|
174
|
-
|
|
174
|
+
}
|
|
175
|
+
case "tool-input-start": {
|
|
176
|
+
const toolCallId = part.id;
|
|
175
177
|
currentMessage.tool = true;
|
|
176
178
|
contentToAdd = {
|
|
177
179
|
type: "tool-call",
|
|
178
|
-
toolCallId
|
|
180
|
+
toolCallId,
|
|
179
181
|
toolName: part.toolName,
|
|
180
182
|
args: "",
|
|
181
|
-
|
|
183
|
+
providerExecuted:
|
|
184
|
+
"providerExecuted" in part ? part.providerExecuted : undefined,
|
|
185
|
+
providerOptions:
|
|
186
|
+
"providerMetadata" in part ? part.providerMetadata : undefined,
|
|
187
|
+
} satisfies Infer<typeof vToolCallPart>;
|
|
182
188
|
break;
|
|
183
|
-
|
|
189
|
+
}
|
|
190
|
+
case "tool-input-delta":
|
|
184
191
|
{
|
|
185
192
|
currentMessage.tool = true;
|
|
186
193
|
if (lastContent?.type !== "tool-call") {
|
|
@@ -189,33 +196,43 @@ export function applyDeltasToStreamMessage(
|
|
|
189
196
|
if (typeof lastContent.args !== "string") {
|
|
190
197
|
throw new Error("Expected args to be a string");
|
|
191
198
|
}
|
|
192
|
-
|
|
199
|
+
const delta =
|
|
200
|
+
"argsTextDelta" in part ? part.argsTextDelta : part.delta;
|
|
201
|
+
lastContent.args = (lastContent.args ?? "") + delta;
|
|
193
202
|
}
|
|
194
203
|
break;
|
|
195
|
-
case "tool-call":
|
|
204
|
+
case "tool-call": {
|
|
196
205
|
currentMessage.tool = true;
|
|
197
|
-
contentToAdd = part;
|
|
206
|
+
contentToAdd = toolCallContent(part);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "tool-result": {
|
|
210
|
+
contentToAdd = toolResultContent(part);
|
|
198
211
|
break;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
212
|
+
}
|
|
213
|
+
case "reasoning-delta": {
|
|
214
|
+
currentMessage.reasoning = (currentMessage.reasoning ?? "") + part.text;
|
|
202
215
|
if (lastContent?.type === "reasoning") {
|
|
203
|
-
lastContent.text = (lastContent.text ?? "") + part.
|
|
216
|
+
lastContent.text = (lastContent.text ?? "") + part.text;
|
|
204
217
|
} else {
|
|
205
218
|
contentToAdd = {
|
|
206
219
|
type: "reasoning",
|
|
207
|
-
text: part.
|
|
208
|
-
|
|
220
|
+
text: part.text,
|
|
221
|
+
providerOptions:
|
|
222
|
+
"providerMetadata" in part ? part.providerMetadata : undefined,
|
|
223
|
+
state: "streaming",
|
|
224
|
+
} satisfies Infer<typeof vReasoningPart>;
|
|
209
225
|
}
|
|
210
226
|
break;
|
|
227
|
+
}
|
|
211
228
|
case "source":
|
|
212
229
|
if (!currentMessage.sources) {
|
|
213
230
|
currentMessage.sources = [];
|
|
214
231
|
}
|
|
215
|
-
currentMessage.sources.push(part
|
|
232
|
+
currentMessage.sources.push(part);
|
|
216
233
|
break;
|
|
217
|
-
case "
|
|
218
|
-
|
|
234
|
+
case "raw":
|
|
235
|
+
// ignore
|
|
219
236
|
break;
|
|
220
237
|
default:
|
|
221
238
|
console.warn(`Received unexpected part: ${JSON.stringify(part)}`);
|
|
@@ -236,6 +253,31 @@ export function applyDeltasToStreamMessage(
|
|
|
236
253
|
return [newStream, true];
|
|
237
254
|
}
|
|
238
255
|
|
|
256
|
+
function toolCallContent(
|
|
257
|
+
part: Extract<TextStreamPart<ToolSet>, { type: "tool-call" }>,
|
|
258
|
+
): Infer<typeof vToolCallPart> {
|
|
259
|
+
const args = "args" in part ? part.args : part.input;
|
|
260
|
+
return {
|
|
261
|
+
type: "tool-call",
|
|
262
|
+
toolCallId: part.toolCallId,
|
|
263
|
+
toolName: part.toolName,
|
|
264
|
+
args,
|
|
265
|
+
providerExecuted: part.providerExecuted,
|
|
266
|
+
} satisfies Infer<typeof vToolCallPart>;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function toolResultContent(
|
|
270
|
+
part: Extract<TextStreamPart<ToolSet>, { type: "tool-result" }>,
|
|
271
|
+
): Infer<typeof vToolResultPart> {
|
|
272
|
+
return {
|
|
273
|
+
type: "tool-result",
|
|
274
|
+
toolCallId: part.toolCallId,
|
|
275
|
+
toolName: part.toolName,
|
|
276
|
+
result: part.output,
|
|
277
|
+
args: part.input,
|
|
278
|
+
providerExecuted: part.providerExecuted,
|
|
279
|
+
} satisfies Infer<typeof vToolResultPart>;
|
|
280
|
+
}
|
|
239
281
|
function cloneMessageAndContent(
|
|
240
282
|
message: Message | undefined,
|
|
241
283
|
): Message | undefined {
|
|
@@ -272,10 +314,11 @@ function statusFromStreamStatus(
|
|
|
272
314
|
}
|
|
273
315
|
}
|
|
274
316
|
|
|
317
|
+
// TODO: share more code with applyDeltasToStreamMessage
|
|
275
318
|
export function createStreamingMessage(
|
|
276
319
|
threadId: string,
|
|
277
320
|
message: StreamMessage,
|
|
278
|
-
part: TextStreamPart
|
|
321
|
+
part: TextStreamPart<ToolSet>,
|
|
279
322
|
index: number,
|
|
280
323
|
): MessageDoc {
|
|
281
324
|
const { streamId, ...rest } = message;
|
|
@@ -288,16 +331,15 @@ export function createStreamingMessage(
|
|
|
288
331
|
tool: false,
|
|
289
332
|
};
|
|
290
333
|
switch (part.type) {
|
|
291
|
-
case "text-delta":
|
|
334
|
+
case "text-delta": {
|
|
335
|
+
const text = part.text || "";
|
|
292
336
|
return {
|
|
293
337
|
...metadata,
|
|
294
|
-
message: {
|
|
295
|
-
|
|
296
|
-
content: [{ type: "text", text: part.textDelta }],
|
|
297
|
-
},
|
|
298
|
-
text: part.textDelta,
|
|
338
|
+
message: { role: "assistant", content: [{ type: "text", text }] },
|
|
339
|
+
text,
|
|
299
340
|
};
|
|
300
|
-
|
|
341
|
+
}
|
|
342
|
+
case "tool-input-start": {
|
|
301
343
|
return {
|
|
302
344
|
...metadata,
|
|
303
345
|
tool: true,
|
|
@@ -307,58 +349,67 @@ export function createStreamingMessage(
|
|
|
307
349
|
{
|
|
308
350
|
type: "tool-call",
|
|
309
351
|
toolName: part.toolName,
|
|
310
|
-
toolCallId: part.
|
|
352
|
+
toolCallId: part.id,
|
|
311
353
|
args: "", // when it's a string, it's a partial call
|
|
354
|
+
providerExecuted:
|
|
355
|
+
"providerExecuted" in part ? part.providerExecuted : undefined,
|
|
356
|
+
providerOptions:
|
|
357
|
+
"providerMetadata" in part ? part.providerMetadata : undefined,
|
|
312
358
|
},
|
|
313
359
|
],
|
|
314
360
|
},
|
|
315
361
|
};
|
|
316
|
-
|
|
362
|
+
}
|
|
363
|
+
case "tool-input-delta": {
|
|
364
|
+
console.warn("Received tool call delta part first??");
|
|
365
|
+
const delta = part.delta;
|
|
366
|
+
const toolCallId = part.id;
|
|
367
|
+
const toolName = part.type.slice("tool-".length);
|
|
317
368
|
return {
|
|
318
369
|
...metadata,
|
|
370
|
+
tool: true,
|
|
319
371
|
message: {
|
|
320
372
|
role: "assistant",
|
|
321
|
-
content: [{ type: "
|
|
373
|
+
content: [{ type: "tool-call", toolCallId, toolName, args: delta }],
|
|
322
374
|
},
|
|
323
|
-
reasoning: part.textDelta,
|
|
324
375
|
};
|
|
325
|
-
|
|
326
|
-
|
|
376
|
+
}
|
|
377
|
+
case "tool-call": {
|
|
327
378
|
return {
|
|
328
379
|
...metadata,
|
|
329
380
|
tool: true,
|
|
330
|
-
message: { role: "
|
|
331
|
-
sources: [part.source],
|
|
381
|
+
message: { role: "assistant", content: [toolCallContent(part)] },
|
|
332
382
|
};
|
|
333
|
-
|
|
383
|
+
}
|
|
384
|
+
case "tool-result":
|
|
334
385
|
return {
|
|
335
386
|
...metadata,
|
|
336
387
|
tool: true,
|
|
337
|
-
message: { role: "
|
|
388
|
+
message: { role: "tool", content: [toolResultContent(part)] },
|
|
338
389
|
};
|
|
339
|
-
case "
|
|
340
|
-
console.warn("Received tool call delta part first??");
|
|
390
|
+
case "reasoning-delta": {
|
|
341
391
|
return {
|
|
342
392
|
...metadata,
|
|
343
|
-
tool: true,
|
|
344
393
|
message: {
|
|
345
394
|
role: "assistant",
|
|
346
|
-
content: [
|
|
347
|
-
{
|
|
348
|
-
type: "tool-call",
|
|
349
|
-
toolCallId: part.toolCallId,
|
|
350
|
-
toolName: part.toolName,
|
|
351
|
-
args: part.argsTextDelta,
|
|
352
|
-
},
|
|
353
|
-
],
|
|
395
|
+
content: [{ type: "reasoning", text: part.text }],
|
|
354
396
|
},
|
|
397
|
+
reasoning: part.text,
|
|
355
398
|
};
|
|
356
|
-
|
|
399
|
+
}
|
|
400
|
+
case "source":
|
|
401
|
+
console.warn("Received source part first??");
|
|
357
402
|
return {
|
|
358
403
|
...metadata,
|
|
359
404
|
tool: true,
|
|
360
|
-
message: { role: "tool", content: [
|
|
405
|
+
message: { role: "tool", content: [] },
|
|
406
|
+
sources: [part],
|
|
361
407
|
};
|
|
408
|
+
// case "raw":
|
|
409
|
+
// return {
|
|
410
|
+
// ...metadata,
|
|
411
|
+
// message: { role: "assistant", content: [part.rawValue] },
|
|
412
|
+
// };
|
|
362
413
|
default:
|
|
363
414
|
throw new Error(`Unexpected part type: ${JSON.stringify(part)}`);
|
|
364
415
|
}
|
package/src/react/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "convex/react";
|
|
8
8
|
import { usePaginatedQuery } from "convex-helpers/react";
|
|
9
9
|
import type { FunctionArgs } from "convex/server";
|
|
10
|
-
import { useMemo,
|
|
10
|
+
import { useMemo, useState } from "react";
|
|
11
11
|
import type { MessageDoc } from "../client/index.js";
|
|
12
12
|
import type { SyncStreamsReturnValue } from "../client/types.js";
|
|
13
13
|
import type { StreamArgs } from "../validators.js";
|
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
|
|
24
24
|
export { optimisticallySendMessage } from "./optimisticallySendMessage.js";
|
|
25
25
|
export { useSmoothText } from "./useSmoothText.js";
|
|
26
|
+
export { SmoothText } from "./SmoothText.js";
|
|
26
27
|
export { toUIMessages, type UIMessage, type ThreadQuery };
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -97,6 +98,15 @@ export function useThreadMessages<
|
|
|
97
98
|
{ initialNumItems: options.initialNumItems },
|
|
98
99
|
);
|
|
99
100
|
|
|
101
|
+
let startOrder = paginated.results.at(-1)?.order ?? 0;
|
|
102
|
+
for (let i = paginated.results.length - 1; i >= 0; i--) {
|
|
103
|
+
const m = paginated.results[i];
|
|
104
|
+
if (!m.streaming && m.status === "pending") {
|
|
105
|
+
// round down to the nearest 10 for some cache benefits
|
|
106
|
+
startOrder = m.order - (m.order % 10);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
100
110
|
// These are streaming messages that will not include full messages.
|
|
101
111
|
const streamMessages = useStreamingThreadMessages(
|
|
102
112
|
query as ThreadStreamQuery<
|
|
@@ -107,7 +117,7 @@ export function useThreadMessages<
|
|
|
107
117
|
args === "skip" ||
|
|
108
118
|
paginated.status === "LoadingFirstPage"
|
|
109
119
|
? "skip"
|
|
110
|
-
: { ...args, startOrder
|
|
120
|
+
: { ...args, startOrder },
|
|
111
121
|
);
|
|
112
122
|
|
|
113
123
|
const merged = useMemo(() => {
|
|
@@ -120,16 +130,32 @@ export function useThreadMessages<
|
|
|
120
130
|
...paginated,
|
|
121
131
|
results: paginated.results
|
|
122
132
|
.map((m) => ({ ...m, streaming: false }))
|
|
133
|
+
// Note: this is intentionally after paginated results.
|
|
123
134
|
.concat(streamListMessages)
|
|
124
135
|
.sort((a, b) =>
|
|
125
136
|
a.order === b.order ? a.stepOrder - b.stepOrder : a.order - b.order,
|
|
126
137
|
)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
!
|
|
131
|
-
|
|
132
|
-
|
|
138
|
+
.reduce(
|
|
139
|
+
(msgs, msg) => {
|
|
140
|
+
const last = msgs.at(-1);
|
|
141
|
+
if (!last) {
|
|
142
|
+
return [msg];
|
|
143
|
+
}
|
|
144
|
+
if (last.order !== msg.order || last.stepOrder !== msg.stepOrder) {
|
|
145
|
+
return [...msgs, msg];
|
|
146
|
+
}
|
|
147
|
+
if (
|
|
148
|
+
last.status === "pending" &&
|
|
149
|
+
(msg.streaming || msg.status !== "pending")
|
|
150
|
+
) {
|
|
151
|
+
// Let's prefer a streaming or finalized message over a pending
|
|
152
|
+
// one.
|
|
153
|
+
return [...msgs.slice(0, -1), msg];
|
|
154
|
+
}
|
|
155
|
+
// skip the new one if the previous one (listed) was finalized
|
|
156
|
+
return msgs;
|
|
157
|
+
},
|
|
158
|
+
[] as (ThreadMessagesResult<Query> & { streaming: boolean })[],
|
|
133
159
|
),
|
|
134
160
|
};
|
|
135
161
|
}, [paginated, streamMessages]);
|
|
@@ -162,11 +188,7 @@ export function useStreamingThreadMessages<
|
|
|
162
188
|
const [streams, setStreams] = useState<
|
|
163
189
|
Array<{ streamId: string; cursor: number; messages: MessageDoc[] }>
|
|
164
190
|
>([]);
|
|
165
|
-
const startOrderRef = useRef<number>(0);
|
|
166
191
|
const queryArgs = args === "skip" ? args : omit(args, ["startOrder"]);
|
|
167
|
-
if (args !== "skip" && !startOrderRef.current && args.startOrder) {
|
|
168
|
-
startOrderRef.current = args.startOrder;
|
|
169
|
-
}
|
|
170
192
|
// Get all the active streams
|
|
171
193
|
const streamList = useQuery(
|
|
172
194
|
query,
|
|
@@ -177,7 +199,7 @@ export function useStreamingThreadMessages<
|
|
|
177
199
|
paginationOpts: { cursor: null, numItems: 0 },
|
|
178
200
|
streamArgs: {
|
|
179
201
|
kind: "list",
|
|
180
|
-
startOrder:
|
|
202
|
+
startOrder: queryArgs.startOrder ?? 0,
|
|
181
203
|
} as StreamArgs,
|
|
182
204
|
} as FunctionArgs<Query>),
|
|
183
205
|
) as
|
|
@@ -12,17 +12,14 @@ export function optimisticallySendMessage(
|
|
|
12
12
|
return (store, args) => {
|
|
13
13
|
const queries = store.getAllQueries(query);
|
|
14
14
|
let maxOrder = -1;
|
|
15
|
-
let maxStepOrder = 0;
|
|
16
15
|
for (const q of queries) {
|
|
17
16
|
if (q.args?.threadId !== args.threadId) continue;
|
|
18
17
|
if (q.args.streamArgs) continue;
|
|
19
18
|
for (const m of q.value?.page ?? []) {
|
|
20
19
|
maxOrder = Math.max(maxOrder, m.order);
|
|
21
|
-
maxStepOrder = Math.max(maxStepOrder, m.stepOrder);
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
22
|
const order = maxOrder + 1;
|
|
25
|
-
const stepOrder = 0;
|
|
26
23
|
insertAtTop({
|
|
27
24
|
paginatedQuery: query,
|
|
28
25
|
argsToMatch: { threadId: args.threadId, streamArgs: undefined },
|
|
@@ -30,14 +27,11 @@ export function optimisticallySendMessage(
|
|
|
30
27
|
_creationTime: Date.now(),
|
|
31
28
|
_id: randomUUID(),
|
|
32
29
|
order,
|
|
33
|
-
stepOrder,
|
|
30
|
+
stepOrder: 0,
|
|
34
31
|
status: "pending",
|
|
35
32
|
threadId: args.threadId,
|
|
36
33
|
tool: false,
|
|
37
|
-
message: {
|
|
38
|
-
role: "user",
|
|
39
|
-
content: args.prompt,
|
|
40
|
-
},
|
|
34
|
+
message: { role: "user", content: args.prompt },
|
|
41
35
|
text: args.prompt,
|
|
42
36
|
},
|
|
43
37
|
localQueryStore: store,
|