@convex-dev/agent 0.2.0-alpha.2 → 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 +3 -5
- package/dist/client/createTool.d.ts.map +1 -1
- package/dist/client/createTool.js +0 -1
- package/dist/client/createTool.js.map +1 -1
- package/dist/client/definePlaygroundAPI.d.ts +28 -28
- package/dist/client/files.d.ts +8 -1
- package/dist/client/files.d.ts.map +1 -1
- package/dist/client/files.js +60 -5
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +223 -239
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +368 -382
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +91 -373
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/messages.js +7 -1
- package/dist/client/messages.js.map +1 -1
- package/dist/client/search.d.ts +15 -15
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +3 -3
- package/dist/client/search.js.map +1 -1
- package/dist/client/streaming.d.ts +21 -22
- 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/types.d.ts +148 -61
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +47 -357
- package/dist/component/messages.d.ts +157 -150
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +124 -70
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +1147 -2687
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +5 -4
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.d.ts +29 -323
- 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 +4 -1
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +17 -1
- 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 -2
- package/dist/react/deltas.d.ts.map +1 -1
- package/dist/react/deltas.js +19 -79
- 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/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 +109 -1590
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +4 -81
- package/dist/validators.js.map +1 -1
- package/package.json +3 -3
- package/src/client/createTool.ts +4 -9
- package/src/client/files.ts +88 -10
- package/src/client/index.test.ts +9 -21
- package/src/client/index.ts +664 -650
- package/src/client/messages.ts +16 -2
- package/src/client/search.ts +10 -7
- package/src/client/streaming.ts +53 -57
- package/src/client/types.ts +227 -97
- package/src/component/_generated/api.d.ts +47 -357
- package/src/component/messages.ts +157 -96
- package/src/component/schema.ts +4 -4
- package/src/component/streams.ts +180 -69
- package/src/mapping.ts +23 -2
- 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 +24 -86
- package/src/react/index.ts +35 -13
- package/src/react/optimisticallySendMessage.ts +2 -8
- package/src/react/useSmoothText.ts +14 -15
- package/src/validators.test.ts +0 -17
- package/src/validators.ts +8 -101
package/src/react/deltas.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
import type { TextStreamPart, ToolSet } from "ai";
|
|
1
2
|
import type { MessageDoc } from "../client/index.js";
|
|
2
3
|
import type {
|
|
3
4
|
Message,
|
|
4
5
|
MessageStatus,
|
|
5
6
|
StreamDelta,
|
|
6
7
|
StreamMessage,
|
|
7
|
-
TextStreamPart,
|
|
8
8
|
vReasoningPart,
|
|
9
|
-
vSource,
|
|
10
9
|
vTextPart,
|
|
11
10
|
vToolCallPart,
|
|
12
11
|
vToolResultPart,
|
|
@@ -74,7 +73,7 @@ export function applyDeltasToStreamMessage(
|
|
|
74
73
|
): [{ streamId: string; cursor: number; messages: MessageDoc[] }, boolean] {
|
|
75
74
|
let changed = false;
|
|
76
75
|
let cursor = existing?.cursor ?? 0;
|
|
77
|
-
let parts: TextStreamPart[] = [];
|
|
76
|
+
let parts: TextStreamPart<ToolSet>[] = [];
|
|
78
77
|
for (const delta of deltas.sort((a, b) => a.start - b.start)) {
|
|
79
78
|
if (delta.parts.length === 0) {
|
|
80
79
|
console.warn(`Got delta with no parts: ${JSON.stringify(delta)}`);
|
|
@@ -162,7 +161,7 @@ export function applyDeltasToStreamMessage(
|
|
|
162
161
|
}
|
|
163
162
|
switch (part.type) {
|
|
164
163
|
case "text-delta": {
|
|
165
|
-
const text =
|
|
164
|
+
const text = part.text;
|
|
166
165
|
currentMessage.text = (currentMessage.text ?? "") + text;
|
|
167
166
|
if (lastContent?.type === "text") {
|
|
168
167
|
lastContent.text = (lastContent.text ?? "") + text;
|
|
@@ -173,9 +172,8 @@ export function applyDeltasToStreamMessage(
|
|
|
173
172
|
}
|
|
174
173
|
break;
|
|
175
174
|
}
|
|
176
|
-
case "tool-input-start":
|
|
177
|
-
|
|
178
|
-
const toolCallId = "toolCallId" in part ? part.toolCallId : part.id;
|
|
175
|
+
case "tool-input-start": {
|
|
176
|
+
const toolCallId = part.id;
|
|
179
177
|
currentMessage.tool = true;
|
|
180
178
|
contentToAdd = {
|
|
181
179
|
type: "tool-call",
|
|
@@ -189,7 +187,6 @@ export function applyDeltasToStreamMessage(
|
|
|
189
187
|
} satisfies Infer<typeof vToolCallPart>;
|
|
190
188
|
break;
|
|
191
189
|
}
|
|
192
|
-
case "tool-call-delta":
|
|
193
190
|
case "tool-input-delta":
|
|
194
191
|
{
|
|
195
192
|
currentMessage.tool = true;
|
|
@@ -213,18 +210,6 @@ export function applyDeltasToStreamMessage(
|
|
|
213
210
|
contentToAdd = toolResultContent(part);
|
|
214
211
|
break;
|
|
215
212
|
}
|
|
216
|
-
case "reasoning":
|
|
217
|
-
currentMessage.reasoning =
|
|
218
|
-
(currentMessage.reasoning ?? "") + part.textDelta;
|
|
219
|
-
if (lastContent?.type === "reasoning") {
|
|
220
|
-
lastContent.text = (lastContent.text ?? "") + part.textDelta;
|
|
221
|
-
} else {
|
|
222
|
-
contentToAdd = {
|
|
223
|
-
type: "reasoning",
|
|
224
|
-
text: part.textDelta,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
break;
|
|
228
213
|
case "reasoning-delta": {
|
|
229
214
|
currentMessage.reasoning = (currentMessage.reasoning ?? "") + part.text;
|
|
230
215
|
if (lastContent?.type === "reasoning") {
|
|
@@ -244,17 +229,10 @@ export function applyDeltasToStreamMessage(
|
|
|
244
229
|
if (!currentMessage.sources) {
|
|
245
230
|
currentMessage.sources = [];
|
|
246
231
|
}
|
|
247
|
-
|
|
248
|
-
currentMessage.sources.push({
|
|
249
|
-
type: "source",
|
|
250
|
-
...part.source,
|
|
251
|
-
} satisfies Infer<typeof vSource>);
|
|
252
|
-
} else {
|
|
253
|
-
currentMessage.sources.push(part);
|
|
254
|
-
}
|
|
232
|
+
currentMessage.sources.push(part);
|
|
255
233
|
break;
|
|
256
234
|
case "raw":
|
|
257
|
-
|
|
235
|
+
// ignore
|
|
258
236
|
break;
|
|
259
237
|
default:
|
|
260
238
|
console.warn(`Received unexpected part: ${JSON.stringify(part)}`);
|
|
@@ -276,7 +254,7 @@ export function applyDeltasToStreamMessage(
|
|
|
276
254
|
}
|
|
277
255
|
|
|
278
256
|
function toolCallContent(
|
|
279
|
-
part: Extract<TextStreamPart
|
|
257
|
+
part: Extract<TextStreamPart<ToolSet>, { type: "tool-call" }>,
|
|
280
258
|
): Infer<typeof vToolCallPart> {
|
|
281
259
|
const args = "args" in part ? part.args : part.input;
|
|
282
260
|
return {
|
|
@@ -289,18 +267,14 @@ function toolCallContent(
|
|
|
289
267
|
}
|
|
290
268
|
|
|
291
269
|
function toolResultContent(
|
|
292
|
-
part: Extract<TextStreamPart
|
|
270
|
+
part: Extract<TextStreamPart<ToolSet>, { type: "tool-result" }>,
|
|
293
271
|
): Infer<typeof vToolResultPart> {
|
|
294
|
-
const result =
|
|
295
|
-
"output" in part ? part.output : "result" in part ? part.result : undefined;
|
|
296
|
-
const args =
|
|
297
|
-
"input" in part ? part.input : "args" in part ? part.args : undefined;
|
|
298
272
|
return {
|
|
299
273
|
type: "tool-result",
|
|
300
274
|
toolCallId: part.toolCallId,
|
|
301
275
|
toolName: part.toolName,
|
|
302
|
-
result,
|
|
303
|
-
args,
|
|
276
|
+
result: part.output,
|
|
277
|
+
args: part.input,
|
|
304
278
|
providerExecuted: part.providerExecuted,
|
|
305
279
|
} satisfies Infer<typeof vToolResultPart>;
|
|
306
280
|
}
|
|
@@ -344,7 +318,7 @@ function statusFromStreamStatus(
|
|
|
344
318
|
export function createStreamingMessage(
|
|
345
319
|
threadId: string,
|
|
346
320
|
message: StreamMessage,
|
|
347
|
-
part: TextStreamPart
|
|
321
|
+
part: TextStreamPart<ToolSet>,
|
|
348
322
|
index: number,
|
|
349
323
|
): MessageDoc {
|
|
350
324
|
const { streamId, ...rest } = message;
|
|
@@ -358,19 +332,14 @@ export function createStreamingMessage(
|
|
|
358
332
|
};
|
|
359
333
|
switch (part.type) {
|
|
360
334
|
case "text-delta": {
|
|
361
|
-
const text =
|
|
335
|
+
const text = part.text || "";
|
|
362
336
|
return {
|
|
363
337
|
...metadata,
|
|
364
|
-
message: {
|
|
365
|
-
role: "assistant",
|
|
366
|
-
content: [{ type: "text", text }],
|
|
367
|
-
},
|
|
338
|
+
message: { role: "assistant", content: [{ type: "text", text }] },
|
|
368
339
|
text,
|
|
369
340
|
};
|
|
370
341
|
}
|
|
371
|
-
case "tool-input-start":
|
|
372
|
-
case "tool-call-streaming-start": {
|
|
373
|
-
const toolCallId = "toolCallId" in part ? part.toolCallId : part.id;
|
|
342
|
+
case "tool-input-start": {
|
|
374
343
|
return {
|
|
375
344
|
...metadata,
|
|
376
345
|
tool: true,
|
|
@@ -380,7 +349,7 @@ export function createStreamingMessage(
|
|
|
380
349
|
{
|
|
381
350
|
type: "tool-call",
|
|
382
351
|
toolName: part.toolName,
|
|
383
|
-
toolCallId,
|
|
352
|
+
toolCallId: part.id,
|
|
384
353
|
args: "", // when it's a string, it's a partial call
|
|
385
354
|
providerExecuted:
|
|
386
355
|
"providerExecuted" in part ? part.providerExecuted : undefined,
|
|
@@ -391,26 +360,17 @@ export function createStreamingMessage(
|
|
|
391
360
|
},
|
|
392
361
|
};
|
|
393
362
|
}
|
|
394
|
-
case "tool-input-delta":
|
|
395
|
-
case "tool-call-delta": {
|
|
363
|
+
case "tool-input-delta": {
|
|
396
364
|
console.warn("Received tool call delta part first??");
|
|
397
|
-
const delta =
|
|
398
|
-
const toolCallId =
|
|
399
|
-
const toolName =
|
|
400
|
-
"toolName" in part ? part.toolName : part.type.slice("tool-".length);
|
|
365
|
+
const delta = part.delta;
|
|
366
|
+
const toolCallId = part.id;
|
|
367
|
+
const toolName = part.type.slice("tool-".length);
|
|
401
368
|
return {
|
|
402
369
|
...metadata,
|
|
403
370
|
tool: true,
|
|
404
371
|
message: {
|
|
405
372
|
role: "assistant",
|
|
406
|
-
content: [
|
|
407
|
-
{
|
|
408
|
-
type: "tool-call",
|
|
409
|
-
toolCallId,
|
|
410
|
-
toolName,
|
|
411
|
-
args: delta,
|
|
412
|
-
},
|
|
413
|
-
],
|
|
373
|
+
content: [{ type: "tool-call", toolCallId, toolName, args: delta }],
|
|
414
374
|
},
|
|
415
375
|
};
|
|
416
376
|
}
|
|
@@ -418,29 +378,14 @@ export function createStreamingMessage(
|
|
|
418
378
|
return {
|
|
419
379
|
...metadata,
|
|
420
380
|
tool: true,
|
|
421
|
-
message: {
|
|
422
|
-
role: "assistant",
|
|
423
|
-
content: [toolCallContent(part)],
|
|
424
|
-
},
|
|
381
|
+
message: { role: "assistant", content: [toolCallContent(part)] },
|
|
425
382
|
};
|
|
426
383
|
}
|
|
427
384
|
case "tool-result":
|
|
428
385
|
return {
|
|
429
386
|
...metadata,
|
|
430
387
|
tool: true,
|
|
431
|
-
message: {
|
|
432
|
-
role: "tool",
|
|
433
|
-
content: [toolResultContent(part)],
|
|
434
|
-
},
|
|
435
|
-
};
|
|
436
|
-
case "reasoning":
|
|
437
|
-
return {
|
|
438
|
-
...metadata,
|
|
439
|
-
message: {
|
|
440
|
-
role: "assistant",
|
|
441
|
-
content: [{ type: "reasoning", text: part.textDelta }],
|
|
442
|
-
},
|
|
443
|
-
reasoning: part.textDelta,
|
|
388
|
+
message: { role: "tool", content: [toolResultContent(part)] },
|
|
444
389
|
};
|
|
445
390
|
case "reasoning-delta": {
|
|
446
391
|
return {
|
|
@@ -458,14 +403,7 @@ export function createStreamingMessage(
|
|
|
458
403
|
...metadata,
|
|
459
404
|
tool: true,
|
|
460
405
|
message: { role: "tool", content: [] },
|
|
461
|
-
sources: [
|
|
462
|
-
"source" in part
|
|
463
|
-
? {
|
|
464
|
-
...part.source,
|
|
465
|
-
type: "source",
|
|
466
|
-
}
|
|
467
|
-
: part,
|
|
468
|
-
],
|
|
406
|
+
sources: [part],
|
|
469
407
|
};
|
|
470
408
|
// case "raw":
|
|
471
409
|
// return {
|
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,
|
|
@@ -3,6 +3,19 @@ import { useEffect, useRef, useState } from "react";
|
|
|
3
3
|
const FPS = 20;
|
|
4
4
|
const MS_PER_FRAME = 1000 / FPS;
|
|
5
5
|
const MAX_TIME_JUMP_MS = 250;
|
|
6
|
+
|
|
7
|
+
export type SmoothTextOptions = {
|
|
8
|
+
/**
|
|
9
|
+
* The number of characters to display per second.
|
|
10
|
+
*/
|
|
11
|
+
charsPerSec?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Whether to initially start streaming.
|
|
14
|
+
* If this later turns to false, it'll continue streaming.
|
|
15
|
+
* This will start streaming the first value it sees.
|
|
16
|
+
*/
|
|
17
|
+
startStreaming?: boolean;
|
|
18
|
+
};
|
|
6
19
|
/**
|
|
7
20
|
* A hook that smoothly displays text as it is streamed.
|
|
8
21
|
*
|
|
@@ -15,21 +28,7 @@ const MAX_TIME_JUMP_MS = 250;
|
|
|
15
28
|
*/
|
|
16
29
|
export function useSmoothText(
|
|
17
30
|
text: string,
|
|
18
|
-
{
|
|
19
|
-
charsPerSec = 256,
|
|
20
|
-
startStreaming = false,
|
|
21
|
-
}: {
|
|
22
|
-
/**
|
|
23
|
-
* The number of characters to display per second.
|
|
24
|
-
*/
|
|
25
|
-
charsPerSec?: number;
|
|
26
|
-
/**
|
|
27
|
-
* Whether to initially start streaming.
|
|
28
|
-
* If this later turns to false, it'll continue streaming.
|
|
29
|
-
* This will start streaming the first value it sees.
|
|
30
|
-
*/
|
|
31
|
-
startStreaming?: boolean;
|
|
32
|
-
} = {},
|
|
31
|
+
{ charsPerSec = 256, startStreaming = false }: SmoothTextOptions = {},
|
|
33
32
|
): [string, { cursor: number; isStreaming: boolean }] {
|
|
34
33
|
const [visibleText, setVisibleText] = useState(startStreaming ? "" : text);
|
|
35
34
|
const smoothState = useRef({
|
package/src/validators.test.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import type { TextStreamPart as AITextStreamPart, ToolSet } from "ai";
|
|
2
1
|
import type { Infer } from "convex/values";
|
|
3
2
|
import { expectTypeOf, test } from "vitest";
|
|
4
3
|
import type { ContextOptions, StorageOptions } from "./client/types.js";
|
|
5
|
-
import type { TextStreamPart } from "./validators.js";
|
|
6
4
|
import { vContextOptions, vStorageOptions } from "./validators.js";
|
|
7
5
|
|
|
8
6
|
expectTypeOf<Infer<typeof vContextOptions>>().toExtend<ContextOptions>();
|
|
@@ -11,19 +9,4 @@ expectTypeOf<ContextOptions>().toExtend<Infer<typeof vContextOptions>>();
|
|
|
11
9
|
expectTypeOf<Infer<typeof vStorageOptions>>().toExtend<StorageOptions>();
|
|
12
10
|
expectTypeOf<StorageOptions>().toExtend<Infer<typeof vStorageOptions>>();
|
|
13
11
|
|
|
14
|
-
type StreamPart = Extract<
|
|
15
|
-
AITextStreamPart<ToolSet>,
|
|
16
|
-
{
|
|
17
|
-
type:
|
|
18
|
-
| "text-delta"
|
|
19
|
-
| "reasoning"
|
|
20
|
-
| "source"
|
|
21
|
-
| "tool-call"
|
|
22
|
-
| "tool-call-streaming-start"
|
|
23
|
-
| "tool-call-delta"
|
|
24
|
-
| "tool-result";
|
|
25
|
-
}
|
|
26
|
-
>;
|
|
27
|
-
expectTypeOf<StreamPart>().toExtend<TextStreamPart>();
|
|
28
|
-
|
|
29
12
|
test("noop", () => {});
|
package/src/validators.ts
CHANGED
|
@@ -180,7 +180,7 @@ export const vSource = v.union(
|
|
|
180
180
|
type: v.optional(v.literal("source")),
|
|
181
181
|
sourceType: v.literal("url"),
|
|
182
182
|
id: v.string(),
|
|
183
|
-
url: v.
|
|
183
|
+
url: v.string(),
|
|
184
184
|
title: v.optional(v.string()),
|
|
185
185
|
providerOptions,
|
|
186
186
|
}),
|
|
@@ -253,6 +253,9 @@ export const vMessageWithMetadataInternal = v.object({
|
|
|
253
253
|
warnings: v.optional(v.array(vLanguageModelCallWarning)),
|
|
254
254
|
error: v.optional(v.string()),
|
|
255
255
|
});
|
|
256
|
+
export type MessageWithMetadataInternal = Infer<
|
|
257
|
+
typeof vMessageWithMetadataInternal
|
|
258
|
+
>;
|
|
256
259
|
export const vMessageWithMetadata = v.object({
|
|
257
260
|
...vMessageWithMetadataInternal.fields,
|
|
258
261
|
fileIds: v.optional(v.array(v.string())),
|
|
@@ -334,10 +337,7 @@ export const vTextArgs = v.object({
|
|
|
334
337
|
v.literal("auto"),
|
|
335
338
|
v.literal("none"),
|
|
336
339
|
v.literal("required"),
|
|
337
|
-
v.object({
|
|
338
|
-
type: v.literal("tool"),
|
|
339
|
-
toolName: v.string(),
|
|
340
|
-
}),
|
|
340
|
+
v.object({ type: v.literal("tool"), toolName: v.string() }),
|
|
341
341
|
),
|
|
342
342
|
),
|
|
343
343
|
maxSteps: v.optional(v.number()),
|
|
@@ -373,93 +373,6 @@ export function vPaginationResult<
|
|
|
373
373
|
});
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
-
export const vTextStreamPartV4 = v.union(
|
|
377
|
-
v.object({
|
|
378
|
-
type: v.literal("text-delta"),
|
|
379
|
-
textDelta: v.string(),
|
|
380
|
-
}),
|
|
381
|
-
v.object({
|
|
382
|
-
type: v.literal("reasoning"),
|
|
383
|
-
textDelta: v.string(),
|
|
384
|
-
}),
|
|
385
|
-
v.object({
|
|
386
|
-
type: v.literal("source"),
|
|
387
|
-
source: v.object({
|
|
388
|
-
sourceType: v.literal("url"),
|
|
389
|
-
id: v.string(),
|
|
390
|
-
url: v.optional(v.string()),
|
|
391
|
-
title: v.optional(v.string()),
|
|
392
|
-
providerOptions,
|
|
393
|
-
}),
|
|
394
|
-
}),
|
|
395
|
-
vToolCallPart,
|
|
396
|
-
vToolResultPart,
|
|
397
|
-
v.object({
|
|
398
|
-
type: v.literal("tool-call-streaming-start"),
|
|
399
|
-
toolCallId: v.string(),
|
|
400
|
-
toolName: v.string(),
|
|
401
|
-
}),
|
|
402
|
-
v.object({
|
|
403
|
-
type: v.literal("tool-call-delta"),
|
|
404
|
-
toolCallId: v.string(),
|
|
405
|
-
toolName: v.string(),
|
|
406
|
-
argsTextDelta: v.string(),
|
|
407
|
-
}),
|
|
408
|
-
);
|
|
409
|
-
export const vTextStreamPartV5 = v.union(
|
|
410
|
-
v.object({
|
|
411
|
-
type: v.literal("text-delta"),
|
|
412
|
-
id: v.string(),
|
|
413
|
-
text: v.string(),
|
|
414
|
-
providerMetadata,
|
|
415
|
-
}),
|
|
416
|
-
v.object({
|
|
417
|
-
type: v.literal("reasoning-delta"),
|
|
418
|
-
id: v.string(),
|
|
419
|
-
text: v.string(),
|
|
420
|
-
providerMetadata,
|
|
421
|
-
}),
|
|
422
|
-
vSource,
|
|
423
|
-
v.object({
|
|
424
|
-
type: v.literal("tool-call"),
|
|
425
|
-
toolCallId: v.string(),
|
|
426
|
-
toolName: v.string(),
|
|
427
|
-
input: v.any(),
|
|
428
|
-
providerExecuted: v.optional(v.boolean()),
|
|
429
|
-
dynamic: v.optional(v.boolean()),
|
|
430
|
-
providerMetadata,
|
|
431
|
-
}),
|
|
432
|
-
v.object({
|
|
433
|
-
type: v.literal("tool-input-start"),
|
|
434
|
-
id: v.string(),
|
|
435
|
-
toolName: v.string(),
|
|
436
|
-
providerMetadata,
|
|
437
|
-
providerExecuted: v.optional(v.boolean()),
|
|
438
|
-
dynamic: v.optional(v.boolean()),
|
|
439
|
-
}),
|
|
440
|
-
v.object({
|
|
441
|
-
type: v.literal("tool-input-delta"),
|
|
442
|
-
id: v.string(),
|
|
443
|
-
delta: v.string(),
|
|
444
|
-
providerMetadata,
|
|
445
|
-
}),
|
|
446
|
-
v.object({
|
|
447
|
-
type: v.literal("tool-result"),
|
|
448
|
-
toolCallId: v.string(),
|
|
449
|
-
toolName: v.string(),
|
|
450
|
-
input: v.optional(v.any()),
|
|
451
|
-
output: v.optional(v.any()),
|
|
452
|
-
providerExecuted: v.optional(v.boolean()),
|
|
453
|
-
dynamic: v.optional(v.boolean()),
|
|
454
|
-
}),
|
|
455
|
-
v.object({
|
|
456
|
-
type: v.literal("raw"),
|
|
457
|
-
rawValue: v.any(),
|
|
458
|
-
}),
|
|
459
|
-
);
|
|
460
|
-
export const vTextStreamPart = v.union(vTextStreamPartV4, vTextStreamPartV5);
|
|
461
|
-
export type TextStreamPart = Infer<typeof vTextStreamPart>;
|
|
462
|
-
|
|
463
376
|
export const vStreamCursor = v.object({
|
|
464
377
|
streamId: v.string(),
|
|
465
378
|
cursor: v.number(),
|
|
@@ -468,14 +381,8 @@ export type StreamCursor = Infer<typeof vStreamCursor>;
|
|
|
468
381
|
|
|
469
382
|
export const vStreamArgs = v.optional(
|
|
470
383
|
v.union(
|
|
471
|
-
v.object({
|
|
472
|
-
|
|
473
|
-
startOrder: v.optional(v.number()),
|
|
474
|
-
}),
|
|
475
|
-
v.object({
|
|
476
|
-
kind: v.literal("deltas"),
|
|
477
|
-
cursors: v.array(vStreamCursor),
|
|
478
|
-
}),
|
|
384
|
+
v.object({ kind: v.literal("list"), startOrder: v.optional(v.number()) }),
|
|
385
|
+
v.object({ kind: v.literal("deltas"), cursors: v.array(vStreamCursor) }),
|
|
479
386
|
),
|
|
480
387
|
);
|
|
481
388
|
export type StreamArgs = Infer<typeof vStreamArgs>;
|
|
@@ -502,6 +409,6 @@ export const vStreamDelta = v.object({
|
|
|
502
409
|
streamId: v.string(),
|
|
503
410
|
start: v.number(), // inclusive
|
|
504
411
|
end: v.number(), // exclusive
|
|
505
|
-
parts: v.array(
|
|
412
|
+
parts: v.array(v.any()),
|
|
506
413
|
});
|
|
507
414
|
export type StreamDelta = Infer<typeof vStreamDelta>;
|