@convex-dev/agent 0.5.0-alpha.1 → 0.6.0-alpha.1
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/MIGRATION.md +153 -0
- package/README.md +32 -27
- package/dist/UIMessages.d.ts +46 -0
- package/dist/UIMessages.d.ts.map +1 -0
- package/dist/UIMessages.js +546 -0
- package/dist/UIMessages.js.map +1 -0
- package/dist/client/createTool.d.ts +126 -27
- package/dist/client/createTool.d.ts.map +1 -1
- package/dist/client/createTool.js +67 -12
- 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 +1335 -204
- package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/client/definePlaygroundAPI.js +52 -28
- package/dist/client/definePlaygroundAPI.js.map +1 -1
- package/dist/client/files.d.ts +20 -7
- package/dist/client/files.d.ts.map +1 -1
- package/dist/client/files.js +68 -11
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +1116 -978
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +332 -747
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +461 -0
- package/dist/client/messages.d.ts.map +1 -0
- package/dist/client/messages.js +106 -0
- package/dist/client/messages.js.map +1 -0
- package/dist/client/mockModel.d.ts +42 -0
- package/dist/client/mockModel.d.ts.map +1 -0
- package/dist/client/mockModel.js +182 -0
- package/dist/client/mockModel.js.map +1 -0
- package/dist/client/saveInputMessages.d.ts +20 -0
- package/dist/client/saveInputMessages.d.ts.map +1 -0
- package/dist/client/saveInputMessages.js +58 -0
- package/dist/client/saveInputMessages.js.map +1 -0
- package/dist/client/search.d.ts +350 -39
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +350 -39
- package/dist/client/search.js.map +1 -1
- package/dist/client/start.d.ts +84 -0
- package/dist/client/start.d.ts.map +1 -0
- package/dist/client/start.js +185 -0
- package/dist/client/start.js.map +1 -0
- package/dist/client/streamText.d.ts +46 -0
- package/dist/client/streamText.d.ts.map +1 -0
- package/dist/client/streamText.js +117 -0
- package/dist/client/streamText.js.map +1 -0
- package/dist/client/streaming.d.ts +3716 -32
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/streaming.js +161 -59
- 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 +266 -128
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/utils.d.ts +4 -0
- package/dist/client/utils.d.ts.map +1 -0
- package/dist/client/utils.js +21 -0
- package/dist/client/utils.js.map +1 -0
- package/dist/component/_generated/api.d.ts +24 -2178
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js +10 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +3120 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +4 -18
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +10 -38
- package/dist/component/_generated/server.d.ts.map +1 -1
- package/dist/component/_generated/server.js +9 -5
- package/dist/component/_generated/server.js.map +1 -1
- package/dist/component/files.d.ts +16 -10
- package/dist/component/files.d.ts.map +1 -1
- package/dist/component/files.js +10 -2
- package/dist/component/files.js.map +1 -1
- package/dist/component/messages.d.ts +2578 -366
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +397 -154
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +5697 -3584
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +18 -41
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.d.ts +39 -339
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +114 -73
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts +13 -13
- package/dist/component/users.d.ts +7 -7
- package/dist/component/vector/index.d.ts +1 -1
- package/dist/component/vector/index.d.ts.map +1 -1
- package/dist/component/vector/index.js +1 -3
- package/dist/component/vector/index.js.map +1 -1
- package/dist/deltas.d.ts +43 -0
- package/dist/deltas.d.ts.map +1 -0
- package/dist/deltas.js +446 -0
- package/dist/deltas.js.map +1 -0
- package/dist/mapping.d.ts +38 -20
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +365 -97
- package/dist/mapping.js.map +1 -1
- 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/index.d.ts +5 -77
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +6 -160
- package/dist/react/index.js.map +1 -1
- package/dist/react/optimisticallySendMessage.d.ts +36 -3
- package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
- package/dist/react/optimisticallySendMessage.js +35 -9
- package/dist/react/optimisticallySendMessage.js.map +1 -1
- package/dist/react/types.d.ts +4 -18
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/useDeltaStreams.d.ts +10 -0
- package/dist/react/useDeltaStreams.d.ts.map +1 -0
- package/dist/react/useDeltaStreams.js +106 -0
- package/dist/react/useDeltaStreams.js.map +1 -0
- package/dist/react/useSmoothText.d.ts +13 -12
- package/dist/react/useSmoothText.d.ts.map +1 -1
- package/dist/react/useSmoothText.js +32 -15
- package/dist/react/useSmoothText.js.map +1 -1
- package/dist/react/useStreamingUIMessages.d.ts +22 -0
- package/dist/react/useStreamingUIMessages.d.ts.map +1 -0
- package/dist/react/useStreamingUIMessages.js +92 -0
- package/dist/react/useStreamingUIMessages.js.map +1 -0
- package/dist/react/useThreadMessages.d.ts +104 -0
- package/dist/react/useThreadMessages.d.ts.map +1 -0
- package/dist/react/useThreadMessages.js +148 -0
- package/dist/react/useThreadMessages.js.map +1 -0
- package/dist/react/useUIMessages.d.ts +96 -0
- package/dist/react/useUIMessages.d.ts.map +1 -0
- package/dist/react/useUIMessages.js +108 -0
- package/dist/react/useUIMessages.js.map +1 -0
- package/dist/shared.d.ts +20 -4
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +45 -8
- package/dist/shared.js.map +1 -1
- package/dist/validators.d.ts +22981 -5666
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +245 -137
- package/dist/validators.js.map +1 -1
- package/package.json +101 -51
- package/src/UIMessages.combineUIMessages.test.ts +239 -0
- package/src/UIMessages.test.ts +273 -0
- package/src/UIMessages.ts +739 -0
- package/src/client/approval.test.ts +350 -0
- package/src/client/createTool.ts +291 -76
- package/src/client/defaultComponent.ts +17 -0
- package/src/client/definePlaygroundAPI.ts +67 -31
- package/src/client/files.ts +100 -20
- package/src/client/index.test.ts +40 -85
- package/src/client/index.ts +638 -1289
- package/src/client/messages.ts +237 -0
- package/src/client/mockModel.ts +252 -0
- package/src/client/saveInputMessages.test.ts +583 -0
- package/src/client/saveInputMessages.ts +101 -0
- package/src/client/search.test.ts +1207 -0
- package/src/client/search.ts +581 -70
- package/src/client/start.ts +327 -0
- package/src/client/streamText.ts +187 -0
- package/src/client/streaming.test.ts +186 -0
- package/src/client/streaming.ts +241 -97
- package/src/client/threads.ts +83 -0
- package/src/client/types.ts +370 -219
- package/src/client/utils.ts +27 -0
- package/src/component/_generated/api.ts +64 -0
- package/src/component/_generated/component.ts +4902 -0
- package/src/component/_generated/{server.d.ts → server.ts} +33 -21
- package/src/component/files.ts +11 -2
- package/src/component/messages.test.ts +195 -51
- package/src/component/messages.ts +500 -201
- package/src/component/schema.ts +20 -46
- package/src/component/setup.test.ts +7 -0
- package/src/component/streams.ts +184 -83
- package/src/component/users.test.ts +0 -1
- package/src/component/vector/index.ts +1 -3
- package/src/deltas.test.ts +626 -0
- package/src/deltas.ts +569 -0
- package/src/fromUIMessages.test.ts +497 -0
- package/src/mapping.test.ts +180 -6
- package/src/mapping.ts +479 -162
- package/src/react/SmoothText.tsx +9 -0
- package/src/react/index.ts +10 -230
- package/src/react/optimisticallySendMessage.ts +55 -12
- package/src/react/types.ts +6 -39
- package/src/react/useDeltaStreams.ts +160 -0
- package/src/react/useSmoothText.ts +56 -36
- package/src/react/useStreamingUIMessages.ts +143 -0
- package/src/react/useThreadMessages.ts +262 -0
- package/src/react/useUIMessages.test.ts +255 -0
- package/src/react/useUIMessages.ts +195 -0
- package/src/shared.ts +88 -12
- package/src/test.ts +18 -0
- package/src/toUIMessages.test.ts +1269 -0
- package/src/validators.test.ts +18 -19
- package/src/validators.ts +325 -185
- package/dist/client/_generated/_ignore.d.ts +0 -1
- package/dist/client/_generated/_ignore.d.ts.map +0 -1
- package/dist/client/_generated/_ignore.js +0 -3
- package/dist/client/_generated/_ignore.js.map +0 -1
- 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/dist/package.json +0 -3
- package/dist/react/deltas.d.ts +0 -26
- package/dist/react/deltas.d.ts.map +0 -1
- package/dist/react/deltas.js +0 -384
- package/dist/react/deltas.js.map +0 -1
- package/dist/react/toUIMessages.d.ts +0 -15
- package/dist/react/toUIMessages.d.ts.map +0 -1
- package/dist/react/toUIMessages.js +0 -211
- package/dist/react/toUIMessages.js.map +0 -1
- package/src/client/listMessages.ts +0 -38
- package/src/component/_generated/api.d.ts +0 -2202
- package/src/component/_generated/api.js +0 -23
- package/src/component/_generated/server.js +0 -90
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/src/react/deltas.test.ts +0 -315
- package/src/react/deltas.ts +0 -478
- package/src/react/toUIMessages.test.ts +0 -420
- package/src/react/toUIMessages.ts +0 -253
- package/src/vitest.config.ts +0 -7
- /package/src/component/_generated/{dataModel.d.ts → dataModel.ts} +0 -0
package/src/react/index.ts
CHANGED
|
@@ -1,237 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
type PaginatedQueryArgs,
|
|
5
|
-
type UsePaginatedQueryResult,
|
|
6
|
-
useQuery,
|
|
7
|
-
} from "convex/react";
|
|
8
|
-
import { usePaginatedQuery } from "convex-helpers/react";
|
|
9
|
-
import type { FunctionArgs } from "convex/server";
|
|
10
|
-
import { useMemo, useRef, useState } from "react";
|
|
11
|
-
import type { MessageDoc } from "../client/index.js";
|
|
12
|
-
import type { SyncStreamsReturnValue } from "../client/types.js";
|
|
13
|
-
import type { StreamArgs } from "../validators.js";
|
|
14
|
-
import type { UIMessage } from "./toUIMessages.js";
|
|
15
|
-
import { toUIMessages } from "./toUIMessages.js";
|
|
16
|
-
import { mergeDeltas } from "./deltas.js";
|
|
17
|
-
import type {
|
|
18
|
-
ThreadQuery,
|
|
19
|
-
ThreadStreamQuery,
|
|
20
|
-
ThreadMessagesArgs,
|
|
21
|
-
ThreadMessagesResult,
|
|
22
|
-
} from "./types.js";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
export { toUIMessages, type UIMessage } from "../UIMessages.js";
|
|
23
4
|
|
|
24
5
|
export { optimisticallySendMessage } from "./optimisticallySendMessage.js";
|
|
25
6
|
export { useSmoothText } from "./useSmoothText.js";
|
|
26
|
-
export {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* The query must take as arguments `{ threadId, paginationOpts }` and return a
|
|
35
|
-
* pagination result of objects that extend `MessageDoc`.
|
|
36
|
-
*
|
|
37
|
-
* For streaming, it should look like this:
|
|
38
|
-
* ```ts
|
|
39
|
-
* export const listThreadMessages = query({
|
|
40
|
-
* args: {
|
|
41
|
-
* threadId: v.string(),
|
|
42
|
-
* paginationOpts: paginationOptsValidator,
|
|
43
|
-
* streamArgs: vStreamArgs,
|
|
44
|
-
* ... other arguments you want
|
|
45
|
-
* },
|
|
46
|
-
* handler: async (ctx, { threadId, paginationOpts, streamArgs }) => {
|
|
47
|
-
* // await authorizeThreadAccess(ctx, threadId);
|
|
48
|
-
* const paginated = await agent.listMessages(ctx, { threadId, paginationOpts });
|
|
49
|
-
* const streams = await agent.syncStreams(ctx, { threadId, streamArgs });
|
|
50
|
-
* // Here you could filter out / modify the documents & stream deltas.
|
|
51
|
-
* return { ...paginated, streams };
|
|
52
|
-
* },
|
|
53
|
-
* });
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* Then the hook can be used like this:
|
|
57
|
-
* ```ts
|
|
58
|
-
* const messages = useThreadMessages(
|
|
59
|
-
* api.myModule.listThreadMessages,
|
|
60
|
-
* { threadId },
|
|
61
|
-
* { initialNumItems: 10, stream: true }
|
|
62
|
-
* );
|
|
63
|
-
* ```
|
|
64
|
-
*
|
|
65
|
-
* @param query The query to use to fetch messages.
|
|
66
|
-
* It must take as arguments `{ threadId, paginationOpts }` and return a
|
|
67
|
-
* pagination result of objects that extend `MessageDoc`.
|
|
68
|
-
* To support streaming, it must also take in `streamArgs: vStreamArgs` and
|
|
69
|
-
* return a `streams` object returned from `agent.syncStreams`.
|
|
70
|
-
* @param args The arguments to pass to the query other than `paginationOpts`
|
|
71
|
-
* and `streamArgs`. So `{ threadId }` at minimum, plus any other arguments that
|
|
72
|
-
* you want to pass to the query.
|
|
73
|
-
* @param options The options for the query. Similar to usePaginatedQuery.
|
|
74
|
-
* To enable streaming, pass `stream: true`.
|
|
75
|
-
* @returns The messages. If stream is true, it will return a list of messages
|
|
76
|
-
* that includes both full messages and streaming messages.
|
|
77
|
-
*/
|
|
78
|
-
export function useThreadMessages<
|
|
79
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
80
|
-
Query extends ThreadQuery<any, any>,
|
|
81
|
-
>(
|
|
82
|
-
query: Query,
|
|
83
|
-
args: ThreadMessagesArgs<Query> | "skip",
|
|
84
|
-
options: {
|
|
85
|
-
initialNumItems: number;
|
|
86
|
-
stream?: Query extends ThreadStreamQuery
|
|
87
|
-
? boolean
|
|
88
|
-
: ErrorMessage<"To enable streaming, your query must take in streamArgs: vStreamArgs and return a streams object returned from agent.syncStreams. See docs.">;
|
|
89
|
-
},
|
|
90
|
-
): UsePaginatedQueryResult<
|
|
91
|
-
ThreadMessagesResult<Query> & { streaming?: boolean }
|
|
92
|
-
> {
|
|
93
|
-
// These are full messages
|
|
94
|
-
const paginated = usePaginatedQuery(
|
|
95
|
-
query,
|
|
96
|
-
args as PaginatedQueryArgs<Query> | "skip",
|
|
97
|
-
{ initialNumItems: options.initialNumItems },
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
// These are streaming messages that will not include full messages.
|
|
101
|
-
const streamMessages = useStreamingThreadMessages(
|
|
102
|
-
query as ThreadStreamQuery<
|
|
103
|
-
ThreadMessagesArgs<Query>,
|
|
104
|
-
ThreadMessagesResult<Query>
|
|
105
|
-
>,
|
|
106
|
-
!options.stream ||
|
|
107
|
-
args === "skip" ||
|
|
108
|
-
paginated.status === "LoadingFirstPage"
|
|
109
|
-
? "skip"
|
|
110
|
-
: { ...args, startOrder: paginated.results.at(-1)?.order },
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
const merged = useMemo(() => {
|
|
114
|
-
const streamListMessages =
|
|
115
|
-
streamMessages?.map((m) => ({
|
|
116
|
-
...m,
|
|
117
|
-
streaming: !m.status || m.status === "pending",
|
|
118
|
-
})) ?? [];
|
|
119
|
-
return {
|
|
120
|
-
...paginated,
|
|
121
|
-
results: paginated.results
|
|
122
|
-
.map((m) => ({ ...m, streaming: false }))
|
|
123
|
-
.concat(streamListMessages)
|
|
124
|
-
.sort((a, b) =>
|
|
125
|
-
a.order === b.order ? a.stepOrder - b.stepOrder : a.order - b.order,
|
|
126
|
-
)
|
|
127
|
-
// They shouldn't overlap, but check for duplicates just in case.
|
|
128
|
-
.filter(
|
|
129
|
-
(m, i, arr) =>
|
|
130
|
-
!arr[i - 1] ||
|
|
131
|
-
m.order !== arr[i - 1].order ||
|
|
132
|
-
m.stepOrder !== arr[i - 1].stepOrder,
|
|
133
|
-
),
|
|
134
|
-
};
|
|
135
|
-
}, [paginated, streamMessages]);
|
|
136
|
-
|
|
137
|
-
return merged as ThreadMessagesResult<Query>;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* A hook that fetches streaming messages from a thread.
|
|
142
|
-
* This ONLY returns streaming messages. To get both, use `useThreadMessages`.
|
|
143
|
-
*
|
|
144
|
-
* @param query The query to use to fetch messages.
|
|
145
|
-
* It must take as arguments `{ threadId, paginationOpts, streamArgs }` and
|
|
146
|
-
* return a `streams` object returned from `agent.syncStreams`.
|
|
147
|
-
* @param args The arguments to pass to the query other than `paginationOpts`
|
|
148
|
-
* and `streamArgs`. So `{ threadId }` at minimum, plus any other arguments that
|
|
149
|
-
* you want to pass to the query.
|
|
150
|
-
* @returns The streaming messages.
|
|
151
|
-
*/
|
|
152
|
-
export function useStreamingThreadMessages<
|
|
153
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
154
|
-
Query extends ThreadStreamQuery<any, any>,
|
|
155
|
-
>(
|
|
156
|
-
query: Query,
|
|
157
|
-
args: (ThreadMessagesArgs<Query> & { startOrder?: number }) | "skip",
|
|
158
|
-
): Array<ThreadMessagesResult<Query>> | undefined {
|
|
159
|
-
// Invariant: streamMessages[streamId] is comprised of all deltas up to the
|
|
160
|
-
// cursor. There can be multiple messages in the same stream, e.g. for tool
|
|
161
|
-
// calls.
|
|
162
|
-
const [streams, setStreams] = useState<
|
|
163
|
-
Array<{ streamId: string; cursor: number; messages: MessageDoc[] }>
|
|
164
|
-
>([]);
|
|
165
|
-
const startOrderRef = useRef<number>(0);
|
|
166
|
-
const queryArgs = args === "skip" ? args : omit(args, ["startOrder"]);
|
|
167
|
-
if (args !== "skip" && !startOrderRef.current && args.startOrder) {
|
|
168
|
-
startOrderRef.current = args.startOrder;
|
|
169
|
-
}
|
|
170
|
-
// Get all the active streams
|
|
171
|
-
const streamList = useQuery(
|
|
172
|
-
query,
|
|
173
|
-
queryArgs === "skip"
|
|
174
|
-
? queryArgs
|
|
175
|
-
: ({
|
|
176
|
-
...queryArgs,
|
|
177
|
-
paginationOpts: { cursor: null, numItems: 0 },
|
|
178
|
-
streamArgs: {
|
|
179
|
-
kind: "list",
|
|
180
|
-
startOrder: startOrderRef.current,
|
|
181
|
-
} as StreamArgs,
|
|
182
|
-
} as FunctionArgs<Query>),
|
|
183
|
-
) as
|
|
184
|
-
| { streams: Extract<SyncStreamsReturnValue, { kind: "list" }> }
|
|
185
|
-
| undefined;
|
|
186
|
-
// Get the cursors for all the active streams
|
|
187
|
-
const cursors = useMemo(() => {
|
|
188
|
-
if (!streamList?.streams) return [];
|
|
189
|
-
if (streamList.streams.kind !== "list") {
|
|
190
|
-
throw new Error("Expected list streams");
|
|
191
|
-
}
|
|
192
|
-
return streamList.streams.messages.map(({ streamId }) => {
|
|
193
|
-
const stream = streams.find((s) => s.streamId === streamId);
|
|
194
|
-
const cursor = stream?.cursor ?? 0;
|
|
195
|
-
return { streamId, cursor };
|
|
196
|
-
});
|
|
197
|
-
}, [streamList, streams]);
|
|
198
|
-
// Get the deltas for all the active streams, if any.
|
|
199
|
-
const cursorQuery = useQuery(
|
|
200
|
-
query,
|
|
201
|
-
queryArgs === "skip" || !streamList
|
|
202
|
-
? ("skip" as const)
|
|
203
|
-
: ({
|
|
204
|
-
...queryArgs,
|
|
205
|
-
paginationOpts: { cursor: null, numItems: 0 },
|
|
206
|
-
streamArgs: { kind: "deltas", cursors } as StreamArgs,
|
|
207
|
-
} as FunctionArgs<Query>),
|
|
208
|
-
) as
|
|
209
|
-
| { streams: Extract<SyncStreamsReturnValue, { kind: "deltas" }> }
|
|
210
|
-
| undefined;
|
|
211
|
-
// Merge any deltas into the streamChunks, keeping it unmodified if unchanged.
|
|
212
|
-
const threadId = args === "skip" ? undefined : args.threadId;
|
|
213
|
-
const [messages, newStreams, changed] = useMemo(() => {
|
|
214
|
-
if (!threadId) return [undefined, [], false];
|
|
215
|
-
if (!streamList) return [undefined, [], false];
|
|
216
|
-
if (cursorQuery && cursorQuery.streams?.kind !== "deltas") {
|
|
217
|
-
throw new Error("Expected deltas streams");
|
|
218
|
-
}
|
|
219
|
-
return mergeDeltas(
|
|
220
|
-
threadId,
|
|
221
|
-
streamList.streams.messages,
|
|
222
|
-
streams,
|
|
223
|
-
cursorQuery?.streams?.deltas ?? [],
|
|
224
|
-
);
|
|
225
|
-
}, [threadId, cursorQuery, streams, streamList]);
|
|
226
|
-
// Now assemble the chunks into messages
|
|
227
|
-
if (!threadId) {
|
|
228
|
-
return undefined;
|
|
229
|
-
}
|
|
230
|
-
if (changed) {
|
|
231
|
-
setStreams(newStreams);
|
|
232
|
-
}
|
|
233
|
-
return messages as ThreadMessagesResult<Query>[] | undefined;
|
|
234
|
-
}
|
|
7
|
+
export { SmoothText } from "./SmoothText.js";
|
|
8
|
+
export {
|
|
9
|
+
type ThreadMessagesQuery,
|
|
10
|
+
useThreadMessages,
|
|
11
|
+
useStreamingThreadMessages,
|
|
12
|
+
} from "./useThreadMessages.js";
|
|
13
|
+
export { type UIMessagesQuery, useUIMessages } from "./useUIMessages.js";
|
|
14
|
+
export { useStreamingUIMessages } from "./useStreamingUIMessages.js";
|
|
235
15
|
|
|
236
16
|
/**
|
|
237
17
|
* @deprecated use useThreadMessages or useStreamingThreadMessages instead
|
|
@@ -1,10 +1,52 @@
|
|
|
1
1
|
import { insertAtTop } from "convex/react";
|
|
2
|
-
import type { MessageDoc } from "../
|
|
2
|
+
import type { MessageDoc, StreamArgs } from "../validators.js";
|
|
3
3
|
import type { OptimisticLocalStore } from "convex/browser";
|
|
4
|
-
import type {
|
|
4
|
+
import type { UIMessage } from "../UIMessages.js";
|
|
5
|
+
import type {
|
|
6
|
+
FunctionReference,
|
|
7
|
+
PaginationOptions,
|
|
8
|
+
PaginationResult,
|
|
9
|
+
} from "convex/server";
|
|
10
|
+
import type { SyncStreamsReturnValue } from "@convex-dev/agent";
|
|
5
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Adds a sent message to the end of a list of messages, so it shows up until
|
|
14
|
+
* the message is saved on the server and arrives in the query.
|
|
15
|
+
* It generates a message with fields that match both MessageDoc and UIMessage,
|
|
16
|
+
* for convenience. It will not include any other fields you might have in your
|
|
17
|
+
* regular query, however.
|
|
18
|
+
*
|
|
19
|
+
* @param query The query used to fetch messages, typically with
|
|
20
|
+
* useThreadMessages or useUIMessages.
|
|
21
|
+
* @returns A function that can be used to optimistically send a message.
|
|
22
|
+
* If your mutation takes different arguments than { threadId, prompt }, you can
|
|
23
|
+
* use it as a helper function in your optimistic update:
|
|
24
|
+
* ```ts
|
|
25
|
+
* const sendMessage = useMutation(
|
|
26
|
+
* api.chatStreaming.streamStoryAsynchronously,
|
|
27
|
+
* ).withOptimisticUpdate(
|
|
28
|
+
* (store, args) => {
|
|
29
|
+
* optimisticallySendMessage(api.chatStreaming.listThreadMessages)(store, {
|
|
30
|
+
* threadId:
|
|
31
|
+
* prompt: whatever you would have passed to the mutation,
|
|
32
|
+
* })
|
|
33
|
+
* }
|
|
34
|
+
* );
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
6
37
|
export function optimisticallySendMessage(
|
|
7
|
-
query:
|
|
38
|
+
query: FunctionReference<
|
|
39
|
+
"query",
|
|
40
|
+
"public",
|
|
41
|
+
{
|
|
42
|
+
threadId: string;
|
|
43
|
+
paginationOpts: PaginationOptions;
|
|
44
|
+
streamArgs?: StreamArgs;
|
|
45
|
+
},
|
|
46
|
+
PaginationResult<MessageDoc | UIMessage> & {
|
|
47
|
+
streams?: SyncStreamsReturnValue;
|
|
48
|
+
}
|
|
49
|
+
>,
|
|
8
50
|
): (
|
|
9
51
|
store: OptimisticLocalStore,
|
|
10
52
|
args: { threadId: string; prompt: string },
|
|
@@ -12,33 +54,34 @@ export function optimisticallySendMessage(
|
|
|
12
54
|
return (store, args) => {
|
|
13
55
|
const queries = store.getAllQueries(query);
|
|
14
56
|
let maxOrder = -1;
|
|
15
|
-
let maxStepOrder = 0;
|
|
16
57
|
for (const q of queries) {
|
|
17
58
|
if (q.args?.threadId !== args.threadId) continue;
|
|
18
59
|
if (q.args.streamArgs) continue;
|
|
19
60
|
for (const m of q.value?.page ?? []) {
|
|
20
61
|
maxOrder = Math.max(maxOrder, m.order);
|
|
21
|
-
maxStepOrder = Math.max(maxStepOrder, m.stepOrder);
|
|
22
62
|
}
|
|
23
63
|
}
|
|
24
64
|
const order = maxOrder + 1;
|
|
25
65
|
const stepOrder = 0;
|
|
66
|
+
const id = randomUUID();
|
|
67
|
+
const { prompt, ...rest } = args;
|
|
26
68
|
insertAtTop({
|
|
27
69
|
paginatedQuery: query,
|
|
28
70
|
argsToMatch: { threadId: args.threadId, streamArgs: undefined },
|
|
29
71
|
item: {
|
|
72
|
+
...rest,
|
|
30
73
|
_creationTime: Date.now(),
|
|
31
|
-
_id:
|
|
74
|
+
_id: id,
|
|
75
|
+
id,
|
|
76
|
+
key: `${args.threadId}-${order}-${stepOrder}`,
|
|
32
77
|
order,
|
|
33
78
|
stepOrder,
|
|
34
79
|
status: "pending",
|
|
35
|
-
threadId: args.threadId,
|
|
36
80
|
tool: false,
|
|
37
|
-
message: {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
text: args.prompt,
|
|
81
|
+
message: { role: "user", content: prompt },
|
|
82
|
+
parts: [{ type: "text", text: prompt }],
|
|
83
|
+
role: "user",
|
|
84
|
+
text: prompt,
|
|
42
85
|
},
|
|
43
86
|
localQueryStore: store,
|
|
44
87
|
});
|
package/src/react/types.ts
CHANGED
|
@@ -1,52 +1,19 @@
|
|
|
1
1
|
import type { BetterOmit, Expand } from "convex-helpers";
|
|
2
|
-
import type {
|
|
3
|
-
FunctionArgs,
|
|
4
|
-
FunctionReference,
|
|
5
|
-
PaginationOptions,
|
|
6
|
-
PaginationResult,
|
|
7
|
-
} from "convex/server";
|
|
8
|
-
import type { MessageDoc } from "../client/index.js";
|
|
2
|
+
import type { FunctionArgs, FunctionReference } from "convex/server";
|
|
9
3
|
import type { SyncStreamsReturnValue } from "../client/types.js";
|
|
10
4
|
import type { StreamArgs } from "../validators.js";
|
|
11
5
|
|
|
12
|
-
export type
|
|
13
|
-
Args = unknown,
|
|
14
|
-
M extends MessageDoc = MessageDoc,
|
|
15
|
-
> = FunctionReference<
|
|
6
|
+
export type StreamQuery<Args = Record<string, unknown>> = FunctionReference<
|
|
16
7
|
"query",
|
|
17
8
|
"public",
|
|
18
9
|
{
|
|
19
10
|
threadId: string;
|
|
20
|
-
paginationOpts: PaginationOptions;
|
|
21
|
-
// TODO: will this allow passing a function that doesn't have this param?
|
|
22
|
-
/**
|
|
23
|
-
* If { stream: true } is passed, it will also query for stream deltas.
|
|
24
|
-
* In order for this to work, the query must take as an argument streamArgs.
|
|
25
|
-
*/
|
|
26
|
-
streamArgs?: StreamArgs;
|
|
27
|
-
} & Args,
|
|
28
|
-
PaginationResult<M> & { streams?: SyncStreamsReturnValue }
|
|
29
|
-
>;
|
|
30
|
-
|
|
31
|
-
export type ThreadStreamQuery<
|
|
32
|
-
Args = Record<string, unknown>,
|
|
33
|
-
M extends MessageDoc = MessageDoc,
|
|
34
|
-
> = FunctionReference<
|
|
35
|
-
"query",
|
|
36
|
-
"public",
|
|
37
|
-
{
|
|
38
|
-
threadId: string;
|
|
39
|
-
paginationOpts: PaginationOptions;
|
|
40
11
|
streamArgs?: StreamArgs; // required for stream query
|
|
41
12
|
} & Args,
|
|
42
|
-
|
|
13
|
+
{ streams: SyncStreamsReturnValue }
|
|
43
14
|
>;
|
|
44
15
|
|
|
45
|
-
export type
|
|
46
|
-
Query extends
|
|
47
|
-
? Expand<BetterOmit<FunctionArgs<Query>, "
|
|
16
|
+
export type StreamQueryArgs<Query extends StreamQuery<unknown>> =
|
|
17
|
+
Query extends StreamQuery<unknown>
|
|
18
|
+
? Expand<BetterOmit<FunctionArgs<Query>, "streamArgs">>
|
|
48
19
|
: never;
|
|
49
|
-
|
|
50
|
-
export type ThreadMessagesResult<
|
|
51
|
-
Query extends ThreadQuery<unknown, MessageDoc>,
|
|
52
|
-
> = Query extends ThreadQuery<unknown, infer M> ? M : never;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { StreamQuery, StreamQueryArgs } from "./types.js";
|
|
4
|
+
import type { SyncStreamsReturnValue } from "../client/types.js";
|
|
5
|
+
import type { FunctionArgs } from "convex/server";
|
|
6
|
+
import type { StreamArgs, StreamDelta, StreamMessage } from "../validators.js";
|
|
7
|
+
import { sorted } from "../shared.js";
|
|
8
|
+
import { useQuery } from "convex/react";
|
|
9
|
+
import { useState } from "react";
|
|
10
|
+
import { assert } from "convex-helpers";
|
|
11
|
+
|
|
12
|
+
export function useDeltaStreams<
|
|
13
|
+
Query extends StreamQuery<any> = StreamQuery<object>,
|
|
14
|
+
>(
|
|
15
|
+
query: Query,
|
|
16
|
+
args: StreamQueryArgs<Query> | "skip",
|
|
17
|
+
options?: {
|
|
18
|
+
startOrder?: number;
|
|
19
|
+
skipStreamIds?: string[];
|
|
20
|
+
},
|
|
21
|
+
): { streamMessage: StreamMessage; deltas: StreamDelta[] }[] | undefined {
|
|
22
|
+
// We hold onto and modify state directly to avoid re-running unnecessarily.
|
|
23
|
+
const [state] = useState<{
|
|
24
|
+
startOrder: number;
|
|
25
|
+
threadId: string | undefined;
|
|
26
|
+
deltaStreams:
|
|
27
|
+
| Array<{
|
|
28
|
+
streamMessage: StreamMessage;
|
|
29
|
+
deltas: StreamDelta[];
|
|
30
|
+
}>
|
|
31
|
+
| undefined;
|
|
32
|
+
}>({
|
|
33
|
+
startOrder: options?.startOrder ?? 0,
|
|
34
|
+
deltaStreams: undefined,
|
|
35
|
+
threadId: args === "skip" ? undefined : args.threadId,
|
|
36
|
+
});
|
|
37
|
+
const [cursors, setCursors] = useState<Record<string, number>>({});
|
|
38
|
+
if (args !== "skip" && state.threadId !== args.threadId) {
|
|
39
|
+
state.threadId = args.threadId;
|
|
40
|
+
state.deltaStreams = undefined;
|
|
41
|
+
state.startOrder = options?.startOrder ?? 0;
|
|
42
|
+
setCursors({});
|
|
43
|
+
}
|
|
44
|
+
if (
|
|
45
|
+
state.deltaStreams?.length ||
|
|
46
|
+
(options?.startOrder && options.startOrder < state.startOrder)
|
|
47
|
+
) {
|
|
48
|
+
const cacheFriendlyStartOrder = options?.startOrder
|
|
49
|
+
? // round down to the nearest 10 for some cache benefits
|
|
50
|
+
options.startOrder - (options.startOrder % 10)
|
|
51
|
+
: 0;
|
|
52
|
+
if (cacheFriendlyStartOrder !== state.startOrder) {
|
|
53
|
+
state.startOrder = cacheFriendlyStartOrder;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Get all the active streams
|
|
58
|
+
const streamList = useQuery(
|
|
59
|
+
query,
|
|
60
|
+
args === "skip"
|
|
61
|
+
? args
|
|
62
|
+
: ({
|
|
63
|
+
...args,
|
|
64
|
+
streamArgs: {
|
|
65
|
+
kind: "list",
|
|
66
|
+
startOrder: state.startOrder,
|
|
67
|
+
} as StreamArgs,
|
|
68
|
+
} as FunctionArgs<Query>),
|
|
69
|
+
) as
|
|
70
|
+
| { streams: Extract<SyncStreamsReturnValue, { kind: "list" }> }
|
|
71
|
+
| undefined;
|
|
72
|
+
|
|
73
|
+
const streamMessages =
|
|
74
|
+
args === "skip"
|
|
75
|
+
? undefined
|
|
76
|
+
: !streamList
|
|
77
|
+
? state.deltaStreams?.map(({ streamMessage }) => streamMessage)
|
|
78
|
+
: sorted(
|
|
79
|
+
streamList.streams.messages.filter(
|
|
80
|
+
({ streamId, order }) =>
|
|
81
|
+
!options?.skipStreamIds?.includes(streamId) &&
|
|
82
|
+
(!options?.startOrder || order >= options.startOrder),
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// When no active streams remain, clear the stale state so we stop
|
|
87
|
+
// returning old streaming UIMessages.
|
|
88
|
+
if (streamMessages !== undefined && streamMessages.length === 0) {
|
|
89
|
+
state.deltaStreams = undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Get the deltas for all the active streams, if any.
|
|
93
|
+
const cursorQuery = useQuery(
|
|
94
|
+
query,
|
|
95
|
+
args === "skip" || !streamMessages?.length
|
|
96
|
+
? ("skip" as const)
|
|
97
|
+
: ({
|
|
98
|
+
...args,
|
|
99
|
+
streamArgs: {
|
|
100
|
+
kind: "deltas",
|
|
101
|
+
cursors: streamMessages.map(({ streamId }) => ({
|
|
102
|
+
streamId,
|
|
103
|
+
cursor: cursors[streamId] ?? 0,
|
|
104
|
+
})),
|
|
105
|
+
} as StreamArgs,
|
|
106
|
+
} as FunctionArgs<Query>),
|
|
107
|
+
) as
|
|
108
|
+
| { streams: Extract<SyncStreamsReturnValue, { kind: "deltas" }> }
|
|
109
|
+
| undefined;
|
|
110
|
+
|
|
111
|
+
const newDeltas = cursorQuery?.streams.deltas;
|
|
112
|
+
if (newDeltas?.length && streamMessages) {
|
|
113
|
+
const newDeltasByStreamId = new Map<string, StreamDelta[]>();
|
|
114
|
+
for (const delta of newDeltas) {
|
|
115
|
+
const oldCursor = cursors[delta.streamId];
|
|
116
|
+
if (oldCursor && delta.start < oldCursor) continue;
|
|
117
|
+
const existing = newDeltasByStreamId.get(delta.streamId);
|
|
118
|
+
if (existing) {
|
|
119
|
+
const previousEnd = existing.at(-1)!.end;
|
|
120
|
+
assert(
|
|
121
|
+
previousEnd === delta.start,
|
|
122
|
+
`Gap found in deltas for ${delta.streamId} jumping to ${delta.start} from ${previousEnd}`,
|
|
123
|
+
);
|
|
124
|
+
existing.push(delta);
|
|
125
|
+
} else {
|
|
126
|
+
assert(
|
|
127
|
+
!oldCursor || oldCursor === delta.start,
|
|
128
|
+
`Gap found - first delta after ${oldCursor} is ${delta.start} for stream ${delta.streamId}`,
|
|
129
|
+
);
|
|
130
|
+
newDeltasByStreamId.set(delta.streamId, [delta]);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const newCursors: Record<string, number> = {};
|
|
134
|
+
for (const { streamId } of streamMessages) {
|
|
135
|
+
const cursor =
|
|
136
|
+
newDeltasByStreamId.get(streamId)?.at(-1)?.end ?? cursors[streamId];
|
|
137
|
+
if (cursor !== undefined) {
|
|
138
|
+
newCursors[streamId] = cursor;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
setCursors(newCursors);
|
|
142
|
+
|
|
143
|
+
// we defensively create a new object so object identity matches contents
|
|
144
|
+
state.deltaStreams = streamMessages.map((streamMessage) => {
|
|
145
|
+
const streamId = streamMessage.streamId;
|
|
146
|
+
const old = state.deltaStreams?.find(
|
|
147
|
+
(ds) => ds.streamMessage.streamId === streamId,
|
|
148
|
+
);
|
|
149
|
+
const newDeltas = newDeltasByStreamId.get(streamId);
|
|
150
|
+
if (!newDeltas && streamMessage === old?.streamMessage) {
|
|
151
|
+
return old;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
streamMessage,
|
|
155
|
+
deltas: [...(old?.deltas ?? []), ...(newDeltas ?? [])],
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return state.deltaStreams;
|
|
160
|
+
}
|