@convex-dev/agent 0.1.3 → 0.1.4
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/commonjs/client/index.d.ts +6 -5
- package/dist/commonjs/client/index.d.ts.map +1 -1
- package/dist/commonjs/client/index.js +37 -11
- package/dist/commonjs/client/index.js.map +1 -1
- package/dist/commonjs/client/streaming.d.ts +2 -2
- package/dist/commonjs/client/streaming.d.ts.map +1 -1
- package/dist/commonjs/client/streaming.js +2 -1
- package/dist/commonjs/client/streaming.js.map +1 -1
- package/dist/commonjs/component/messages.d.ts +3 -3
- package/dist/commonjs/component/messages.d.ts.map +1 -1
- package/dist/commonjs/component/messages.js +16 -14
- package/dist/commonjs/component/messages.js.map +1 -1
- package/dist/commonjs/component/streams.d.ts.map +1 -1
- package/dist/commonjs/component/streams.js +1 -4
- package/dist/commonjs/component/streams.js.map +1 -1
- package/dist/commonjs/component/users.d.ts +3 -0
- package/dist/commonjs/component/users.d.ts.map +1 -1
- package/dist/commonjs/component/users.js +11 -1
- package/dist/commonjs/component/users.js.map +1 -1
- package/dist/commonjs/component/vector/index.d.ts +3 -1
- package/dist/commonjs/component/vector/index.d.ts.map +1 -1
- package/dist/commonjs/component/vector/index.js +6 -2
- package/dist/commonjs/component/vector/index.js.map +1 -1
- package/dist/commonjs/react/deltas.d.ts +29 -0
- package/dist/commonjs/react/deltas.d.ts.map +1 -0
- package/dist/commonjs/react/deltas.js +267 -0
- package/dist/commonjs/react/deltas.js.map +1 -0
- package/dist/commonjs/react/index.d.ts +4 -49
- package/dist/commonjs/react/index.d.ts.map +1 -1
- package/dist/commonjs/react/index.js +15 -446
- package/dist/commonjs/react/index.js.map +1 -1
- package/dist/commonjs/react/optimisticallySendMessage.d.ts +8 -0
- package/dist/commonjs/react/optimisticallySendMessage.d.ts.map +1 -0
- package/dist/commonjs/react/optimisticallySendMessage.js +40 -0
- package/dist/commonjs/react/optimisticallySendMessage.js.map +1 -0
- package/dist/commonjs/react/toUIMessages.d.ts.map +1 -1
- package/dist/commonjs/react/toUIMessages.js +4 -2
- package/dist/commonjs/react/toUIMessages.js.map +1 -1
- package/dist/commonjs/react/types.d.ts +26 -0
- package/dist/commonjs/react/types.d.ts.map +1 -0
- package/dist/commonjs/react/types.js +2 -0
- package/dist/commonjs/react/types.js.map +1 -0
- package/dist/commonjs/react/useSmoothText.d.ts +20 -0
- package/dist/commonjs/react/useSmoothText.d.ts.map +1 -0
- package/dist/commonjs/react/useSmoothText.js +50 -0
- package/dist/commonjs/react/useSmoothText.js.map +1 -0
- package/dist/esm/client/index.d.ts +6 -5
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js +37 -11
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/streaming.d.ts +2 -2
- package/dist/esm/client/streaming.d.ts.map +1 -1
- package/dist/esm/client/streaming.js +2 -1
- package/dist/esm/client/streaming.js.map +1 -1
- package/dist/esm/component/messages.d.ts +3 -3
- package/dist/esm/component/messages.d.ts.map +1 -1
- package/dist/esm/component/messages.js +16 -14
- package/dist/esm/component/messages.js.map +1 -1
- package/dist/esm/component/streams.d.ts.map +1 -1
- package/dist/esm/component/streams.js +1 -4
- package/dist/esm/component/streams.js.map +1 -1
- package/dist/esm/component/users.d.ts +3 -0
- package/dist/esm/component/users.d.ts.map +1 -1
- package/dist/esm/component/users.js +11 -1
- package/dist/esm/component/users.js.map +1 -1
- package/dist/esm/component/vector/index.d.ts +3 -1
- package/dist/esm/component/vector/index.d.ts.map +1 -1
- package/dist/esm/component/vector/index.js +6 -2
- package/dist/esm/component/vector/index.js.map +1 -1
- package/dist/esm/react/deltas.d.ts +29 -0
- package/dist/esm/react/deltas.d.ts.map +1 -0
- package/dist/esm/react/deltas.js +267 -0
- package/dist/esm/react/deltas.js.map +1 -0
- package/dist/esm/react/index.d.ts +4 -49
- package/dist/esm/react/index.d.ts.map +1 -1
- package/dist/esm/react/index.js +15 -446
- package/dist/esm/react/index.js.map +1 -1
- package/dist/esm/react/optimisticallySendMessage.d.ts +8 -0
- package/dist/esm/react/optimisticallySendMessage.d.ts.map +1 -0
- package/dist/esm/react/optimisticallySendMessage.js +40 -0
- package/dist/esm/react/optimisticallySendMessage.js.map +1 -0
- package/dist/esm/react/toUIMessages.d.ts.map +1 -1
- package/dist/esm/react/toUIMessages.js +4 -2
- package/dist/esm/react/toUIMessages.js.map +1 -1
- package/dist/esm/react/types.d.ts +26 -0
- package/dist/esm/react/types.d.ts.map +1 -0
- package/dist/esm/react/types.js +2 -0
- package/dist/esm/react/types.js.map +1 -0
- package/dist/esm/react/useSmoothText.d.ts +20 -0
- package/dist/esm/react/useSmoothText.d.ts.map +1 -0
- package/dist/esm/react/useSmoothText.js +50 -0
- package/dist/esm/react/useSmoothText.js.map +1 -0
- package/package.json +4 -1
- package/src/client/index.ts +41 -23
- package/src/client/streaming.ts +3 -2
- package/src/component/_generated/api.d.ts +2 -2
- package/src/component/messages.ts +26 -15
- package/src/component/streams.ts +3 -4
- package/src/component/users.ts +12 -0
- package/src/component/vector/index.ts +9 -3
- package/src/react/deltas.test.ts +298 -0
- package/src/react/deltas.ts +340 -0
- package/src/react/index.ts +26 -589
- package/src/react/optimisticallySendMessage.ts +46 -0
- package/src/react/toUIMessages.test.ts +301 -0
- package/src/react/toUIMessages.ts +6 -2
- package/src/react/types.ts +52 -0
- package/src/react/useSmoothText.ts +76 -0
package/src/react/index.ts
CHANGED
|
@@ -1,31 +1,27 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { BetterOmit, ErrorMessage, Expand } from "convex-helpers";
|
|
1
|
+
import type { ErrorMessage } from "convex-helpers";
|
|
3
2
|
import {
|
|
4
|
-
insertAtTop,
|
|
5
3
|
type PaginatedQueryArgs,
|
|
6
4
|
usePaginatedQuery,
|
|
7
5
|
type UsePaginatedQueryResult,
|
|
8
6
|
useQuery,
|
|
9
7
|
} from "convex/react";
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
FunctionReference,
|
|
13
|
-
PaginationOptions,
|
|
14
|
-
PaginationResult,
|
|
15
|
-
} from "convex/server";
|
|
16
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
+
import type { FunctionArgs } from "convex/server";
|
|
9
|
+
import { useMemo, useState } from "react";
|
|
17
10
|
import type { MessageDoc } from "../client";
|
|
18
11
|
import type { SyncStreamsReturnValue } from "../client/types";
|
|
19
|
-
import type {
|
|
20
|
-
StreamArgs,
|
|
21
|
-
StreamDelta,
|
|
22
|
-
StreamMessage,
|
|
23
|
-
TextStreamPart,
|
|
24
|
-
} from "../validators";
|
|
12
|
+
import type { StreamArgs } from "../validators";
|
|
25
13
|
import type { UIMessage } from "./toUIMessages";
|
|
26
14
|
import { toUIMessages } from "./toUIMessages";
|
|
27
|
-
import {
|
|
15
|
+
import { mergeDeltas } from "./deltas";
|
|
16
|
+
import type {
|
|
17
|
+
ThreadQuery,
|
|
18
|
+
ThreadStreamQuery,
|
|
19
|
+
ThreadMessagesArgs,
|
|
20
|
+
ThreadMessagesResult,
|
|
21
|
+
} from "./types";
|
|
28
22
|
|
|
23
|
+
export { optimisticallySendMessage } from "./optimisticallySendMessage";
|
|
24
|
+
export { useSmoothText } from "./useSmoothText";
|
|
29
25
|
export { toUIMessages, type UIMessage };
|
|
30
26
|
|
|
31
27
|
/**
|
|
@@ -102,8 +98,10 @@ export function useThreadMessages<
|
|
|
102
98
|
|
|
103
99
|
// These are streaming messages that will not include full messages.
|
|
104
100
|
const streamMessages = useStreamingThreadMessages(
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
query as ThreadStreamQuery<
|
|
102
|
+
ThreadMessagesArgs<Query>,
|
|
103
|
+
ThreadMessagesResult<Query>
|
|
104
|
+
>,
|
|
107
105
|
!options.stream ? "skip" : args
|
|
108
106
|
);
|
|
109
107
|
|
|
@@ -157,7 +155,7 @@ export function useStreamingThreadMessages<
|
|
|
157
155
|
// cursor. There can be multiple messages in the same stream, e.g. for tool
|
|
158
156
|
// calls.
|
|
159
157
|
const [streams, setStreams] = useState<
|
|
160
|
-
Array<{
|
|
158
|
+
Array<{ streamId: string; cursor: number; messages: MessageDoc[] }>
|
|
161
159
|
>([]);
|
|
162
160
|
// Get all the active streams
|
|
163
161
|
const streamList = useQuery(
|
|
@@ -179,8 +177,7 @@ export function useStreamingThreadMessages<
|
|
|
179
177
|
throw new Error("Expected list streams");
|
|
180
178
|
}
|
|
181
179
|
return streamList.streams.messages.map(({ streamId }) => {
|
|
182
|
-
const stream = streams.find((s) => s.
|
|
183
|
-
// Because of the invariant, we can just take the last chunk's end.
|
|
180
|
+
const stream = streams.find((s) => s.streamId === streamId);
|
|
184
181
|
const cursor = stream?.cursor ?? 0;
|
|
185
182
|
return { streamId, cursor };
|
|
186
183
|
});
|
|
@@ -199,21 +196,22 @@ export function useStreamingThreadMessages<
|
|
|
199
196
|
| { streams: Extract<SyncStreamsReturnValue, { kind: "deltas" }> }
|
|
200
197
|
| undefined;
|
|
201
198
|
// Merge any deltas into the streamChunks, keeping it unmodified if unchanged.
|
|
199
|
+
const threadId = args === "skip" ? undefined : args.threadId;
|
|
202
200
|
const [messages, newStreams, changed] = useMemo(() => {
|
|
203
|
-
if (
|
|
204
|
-
if (!streamList) return [undefined,
|
|
201
|
+
if (!threadId) return [undefined, [], false];
|
|
202
|
+
if (!streamList) return [undefined, [], false];
|
|
205
203
|
if (cursorQuery && cursorQuery.streams?.kind !== "deltas") {
|
|
206
204
|
throw new Error("Expected deltas streams");
|
|
207
205
|
}
|
|
208
206
|
return mergeDeltas(
|
|
209
|
-
|
|
207
|
+
threadId,
|
|
210
208
|
streamList.streams.messages,
|
|
211
209
|
streams,
|
|
212
210
|
cursorQuery?.streams?.deltas ?? []
|
|
213
211
|
);
|
|
214
|
-
}, [cursorQuery, streams, streamList]);
|
|
212
|
+
}, [threadId, cursorQuery, streams, streamList]);
|
|
215
213
|
// Now assemble the chunks into messages
|
|
216
|
-
if (
|
|
214
|
+
if (!threadId) {
|
|
217
215
|
return undefined;
|
|
218
216
|
}
|
|
219
217
|
if (changed) {
|
|
@@ -222,567 +220,6 @@ export function useStreamingThreadMessages<
|
|
|
222
220
|
return messages as ThreadMessagesResult<Query>[] | undefined;
|
|
223
221
|
}
|
|
224
222
|
|
|
225
|
-
/**
|
|
226
|
-
* A hook that smoothly displays text as it is streamed.
|
|
227
|
-
*
|
|
228
|
-
* @param text The text to display. Pass in the full text each time.
|
|
229
|
-
* @param charsPerSec The number of characters to display per second.
|
|
230
|
-
* @returns A tuple of the visible text and the state of the smooth text,
|
|
231
|
-
* including the current cursor position and whether it's still streaming.
|
|
232
|
-
* This allows you to decide if it's too far behind and you want to adjust
|
|
233
|
-
* the charsPerSec or just prefer the full text.
|
|
234
|
-
*/
|
|
235
|
-
export function useSmoothText(
|
|
236
|
-
text: string,
|
|
237
|
-
{
|
|
238
|
-
charsPerSec = 512,
|
|
239
|
-
}: {
|
|
240
|
-
/**
|
|
241
|
-
* The number of characters to display per second.
|
|
242
|
-
*/
|
|
243
|
-
charsPerSec?: number;
|
|
244
|
-
} = {}
|
|
245
|
-
): [string, { cursor: number; isStreaming: boolean }] {
|
|
246
|
-
const [visibleText, setVisibleText] = useState(text);
|
|
247
|
-
const smoothState = useRef({ lastUpdated: Date.now(), cursor: text.length });
|
|
248
|
-
|
|
249
|
-
const isStreaming = smoothState.current.cursor < text.length;
|
|
250
|
-
|
|
251
|
-
useEffect(() => {
|
|
252
|
-
if (!isStreaming) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
function update() {
|
|
256
|
-
if (smoothState.current.cursor >= text.length) {
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
const now = Date.now();
|
|
260
|
-
const timeSinceLastUpdate = now - smoothState.current.lastUpdated;
|
|
261
|
-
const chars = Math.floor((timeSinceLastUpdate * charsPerSec) / 1000);
|
|
262
|
-
smoothState.current.cursor = Math.min(
|
|
263
|
-
smoothState.current.cursor + chars,
|
|
264
|
-
text.length
|
|
265
|
-
);
|
|
266
|
-
smoothState.current.lastUpdated = now;
|
|
267
|
-
setVisibleText(text.slice(0, smoothState.current.cursor));
|
|
268
|
-
}
|
|
269
|
-
update();
|
|
270
|
-
const interval = setInterval(() => {
|
|
271
|
-
update();
|
|
272
|
-
}, 1000 / 60);
|
|
273
|
-
return () => clearInterval(interval);
|
|
274
|
-
}, [text, isStreaming, charsPerSec]);
|
|
275
|
-
|
|
276
|
-
return [visibleText, { cursor: smoothState.current.cursor, isStreaming }];
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export function optimisticallySendMessage(
|
|
280
|
-
query: ThreadQuery<unknown, MessageDoc>
|
|
281
|
-
): (
|
|
282
|
-
store: OptimisticLocalStore,
|
|
283
|
-
args: { threadId: string; prompt: string }
|
|
284
|
-
) => void {
|
|
285
|
-
return (store, args) => {
|
|
286
|
-
const queries = store.getAllQueries(query);
|
|
287
|
-
let maxOrder = 0;
|
|
288
|
-
let maxStepOrder = 0;
|
|
289
|
-
for (const q of queries) {
|
|
290
|
-
if (q.args?.threadId !== args.threadId) continue;
|
|
291
|
-
if (q.args.streamArgs) continue;
|
|
292
|
-
for (const m of q.value?.page ?? []) {
|
|
293
|
-
maxOrder = Math.max(maxOrder, m.order);
|
|
294
|
-
maxStepOrder = Math.max(maxStepOrder, m.stepOrder);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
const order = maxOrder + 1;
|
|
298
|
-
const stepOrder = 0;
|
|
299
|
-
insertAtTop({
|
|
300
|
-
paginatedQuery: query,
|
|
301
|
-
argsToMatch: { threadId: args.threadId, streamArgs: undefined },
|
|
302
|
-
item: {
|
|
303
|
-
_creationTime: Date.now(),
|
|
304
|
-
_id: crypto.randomUUID(),
|
|
305
|
-
order,
|
|
306
|
-
stepOrder,
|
|
307
|
-
status: "pending",
|
|
308
|
-
threadId: args.threadId,
|
|
309
|
-
tool: false,
|
|
310
|
-
message: {
|
|
311
|
-
role: "user",
|
|
312
|
-
content: args.prompt,
|
|
313
|
-
},
|
|
314
|
-
text: args.prompt,
|
|
315
|
-
},
|
|
316
|
-
localQueryStore: store,
|
|
317
|
-
});
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function mergeDeltas(
|
|
322
|
-
threadId: string,
|
|
323
|
-
streamMessages: StreamMessage[],
|
|
324
|
-
existingStreams: Array<{
|
|
325
|
-
stream: StreamMessage;
|
|
326
|
-
cursor: number;
|
|
327
|
-
messages: MessageDoc[];
|
|
328
|
-
}>,
|
|
329
|
-
allDeltas: StreamDelta[]
|
|
330
|
-
): [
|
|
331
|
-
MessageDoc[],
|
|
332
|
-
Array<{ stream: StreamMessage; cursor: number; messages: MessageDoc[] }>,
|
|
333
|
-
boolean,
|
|
334
|
-
] {
|
|
335
|
-
const newStreams: Array<{
|
|
336
|
-
stream: StreamMessage;
|
|
337
|
-
cursor: number;
|
|
338
|
-
messages: MessageDoc[];
|
|
339
|
-
}> = [];
|
|
340
|
-
// Seed the existing chunks
|
|
341
|
-
let changed = false;
|
|
342
|
-
for (const streamMessage of streamMessages) {
|
|
343
|
-
const deltas = allDeltas
|
|
344
|
-
.filter((d) => d.streamId === streamMessage.streamId)
|
|
345
|
-
.sort((a, b) => a.start - b.start);
|
|
346
|
-
const existing = existingStreams.find(
|
|
347
|
-
(s) => s.stream.streamId === streamMessage.streamId
|
|
348
|
-
);
|
|
349
|
-
const [newStream, messageChanged] = applyDeltasToStreamMessages(
|
|
350
|
-
threadId,
|
|
351
|
-
streamMessage,
|
|
352
|
-
existing,
|
|
353
|
-
deltas
|
|
354
|
-
);
|
|
355
|
-
newStreams.push(newStream);
|
|
356
|
-
if (messageChanged) changed = true;
|
|
357
|
-
}
|
|
358
|
-
for (const { stream } of existingStreams) {
|
|
359
|
-
if (!newStreams.find((s) => s.stream.streamId === stream.streamId)) {
|
|
360
|
-
// There's a stream that's no longer active.
|
|
361
|
-
changed = true;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
const messages = newStreams
|
|
365
|
-
.sort(
|
|
366
|
-
(a, b) =>
|
|
367
|
-
a.stream.order - b.stream.order ||
|
|
368
|
-
a.stream.stepOrder - b.stream.stepOrder
|
|
369
|
-
)
|
|
370
|
-
.map((s) => s.messages)
|
|
371
|
-
.flat();
|
|
372
|
-
return [messages, newStreams, changed];
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function applyDeltasToStreamMessages(
|
|
376
|
-
threadId: string,
|
|
377
|
-
streamMessage: StreamMessage,
|
|
378
|
-
existing:
|
|
379
|
-
| { stream: StreamMessage; cursor: number; messages: MessageDoc[] }
|
|
380
|
-
| undefined,
|
|
381
|
-
deltas: StreamDelta[]
|
|
382
|
-
): [
|
|
383
|
-
{ stream: StreamMessage; cursor: number; messages: MessageDoc[] },
|
|
384
|
-
boolean,
|
|
385
|
-
] {
|
|
386
|
-
let changed = false;
|
|
387
|
-
const newStream = {
|
|
388
|
-
stream: streamMessage,
|
|
389
|
-
cursor: existing?.cursor ?? 0,
|
|
390
|
-
messages: existing?.messages ?? [],
|
|
391
|
-
};
|
|
392
|
-
let parts: TextStreamPart[] = [];
|
|
393
|
-
for (const delta of deltas) {
|
|
394
|
-
if (delta.parts.length === 0) {
|
|
395
|
-
console.warn(`Got delta for stream ${delta.streamId} with no parts`);
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
if (newStream.cursor !== delta.start) {
|
|
399
|
-
if (newStream.cursor >= delta.end) {
|
|
400
|
-
console.debug(
|
|
401
|
-
`Got duplicate delta for stream ${delta.streamId} at ${delta.start}`
|
|
402
|
-
);
|
|
403
|
-
continue;
|
|
404
|
-
} else if (newStream.cursor < delta.start) {
|
|
405
|
-
console.warn(
|
|
406
|
-
`Got delta for stream ${delta.streamId} that has a gap ${newStream.cursor} -> ${delta.start}`
|
|
407
|
-
);
|
|
408
|
-
continue;
|
|
409
|
-
} else {
|
|
410
|
-
throw new Error(
|
|
411
|
-
`Got unexpected delta for stream ${delta.streamId}: delta: ${delta.start} -> ${delta.end} existing cursor: ${newStream.cursor}`
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
changed = true;
|
|
416
|
-
newStream.cursor = delta.end;
|
|
417
|
-
parts.push(...delta.parts);
|
|
418
|
-
}
|
|
419
|
-
if (!changed) {
|
|
420
|
-
return [existing ?? newStream, false];
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (!newStream.messages.at(-1)) {
|
|
424
|
-
newStream.messages.push(
|
|
425
|
-
createStreamingMessage(
|
|
426
|
-
threadId,
|
|
427
|
-
streamMessage,
|
|
428
|
-
parts[0]!,
|
|
429
|
-
newStream.messages.length
|
|
430
|
-
)
|
|
431
|
-
);
|
|
432
|
-
parts = parts.slice(1);
|
|
433
|
-
}
|
|
434
|
-
let currentMessage = newStream.messages.at(-1)!;
|
|
435
|
-
let lastContent = getLastContent(currentMessage);
|
|
436
|
-
for (const part of parts) {
|
|
437
|
-
let contentToAdd:
|
|
438
|
-
| TextPart
|
|
439
|
-
| ToolCallPart
|
|
440
|
-
| { type: "reasoning"; text: string }
|
|
441
|
-
| ToolResultPart
|
|
442
|
-
| undefined;
|
|
443
|
-
const isToolRole = part.type === "source" || part.type === "tool-result";
|
|
444
|
-
if (isToolRole !== (currentMessage.message!.role === "tool")) {
|
|
445
|
-
currentMessage = createStreamingMessage(
|
|
446
|
-
threadId,
|
|
447
|
-
streamMessage,
|
|
448
|
-
part,
|
|
449
|
-
newStream.messages.length
|
|
450
|
-
);
|
|
451
|
-
lastContent = getLastContent(currentMessage);
|
|
452
|
-
newStream.messages.push(currentMessage);
|
|
453
|
-
}
|
|
454
|
-
switch (part.type) {
|
|
455
|
-
case "text-delta":
|
|
456
|
-
currentMessage.text += part.textDelta;
|
|
457
|
-
if (lastContent?.type === "text") {
|
|
458
|
-
lastContent.text += part.textDelta;
|
|
459
|
-
} else {
|
|
460
|
-
contentToAdd = {
|
|
461
|
-
type: "text",
|
|
462
|
-
text: part.textDelta,
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
break;
|
|
466
|
-
case "tool-call-streaming-start":
|
|
467
|
-
currentMessage.tool = true;
|
|
468
|
-
contentToAdd = {
|
|
469
|
-
type: "tool-call",
|
|
470
|
-
toolCallId: part.toolCallId,
|
|
471
|
-
toolName: part.toolName,
|
|
472
|
-
args: "",
|
|
473
|
-
};
|
|
474
|
-
break;
|
|
475
|
-
case "tool-call-delta":
|
|
476
|
-
{
|
|
477
|
-
currentMessage.tool = true;
|
|
478
|
-
if (lastContent?.type !== "tool-call") {
|
|
479
|
-
throw new Error("Expected last content to be a tool call");
|
|
480
|
-
}
|
|
481
|
-
if (typeof lastContent.args !== "string") {
|
|
482
|
-
throw new Error("Expected args to be a string");
|
|
483
|
-
}
|
|
484
|
-
lastContent.args += part.argsTextDelta;
|
|
485
|
-
}
|
|
486
|
-
break;
|
|
487
|
-
case "tool-call":
|
|
488
|
-
currentMessage.tool = true;
|
|
489
|
-
contentToAdd = part;
|
|
490
|
-
break;
|
|
491
|
-
case "reasoning":
|
|
492
|
-
if (lastContent?.type === "reasoning") {
|
|
493
|
-
lastContent.text += part.textDelta;
|
|
494
|
-
} else {
|
|
495
|
-
contentToAdd = {
|
|
496
|
-
type: "reasoning",
|
|
497
|
-
text: part.textDelta,
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
break;
|
|
501
|
-
case "source":
|
|
502
|
-
if (!currentMessage.sources) {
|
|
503
|
-
currentMessage.sources = [];
|
|
504
|
-
}
|
|
505
|
-
currentMessage.sources.push(part.source);
|
|
506
|
-
break;
|
|
507
|
-
case "tool-result":
|
|
508
|
-
contentToAdd = part;
|
|
509
|
-
break;
|
|
510
|
-
default:
|
|
511
|
-
console.warn(`Received unexpected part: ${JSON.stringify(part)}`);
|
|
512
|
-
break;
|
|
513
|
-
}
|
|
514
|
-
if (contentToAdd) {
|
|
515
|
-
if (!currentMessage.message!.content) {
|
|
516
|
-
currentMessage.message!.content = [];
|
|
517
|
-
}
|
|
518
|
-
if (!Array.isArray(currentMessage.message?.content)) {
|
|
519
|
-
throw new Error("Expected message content to be an array");
|
|
520
|
-
}
|
|
521
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
522
|
-
currentMessage.message.content.push(contentToAdd as any);
|
|
523
|
-
lastContent = contentToAdd;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
return [newStream, true];
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function getLastContent(message: MessageDoc) {
|
|
530
|
-
if (Array.isArray(message.message?.content)) {
|
|
531
|
-
return message.message.content.at(-1);
|
|
532
|
-
}
|
|
533
|
-
return undefined;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function createStreamingMessage(
|
|
537
|
-
threadId: string,
|
|
538
|
-
message: StreamMessage,
|
|
539
|
-
part: TextStreamPart,
|
|
540
|
-
index: number
|
|
541
|
-
): MessageDoc {
|
|
542
|
-
const { streamId, ...rest } = message;
|
|
543
|
-
const metadata: MessageDoc = {
|
|
544
|
-
_id: `${streamId}-${index}`,
|
|
545
|
-
_creationTime: Date.now(),
|
|
546
|
-
status: "pending",
|
|
547
|
-
threadId,
|
|
548
|
-
tool: false,
|
|
549
|
-
...rest,
|
|
550
|
-
};
|
|
551
|
-
switch (part.type) {
|
|
552
|
-
case "text-delta":
|
|
553
|
-
return {
|
|
554
|
-
...metadata,
|
|
555
|
-
message: {
|
|
556
|
-
role: "assistant",
|
|
557
|
-
content: [{ type: "text", text: part.textDelta }],
|
|
558
|
-
},
|
|
559
|
-
text: part.textDelta,
|
|
560
|
-
};
|
|
561
|
-
case "tool-call-streaming-start":
|
|
562
|
-
return {
|
|
563
|
-
...metadata,
|
|
564
|
-
tool: true,
|
|
565
|
-
message: {
|
|
566
|
-
role: "assistant",
|
|
567
|
-
content: [
|
|
568
|
-
{
|
|
569
|
-
type: "tool-call",
|
|
570
|
-
toolName: part.toolName,
|
|
571
|
-
toolCallId: part.toolCallId,
|
|
572
|
-
args: "", // when it's a string, it's a partial call
|
|
573
|
-
},
|
|
574
|
-
],
|
|
575
|
-
},
|
|
576
|
-
};
|
|
577
|
-
case "reasoning":
|
|
578
|
-
return {
|
|
579
|
-
...metadata,
|
|
580
|
-
message: {
|
|
581
|
-
role: "assistant",
|
|
582
|
-
content: [{ type: "reasoning", text: part.textDelta }],
|
|
583
|
-
},
|
|
584
|
-
reasoning: part.textDelta,
|
|
585
|
-
};
|
|
586
|
-
case "source":
|
|
587
|
-
console.warn("Received source part first??");
|
|
588
|
-
return {
|
|
589
|
-
...metadata,
|
|
590
|
-
tool: true,
|
|
591
|
-
message: { role: "tool", content: [] },
|
|
592
|
-
sources: [part.source],
|
|
593
|
-
};
|
|
594
|
-
case "tool-call":
|
|
595
|
-
return {
|
|
596
|
-
...metadata,
|
|
597
|
-
tool: true,
|
|
598
|
-
message: { role: "assistant", content: [part] },
|
|
599
|
-
};
|
|
600
|
-
case "tool-call-delta":
|
|
601
|
-
console.warn("Received tool call delta part first??");
|
|
602
|
-
return {
|
|
603
|
-
...metadata,
|
|
604
|
-
tool: true,
|
|
605
|
-
message: {
|
|
606
|
-
role: "assistant",
|
|
607
|
-
content: [
|
|
608
|
-
{
|
|
609
|
-
type: "tool-call",
|
|
610
|
-
toolCallId: part.toolCallId,
|
|
611
|
-
toolName: part.toolName,
|
|
612
|
-
args: part.argsTextDelta,
|
|
613
|
-
},
|
|
614
|
-
],
|
|
615
|
-
},
|
|
616
|
-
};
|
|
617
|
-
case "tool-result":
|
|
618
|
-
return {
|
|
619
|
-
...metadata,
|
|
620
|
-
tool: true,
|
|
621
|
-
message: { role: "tool", content: [part] },
|
|
622
|
-
};
|
|
623
|
-
default:
|
|
624
|
-
throw new Error(`Unexpected part type: ${JSON.stringify(part)}`);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
type ThreadQuery<
|
|
629
|
-
Args = unknown,
|
|
630
|
-
M extends MessageDoc = MessageDoc,
|
|
631
|
-
> = FunctionReference<
|
|
632
|
-
"query",
|
|
633
|
-
"public",
|
|
634
|
-
{
|
|
635
|
-
threadId: string;
|
|
636
|
-
paginationOpts: PaginationOptions;
|
|
637
|
-
// TODO: will this allow passing a function that doesn't have this param?
|
|
638
|
-
/**
|
|
639
|
-
* If { stream: true } is passed, it will also query for stream deltas.
|
|
640
|
-
* In order for this to work, the query must take as an argument streamArgs.
|
|
641
|
-
*/
|
|
642
|
-
streamArgs?: StreamArgs;
|
|
643
|
-
} & Args,
|
|
644
|
-
PaginationResult<M> & { streams?: SyncStreamsReturnValue }
|
|
645
|
-
>;
|
|
646
|
-
|
|
647
|
-
type ThreadStreamQuery<
|
|
648
|
-
Args = Record<string, unknown>,
|
|
649
|
-
M extends MessageDoc = MessageDoc,
|
|
650
|
-
> = FunctionReference<
|
|
651
|
-
"query",
|
|
652
|
-
"public",
|
|
653
|
-
{
|
|
654
|
-
threadId: string;
|
|
655
|
-
paginationOpts: PaginationOptions;
|
|
656
|
-
streamArgs?: StreamArgs; // required for stream query
|
|
657
|
-
} & Args,
|
|
658
|
-
PaginationResult<M> & { streams: SyncStreamsReturnValue }
|
|
659
|
-
>;
|
|
660
|
-
|
|
661
|
-
type ThreadMessagesArgs<Query extends ThreadQuery<unknown, MessageDoc>> =
|
|
662
|
-
Query extends ThreadQuery<unknown, MessageDoc>
|
|
663
|
-
? Expand<BetterOmit<FunctionArgs<Query>, "paginationOpts" | "streamArgs">>
|
|
664
|
-
: never;
|
|
665
|
-
|
|
666
|
-
type ThreadMessagesResult<Query extends ThreadQuery<unknown, MessageDoc>> =
|
|
667
|
-
Query extends ThreadQuery<unknown, infer M> ? M : never;
|
|
668
|
-
|
|
669
|
-
// TODO: pass in the messages we need to watch? that way it can be consistent..
|
|
670
|
-
|
|
671
|
-
// export function streamMessagesToUIMessages(
|
|
672
|
-
// messages: StreamsChunk[],
|
|
673
|
-
// keyOrder: string[]
|
|
674
|
-
// ): UIMessageOrdered[] {
|
|
675
|
-
// const uiMessagesByMessageId: Record<string, UIMessageOrdered[]> = {};
|
|
676
|
-
// for (const message of messages) {
|
|
677
|
-
// if (message.deltas.length === 0) {
|
|
678
|
-
// continue;
|
|
679
|
-
// }
|
|
680
|
-
// if (!uiMessagesByMessageId[message.key]) {
|
|
681
|
-
// uiMessagesByMessageId[message.key] = [];
|
|
682
|
-
// }
|
|
683
|
-
// if (uiMessagesByMessageId[message.key].length === 0) {
|
|
684
|
-
// uiMessagesByMessageId[message.key] = [
|
|
685
|
-
// createUIMessageFromPart(message.deltas[0], {
|
|
686
|
-
// id: message.key,
|
|
687
|
-
// createdAt: new Date(),
|
|
688
|
-
// }),
|
|
689
|
-
// ];
|
|
690
|
-
// }
|
|
691
|
-
// const currentMessage = uiMessagesByMessageId[message.key].at(-1)!;
|
|
692
|
-
// const lastPart = currentMessage.parts.at(-1);
|
|
693
|
-
// for (const delta of message.deltas) {
|
|
694
|
-
// switch (delta.type) {
|
|
695
|
-
// case "text-delta":
|
|
696
|
-
// currentMessage.content += delta.textDelta;
|
|
697
|
-
// if (lastPart?.type === "text") {
|
|
698
|
-
// lastPart.text += delta.textDelta;
|
|
699
|
-
// } else {
|
|
700
|
-
// currentMessage.parts.push({
|
|
701
|
-
// type: "text",
|
|
702
|
-
// text: delta.textDelta,
|
|
703
|
-
// });
|
|
704
|
-
// }
|
|
705
|
-
// break;
|
|
706
|
-
// case "tool-call-delta": {
|
|
707
|
-
// let lastToolInvocation: ToolInvocationUIPart | undefined;
|
|
708
|
-
// for (let i = currentMessage.parts.length - 1; i >= 0; i--) {
|
|
709
|
-
// const part = currentMessage.parts[i];
|
|
710
|
-
// if (
|
|
711
|
-
// part.type === "tool-invocation" &&
|
|
712
|
-
// part.toolInvocation.state === "partial-call"
|
|
713
|
-
// ) {
|
|
714
|
-
// lastToolInvocation = part;
|
|
715
|
-
// break;
|
|
716
|
-
// }
|
|
717
|
-
// }
|
|
718
|
-
// if (lastToolInvocation) {
|
|
719
|
-
// lastToolInvocation.toolInvocation = {
|
|
720
|
-
// ...lastToolInvocation.toolInvocation,
|
|
721
|
-
// state: "call",
|
|
722
|
-
// };
|
|
723
|
-
// break;
|
|
724
|
-
// }
|
|
725
|
-
// console.error(
|
|
726
|
-
// `Received a tool call delta without a previous tool invocation: ${JSON.stringify(currentMessage.parts)}, creating one anyways...`
|
|
727
|
-
// );
|
|
728
|
-
// }
|
|
729
|
-
// // fallthrough
|
|
730
|
-
// case "tool-call-streaming-start":
|
|
731
|
-
// currentMessage.parts.push({
|
|
732
|
-
// type: "tool-invocation",
|
|
733
|
-
// toolInvocation: {
|
|
734
|
-
// toolCallId: delta.toolCallId,
|
|
735
|
-
// toolName: delta.toolName,
|
|
736
|
-
// args: {},
|
|
737
|
-
// state: "partial-call",
|
|
738
|
-
// step:
|
|
739
|
-
// currentMessage.parts.filter(
|
|
740
|
-
// (part) => part.type === "tool-invocation"
|
|
741
|
-
// ).length + 1,
|
|
742
|
-
// },
|
|
743
|
-
// });
|
|
744
|
-
// break;
|
|
745
|
-
// case "tool-call":
|
|
746
|
-
// currentMessage.parts.push({
|
|
747
|
-
// type: "tool-invocation",
|
|
748
|
-
// toolInvocation: {
|
|
749
|
-
// toolCallId: delta.toolCallId,
|
|
750
|
-
// toolName: delta.toolName,
|
|
751
|
-
// args: delta.args,
|
|
752
|
-
// state: "call",
|
|
753
|
-
// step:
|
|
754
|
-
// currentMessage.parts.filter(
|
|
755
|
-
// (part) => part.type === "tool-invocation"
|
|
756
|
-
// ).length + 1,
|
|
757
|
-
// },
|
|
758
|
-
// });
|
|
759
|
-
// break;
|
|
760
|
-
// case "reasoning":
|
|
761
|
-
// if (lastPart?.type === "reasoning") {
|
|
762
|
-
// lastPart.reasoning += delta.textDelta;
|
|
763
|
-
// } else {
|
|
764
|
-
// currentMessage.parts.push({
|
|
765
|
-
// type: "reasoning",
|
|
766
|
-
// reasoning: delta.textDelta,
|
|
767
|
-
// details: [],
|
|
768
|
-
// });
|
|
769
|
-
// }
|
|
770
|
-
// break;
|
|
771
|
-
// case "source":
|
|
772
|
-
// currentMessage.parts.push({
|
|
773
|
-
// type: "source",
|
|
774
|
-
// source: delta.source,
|
|
775
|
-
// });
|
|
776
|
-
// break;
|
|
777
|
-
// default:
|
|
778
|
-
// console.warn(`Received unexpected part: ${JSON.stringify(delta)}`);
|
|
779
|
-
// break;
|
|
780
|
-
// }
|
|
781
|
-
// }
|
|
782
|
-
// }
|
|
783
|
-
// return keyOrder.map((key) => uiMessagesByMessageId[key]).flat();
|
|
784
|
-
// }
|
|
785
|
-
|
|
786
223
|
if (typeof window === "undefined") {
|
|
787
224
|
throw new Error("this is frontend code, but it's running somewhere else!");
|
|
788
225
|
}
|
|
@@ -849,7 +286,7 @@ export function useStreamingText(
|
|
|
849
286
|
setLoading(false);
|
|
850
287
|
}
|
|
851
288
|
},
|
|
852
|
-
[threadId, token]
|
|
289
|
+
[threadId, token, url]
|
|
853
290
|
);
|
|
854
291
|
return [{ text, loading, error }, readStream] as const;
|
|
855
292
|
}
|